///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
//   This application incorporates Open Design Alliance software pursuant to a
//   license agreement with Open Design Alliance.
//   Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////
import { Viewer } from "../../Viewer";
import { OdBaseDragger } from "../Common/OdBaseDragger";
import { MeasureLineItem } from "./MeasureLineItem";
import { IOptions } from "@inweb/viewer-core";

function renameUnit(table, unit) {
  return table[unit] || unit;
}

export class MeasureLineDragger extends OdBaseDragger {
  protected lineThickness: number;
  protected gripingRadius: number;
  protected firstPoint: number[];
  protected secondPoint: number[];
  protected renameUnitTable: any;
  protected items: MeasureLineItem[];
  protected m_overlayElement: HTMLElement;
  protected previewMeasureLine: MeasureLineItem;
  protected conversionFactor: number;
  protected oldRulerUnit: string;

  constructor(subject: Viewer) {
    super(subject);
    this.lineThickness = 2;
    this.press = false;
    this.gripingRadius = 5.0;
    this.firstPoint = null;
    this.secondPoint = null;

    this.renameUnitTable = {
      Millimeters: "mm",
      Centimeters: "cm",
      Meters: "m",
      Feet: "ft",
      Inches: "in",
      Yards: "yd",
      Kilometers: "km",
      Miles: "mi",
      Micrometers: "µm",
      MicroInches: "µin",
      Default: "unit",
    };

    this.items = [];
    this.canvasEvents.push("resize");
    this.oldRulerUnit = subject.options.rulerUnit ?? "Default";
    this.optionsChange = this.optionsChange.bind(this);
  }

  override initialize(): void {
    super.initialize();

    this.m_overlayElement = document.createElement("div");
    this.m_overlayElement.style.background = "rgba(0,0,0,0)";
    this.m_overlayElement.style.position = "fixed";
    this.m_overlayElement.style.zIndex = "1";
    this.m_overlayElement.style.pointerEvents = "none";
    document.body.appendChild(this.m_overlayElement);
    this.subject.addEventListener("optionschange", this.optionsChange);

    this.resize();
  }

  override dispose(): void {
    super.dispose();
    this.m_overlayElement.remove();
    this.subject.removeEventListener("optionschange", this.optionsChange);
  }

  override updatePreview(): void {
    this.items.forEach((item) => item.update());
  }

  resize(): void {
    const rect = this.m_module.canvas.getBoundingClientRect();

    this.m_overlayElement.style.top = `${rect.top}px`;
    this.m_overlayElement.style.left = `${rect.left}px`;
    this.m_overlayElement.style.width = `${rect.width}px`;
    this.m_overlayElement.style.height = `${rect.height}px`;
  }

  getSnapPointRadius(): number {
    const view = this.getViewer().activeView;
    const corners = view.viewDcCorners();

    const pt1 = corners.lowerLeft;
    const pt2 = corners.upperRight;

    pt2[0] -= pt1[0];
    pt2[1] -= pt1[1];

    return Math.min(pt2[0], pt2[1]) / 120;
  }

  override start(x: number, y: number): void {
    this.createNewMeasureIfNeed();
    const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius);

    if (point) {
      this.firstPoint = point;
      this.previewMeasureLine.setStartPoint(this.firstPoint);
    }
  }

  override drag(x: number, y: number): void {
    this.createNewMeasureIfNeed();
    const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius);

    if (this.isDragging) {
      if (point) {
        if (this.firstPoint) {
          this.secondPoint = point;
          this.previewMeasureLine.setStartPoint(this.firstPoint);
          this.previewMeasureLine.setEndPoint(this.secondPoint, true);
        } else {
          this.firstPoint = point;
          this.previewMeasureLine.setStartPoint(this.firstPoint);
        }
      } else {
        this.secondPoint = null;
        this.previewMeasureLine.clear();
        this.previewMeasureLine.setStartPoint(this.firstPoint);
        this.previewMeasureLine.setEndPoint(this.getViewer().screenToWorld(x, y), false);
      }
    } else {
      if (point) {
        this.previewMeasureLine.setStartPoint(point);
      } else {
        this.previewMeasureLine.clear();
      }
    }
  }

  override end(): void {
    if (this.firstPoint && this.secondPoint) {
      const newLineMeasure = this.createMeasureLine();
      newLineMeasure.setStartPoint(this.firstPoint);
      newLineMeasure.setEndPoint(this.secondPoint, true);
    }
    this.firstPoint = null;
    this.secondPoint = null;
    this.previewMeasureLine.clear();
  }

  createNewMeasureIfNeed(): void {
    if (!this.previewMeasureLine) {
      this.previewMeasureLine = this.createMeasureLine();
    }
  }

  createMeasureLine(): MeasureLineItem {
    const viewer = this.m_module.getViewer();
    const item = new MeasureLineItem(this.m_overlayElement, viewer, this.m_module);
    item.lineThickness = this.lineThickness || item.lineThickness;
    const isDefaultUnit = !this.subject.options.rulerUnit || this.subject.options.rulerUnit === "Default";
    item.setUnit(renameUnit(this.renameUnitTable, isDefaultUnit ? viewer.getUnit() : this.subject.options.rulerUnit));
    if (!isDefaultUnit) {
      const fromUnit = this.getKUnitByName(viewer.getUnit());
      const toUnit = this.getKUnitByName(this.subject.options.rulerUnit);
      const multiplier = viewer.getUnitsConversionCoef(fromUnit, toUnit);
      this.conversionFactor = 1 / multiplier;
      item.setConversionFactor(this.conversionFactor);
    } else {
      item.setConversionFactor(1.0);
    }
    this.items.push(item);
    return item;
  }

  optionsChange(event): void {
    const options: IOptions = event.data;
    const toUnitName = options.rulerUnit ?? "Default";
    if (this.oldRulerUnit === toUnitName) return;
    this.oldRulerUnit = toUnitName;

    const drawingUnit = this.m_module.getViewer().getUnit();
    const eToUnit = this.getKUnitByName(toUnitName);
    const eFromUnit = this.getKUnitByName(drawingUnit);

    this.items.forEach((item) => {
      if (toUnitName === "Default") {
        item.setUnit(renameUnit(this.renameUnitTable, drawingUnit));
        item.setConversionFactor(1.0);
      } else {
        item.setUnit(renameUnit(this.renameUnitTable, toUnitName));
        const multiplier = this.m_module.getViewer().getUnitsConversionCoef(eFromUnit, eToUnit);
        this.conversionFactor = 1 / multiplier;
        item.setConversionFactor(this.conversionFactor);
      }
    });
  }

  getKUnitByName(unitName: string): any {
    let eUnit = this.m_module.Units.kUserDefined;
    switch (unitName) {
      case "Millimeters":
        eUnit = this.m_module.Units.kMillimeters;
        break;
      case "Centimeters":
        eUnit = this.m_module.Units.kCentimeters;
        break;
      case "Meters":
        eUnit = this.m_module.Units.kMeters;
        break;
      case "Feet":
        eUnit = this.m_module.Units.kFeet;
        break;
      case "Inches":
        eUnit = this.m_module.Units.kInches;
        break;
      case "Yards":
        eUnit = this.m_module.Units.kYards;
        break;
      case "Kilometers":
        eUnit = this.m_module.Units.kKilometers;
        break;
      case "Miles":
        eUnit = this.m_module.Units.kMiles;
        break;
      case "Micrometers":
        eUnit = this.m_module.Units.kMicrometers;
        break;
      case "MicroInches":
        eUnit = this.m_module.Units.kMicroInches;
        break;
      default:
        break;
    }
    return eUnit;
  }
}
