import * as THREE from 'three';
import * as actions from '../../actions/index.js';
import { EventObject3D } from '../EventScene.js';
import transposeEvents from '../../utils/transposeEvents.js';
import ShapeMesh from '../ShapeMesh.js';
import { SpriteHandle } from '../../utils/threeUtils.js';

const RAY_CASTER = new THREE.Raycaster();
const MOUSE = new THREE.Vector2();

export default class BaseTransformer extends EventObject3D {
  constructor(dispatch, scene, camera, DOMNode, renderRequest) {
    super();

    // enable hit detection by default because it is needed for toggling selection
    this.enableHitDetection = true;

    this.dispatch = dispatch;
    this.scene = scene;
    this.camera = camera;
    this.DOMNode = DOMNode;
    this.renderRequest = renderRequest;
  }
  dragStart({ position, preDrags, intersections }) {
    this.dispatch(actions.d3DragStart(position, preDrags, intersections));
  }
  drag({ position, previousPosition }) {
    this.dispatch(actions.d3Drag(position, previousPosition));
  }
  dragEnd({ position }) {
    this.dispatch(actions.d3DragEnd(position));
  }
  secondDragStart({ position, preDrags, intersections }) {
    this.dispatch(actions.d3SecondDragStart(position, preDrags, intersections));
  }
  secondDrag({ position, previousPosition }) {
    this.dispatch(actions.d3SecondDrag(position, previousPosition));
  }
  secondDragEnd({ position }) {
    this.dispatch(actions.d3DragEnd(position));
  }
  tap({ position, intersections }) {
    // perhaps better to do this in a more general location
    // but currently this is the only place we have access to intersections
    this.select(intersections);

    this.dispatch(actions.d3Tap(position, intersections));
  }
  wheel({ position, wheelDelta }) {
    this.dispatch(actions.d3MouseWheel(position, wheelDelta));
  }
  multitouchStart({ positions, preDrags }) {
    this.dispatch(actions.d3MultitouchStart(positions, preDrags));
  }
  multitouch({ positions, previousPositions }) {
    this.dispatch(actions.d3Multitouch(positions, previousPositions));
  }
  multitouchEnd({ positions }) {
    this.dispatch(actions.d3MultitouchEnd(positions));
  }
  select(intersections) {
    const mesh = intersections.find(({ object }) => object instanceof ShapeMesh);
    const bed = intersections.find(({ object }) => object.name === 'bed-plane');

    if (mesh) {
      this.dispatch(actions.toggleSelect(mesh.object.name));
    } else {
      if (bed) {
        this.dispatch(actions.bedSelect());
      }
      this.dispatch(actions.deselectAll());
    }
  }
  onEvent(event) {
    switch (event.type) {
      case 'dragstart':
        this._previousPosition = event.position;
        break;

      case 'drag':
        event.previousPosition = this._previousPosition;
        this._previousPosition = event.position;
        break;

      case 'seconddragstart':
        this._previousSecondPosition = event.position;
        break;

      case 'seconddrag':
        event.previousPosition = this._previousSecondPosition;
        this._previousSecondPosition = event.position;
        break;

      case 'multitouchstart':
        this._previousMultitouchPositions = event.positions;
        break;

      case 'multitouch':
        event.previousPositions = this._previousMultitouchPositions;
        this._previousMultitouchPositions = event.positions;
        break;

      default:
        break;
    }

    event.intersections = [];
    if (this.enableHitDetection) {
      if (event.type === 'tap' || event.type === 'dragstart' || event.type === 'seconddragstart') {
        event.intersections = this.findHit(event.position);
      }
    }

    transposeEvents.call(this, event);
    super.onEvent(event);
  }
  getRayCaster({ x, y }) {
    MOUSE.setX((x / this.DOMNode.offsetWidth) * 2 - 1);
    MOUSE.setY(-(y / this.DOMNode.offsetHeight) * 2 + 1);

    RAY_CASTER.setFromCamera(MOUSE, this.camera);

    return RAY_CASTER;
  }
  findHit(mouse) {
    if (!this.enableHitDetection || this.scene.children.length === 0) return [];

    const rayCaster = this.getRayCaster(mouse);
    const intersections = rayCaster.intersectObjects(this.scene.children, true);

    return intersections;
  }
  updateSpriteScale() {
    for (const sprite of this.children) {
      if (!(sprite instanceof SpriteHandle) || !sprite.material.map) continue;

      const scale = sprite.position.distanceTo(this.camera.getWorldPosition()) / 2000.0;
      const { width, height } = sprite.material.map.image;
      sprite.scale.set(width * scale, height * scale, 0); // TODO only if changed
    }
  }
  destroy() {}
}
