import { IEventEmitter } from "@inweb/eventemitter2";
import { ILine, IText, IViewpoint } from "@inweb/viewer-core";
import { IMarkup, IMarkupObject, IWorldTransform, MarkupMode } from "@inweb/markup";
import { Viewer } from "../../Viewer";
import { MARKUP_ENTITY_LINE, OdaLineDragger } from "../../Draggers/OdaLineDragger";
import { MARKUP_ENTITY_TEXT, OdaTextDragger } from "../../Draggers/OdaTextDragger";

export class VisualizeMarkup implements IMarkup {
  private _viewer: Viewer;
  protected _markupColor = { r: 255, g: 0, b: 0 };

  public lineWidth = 4;
  public lineType: "solid";
  public fontSize = 34;

  initialize(
    container: HTMLElement,
    containerEvents: string[],
    viewer?: IEventEmitter,
    worldTransformer?: IWorldTransform
  ): void {
    this._viewer = viewer as Viewer;
    this._viewer.registerDragger("Line", OdaLineDragger);
    this._viewer.registerDragger("Text", OdaTextDragger);
  }

  dispose(): void {}

  syncOverlay(): void {}

  clearOverlay(): void {
    if (!this._viewer.visualizeJs) return;

    const visViewer = this._viewer.visViewer();
    const model = visViewer.getMarkupModel();
    model.clearEntities();
    model.delete();
  }

  getMarkupColor(): { r: number; g: number; b: number } {
    return this._markupColor;
  }

  setMarkupColor(r: number, g: number, b: number): void {
    const color = { r, g, b };
    this._markupColor = color;
    this._viewer.emitEvent({ type: "changemarkupcolor", data: color });
  }

  colorizeAllMarkup(r = 255, g = 0, b = 0): void {
    if (!this._viewer.visualizeJs) return;

    const visViewer = this._viewer.visViewer();
    const model = visViewer.getMarkupModel();
    const itr = model.getEntitiesIterator();
    for (; !itr.done(); itr.step()) {
      const entityId = itr.getEntity();
      const entityPtr = entityId.openObject();
      const entityName = entityPtr.getName();

      if (entityName === MARKUP_ENTITY_LINE || entityName === MARKUP_ENTITY_TEXT) {
        entityPtr.setColor(r, g, b);
      }

      entityPtr.delete();
    }
    itr.delete();

    this._viewer.update();
  }

  colorizeSelectedMarkups(r = 255, g = 0, b = 0): void {
    throw new Error("Not implemented yet");
  }

  setViewpoint(viewpoint: IViewpoint): void {
    function getLogicalPoint3dAsArray(point3d) {
      return [point3d.x, point3d.y, point3d.z];
    }

    function getPoint3d(module, gePoint) {
      return module.Point3d.createFromArray(gePoint);
    }

    if (!this._viewer.visualizeJs) return;

    const visLib = this._viewer.visLib();
    const visViewer = visLib.getViewer();
    const activeView = visViewer.activeView;

    this._viewer.syncOverlay();

    const markupColor = viewpoint.custom_fields.markup_color || { r: 255, g: 0, b: 0 };
    this.setMarkupColor(markupColor.r, markupColor.g, markupColor.b);

    if (viewpoint.lines) {
      for (const line of viewpoint.lines) {
        const entityId = this._viewer.addMarkupEntity(MARKUP_ENTITY_LINE);
        const entityPtr = entityId.openObject();

        const entityData = [];
        for (const point of line.points) {
          entityData.push(point.x, point.y, point.z);
        }
        const geomData = entityPtr.appendPolyline(entityData);

        geomData.delete();
        entityPtr.delete();
      }
    }

    if (viewpoint.texts) {
      const pos = getPoint3d(visLib, activeView.viewPosition);
      const target = getPoint3d(visLib, activeView.viewTarget);
      const normal = pos.sub(target).asVector();

      for (const text of viewpoint.texts) {
        const entityId = this._viewer.addMarkupEntity(MARKUP_ENTITY_TEXT);
        const entityPtr = entityId.openObject();

        const geomData = entityPtr.appendText(getLogicalPoint3dAsArray(text.position), text.text);

        const textPtr = geomData.openAsText();
        textPtr.setNormal(getLogicalPoint3dAsArray(normal));
        textPtr.setRotation(text.angle);
        textPtr.setTextSize(text.text_size);

        textPtr.delete();
        geomData.delete();
        entityPtr.delete();
      }
    }

    this._viewer.update();
  }

  getViewpoint(viewpoint: IViewpoint): IViewpoint {
    if (!this._viewer.visualizeJs) return {};

    function getLogicalPoint3dFromArray(array) {
      return { x: array[0], y: array[1], z: array[2] };
    }

    const visLib = this._viewer.visLib();
    const visViewer = visLib.getViewer();

    if (!viewpoint) viewpoint = {};

    viewpoint.lines = [];
    viewpoint.texts = [];

    const model = visViewer.getMarkupModel();
    const itr = model.getEntitiesIterator();
    for (; !itr.done(); itr.step()) {
      const entityId = itr.getEntity();
      const entityPtr = entityId.openObject();
      const entityName = entityPtr.getName();

      const geomItr = entityPtr.getGeometryDataIterator();
      if (geomItr.done()) {
        entityPtr.delete();
        continue;
      }

      const geometryId = geomItr.getGeometryData();

      if (entityName === MARKUP_ENTITY_LINE) {
        const polylinePtr = geometryId.openAsPolyline();
        const points = polylinePtr.getPoints();

        const line: ILine = {
          points: [],
        };
        for (const point of points) {
          line.points.push(getLogicalPoint3dFromArray(point));
        }

        viewpoint.lines.push(line);
        polylinePtr.delete();
      } else if (entityName === MARKUP_ENTITY_TEXT) {
        const textPtr = geometryId.openAsText();
        const position = textPtr.getPosition();

        const text: IText = {
          position: getLogicalPoint3dFromArray(position),
          text: textPtr.getString(),
          angle: textPtr.getRotation(),
          text_size: textPtr.getTextSize(),
        };

        viewpoint.texts.push(text);
        textPtr.delete();
      }

      entityPtr.delete();
    }
    itr.delete();

    viewpoint.snapshot = {
      data: visLib.canvas.toDataURL("image/jpeg", 0.25),
    };

    viewpoint.custom_fields = {
      markup_color: this.getMarkupColor(),
    };

    return viewpoint;
  }

  enableEditMode(mode: MarkupMode | false): this {
    return this;
  }

  createObject(type: string, params: any): IMarkupObject {
    return undefined;
  }

  getObjects(): IMarkupObject[] {
    return [];
  }

  getSelectedObjects(): IMarkupObject[] {
    return [];
  }

  selectObjects(objects: IMarkupObject[]): void {}

  clearSelected(): void {}
}
