import * as THREE from '../../libs/three.js/build/three.module.js';
import { Volume, BoxVolume } from './Volume.js';
import { Utils } from '../utils.js';
import { EventDispatcher } from '../EventDispatcher.js';

export class VolumeTool extends EventDispatcher {
  constructor(viewer) {
    super();

    this.viewer = viewer;
    this.renderer = viewer.renderer;

    this.addEventListener('start_inserting_volume', (e) => {
      this.viewer.dispatchEvent({
        type: 'cancel_insertions',
      });
    });

    this.scene = new THREE.Scene();
    this.scene.name = 'scene_volume';

    this.viewer.inputHandler.registerInteractiveScene(this.scene);

    this.onRemove = (e) => {
      this.scene.remove(e.volume);
    };

    this.onAdd = (e) => {
      this.scene.add(e.volume);
    };

    for (let volume of viewer.scene.volumes) {
      this.onAdd({ volume: volume });
    }

    this.viewer.inputHandler.addEventListener('delete', (e) => {
      let volumes = e.selection.filter((e) => e instanceof Volume);
      volumes.forEach((e) => this.viewer.scene.removeVolume(e));
    });

    viewer.addEventListener('update', this.update.bind(this));
    viewer.addEventListener('render.pass.scene', (e) => this.render(e));
    viewer.addEventListener('scene_changed', this.onSceneChange.bind(this));

    viewer.scene.addEventListener('volume_added', this.onAdd);
    viewer.scene.addEventListener('volume_removed', this.onRemove);
  }

  onSceneChange(e) {
    if (e.oldScene) {
      e.oldScene.removeEventListeners('volume_added', this.onAdd);
      e.oldScene.removeEventListeners('volume_removed', this.onRemove);
    }

    e.scene.addEventListener('volume_added', this.onAdd);
    e.scene.addEventListener('volume_removed', this.onRemove);
  }

  startInsertion(args = {}) {
    let volume;
    if (args.type) {
      volume = new args.type(args);
    } else {
      volume = new BoxVolume(args);
    }

    volume.clip = args.clip || false;
    volume.name = args.name || 'Volume';

    this.dispatchEvent({
      type: 'start_inserting_volume',
      volume: volume,
    });

    this.viewer.scene.addVolume(volume);
    this.scene.add(volume);

    let cancel = {
      callback: null,
    };

    let drag = (e) => {
      let camera = this.viewer.scene.getActiveCamera();

      let I = Utils.getMousePointCloudIntersection(e.drag.end, this.viewer.scene.getActiveCamera(), this.viewer, this.viewer.scene.pointclouds, { pickClipped: false });

      if (I) {
        volume.position.copy(I.location);

        let wp = volume.getWorldPosition(new THREE.Vector3()).applyMatrix4(camera.matrixWorldInverse);
        // let pp = new THREE.Vector4(wp.x, wp.y, wp.z).applyMatrix4(camera.projectionMatrix);
        let w = Math.abs(wp.z / 5);
        volume.modelDetails = { scale: { x: w, y: w, z: w } };
      }
    };

    let drop = (e) => {
      volume.removeEventListener('drag', drag);
      volume.removeEventListener('drop', drop);

      cancel.callback();
    };

    cancel.callback = (e) => {
      this.dispatchEvent({
        type: 'finish_inserting_volume',
        volume,
      });
      volume.removeEventListener('drag', drag);
      volume.removeEventListener('drop', drop);
      this.viewer.removeEventListener('cancel_insertions', cancel.callback);
    };

    volume.addEventListener('drag', drag);
    volume.addEventListener('drop', drop);
    this.viewer.addEventListener('cancel_insertions', cancel.callback);

    this.viewer.inputHandler.startDragging(volume);

    return volume;
  }

  update() {
    if (!this.viewer.scene) {
      return;
    }

    let camera = this.viewer.scene.getActiveCamera();
    let renderAreaSize = this.viewer.renderer.getSize(new THREE.Vector2());
    let clientWidth = renderAreaSize.width;
    let clientHeight = renderAreaSize.height;

    let volumes = this.viewer.scene.volumes;
    for (let volume of volumes) {
      let label = volume.label;

      {
        let distance = label.position.distanceTo(camera.position);
        let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);

        let scale = 70 / pr;
        label.scale.set(scale, scale, scale);
      }

      let calculatedVolume = volume.getVolume();
      calculatedVolume = (calculatedVolume / Math.pow(this.viewer.lengthUnit.unitspermeter, 3)) * Math.pow(this.viewer.lengthUnitDisplay.unitspermeter, 3); //convert to cubic meters then to the cubic display unit
      let text = Utils.addCommas(calculatedVolume.toFixed(3)) + ' ' + this.viewer.lengthUnitDisplay.code + '\u00B3';
      label.setText(text);
    }
  }

  render(params) {
    const renderer = this.viewer.renderer;

    const oldTarget = renderer.getRenderTarget();

    if (params.renderTarget) {
      renderer.setRenderTarget(params.renderTarget);
    }
    renderer.render(this.scene, this.viewer.scene.getActiveCamera());
    renderer.setRenderTarget(oldTarget);
  }
}
