import { Vector } from 'cal';
import * as THREE from 'three';
import heightHandleURL from '../../../img/3d/heightHandle.png';
import pivitHandleURL from '../../../img/3d/sculptHandle.png';
import * as d3Tools from '../../constants/d3Tools';
import { SHAPE_TYPE_PROPERTIES } from '../../constants/shapeTypeProperties';
import { createTextureFromURL, SpriteHandle, CanvasPlane } from '../../utils/threeUtils.js';
import BaseTransformer from './BaseTransformer.js';
import { getSelectedObjectsSelector, getBoundingBox } from '../../utils/selectionUtils';
import * as actions from '../../actions/index.js';
import { dimensionsText } from '../../d2/texts.js';
import * as humanReadable from '../../utils/humanReadable.js';

// import createDebug from 'debug';
// const debug = createDebug('d3d:transformer:height');

const HEIGHT_HANDLE_OFFSET = 15;
const HEIGHT_HANDLE_SCALE = 0.166;

const heightHandleTexture = createTextureFromURL(heightHandleURL);
const pivitHandleTexture = createTextureFromURL(pivitHandleURL);

const HEIGHT_LABEL_HEIGHT = 128;
const HEIGHT_LABEL_WIDTH = 32;
const HEIGHT_LABEL_FONT = dimensionsText.clone();
HEIGHT_LABEL_FONT.size = 30;

export default class HeightTransformer extends BaseTransformer {
  constructor(dispatch, scene, camera, domElement) {
    super(dispatch, scene, camera, domElement);

    this.name = 'height-transformer';
    this.enableHitDetection = true;

    this._active = false;
  }

  dragStart(event) {
    const handle = this.includesHandle(event.intersections);
    if (handle) {
      this.dispatch(actions.heightStart(handle));
    } else {
      super.dragStart(event);
    }
  }

  drag(event) {
    if (this._active) {
      const delta = event.position.subtract(event.previousPosition);
      this.dispatch(actions.height(delta));
    } else {
      super.drag(event);
    }
  }

  dragEnd(event) {
    if (this._active) {
      this.dispatch(actions.heightEnd());
    } else {
      super.dragEnd(event);
    }
  }

  update(state) {
    this.visible = state.selection.objects.length > 0;

    // TODO: create general selector for this in a reducer
    const objectsById = state.objectsById;
    const selectedShapeDatas = getSelectedObjectsSelector(state.selection.objects, objectsById);
    const boundingBox = getBoundingBox(selectedShapeDatas, state.selection.transform);

    let editableObjects = false;
    for (const shapeData of selectedShapeDatas) {
      if (SHAPE_TYPE_PROPERTIES[shapeData.type].tools[d3Tools.HEIGHT]) {
        editableObjects = true;
        break;
      }
    }

    this._active = state.d3.height.active;

    this.visible = editableObjects;

    if (!this.visible) return;
    if (!this._handleTop) {
      this.createHandle();
    }
    let { max, min, center } = boundingBox;

    center = center.applyMatrix(state.selection.transform);

    this._handleTop.position.set(center.x, max.y + HEIGHT_HANDLE_OFFSET, center.y);
    this._handleBottom.position.set(center.x, min.y - HEIGHT_HANDLE_OFFSET, center.y);
    this._handleTranslate.position.set(center.x, (min.y + max.y) / 2, center.y);

    if (this._active !== this._heightLabel.visible) {
      if (!this._heightLabel.visible) {
        const inverseWorld = new THREE.Matrix4().getInverse(this.matrixWorld);
        const normalMatrix = new THREE.Matrix3().getNormalMatrix(inverseWorld);

        const objectDirection = new THREE.Vector3(0, 1, 0).applyMatrix3(normalMatrix);

        const cameraDirection = new THREE.Vector3()
          .set(0, 0, -1)
          .applyEuler(state.d3.camera.object.rotation)
          .applyMatrix3(normalMatrix);

        const sculptDirection = new THREE.Vector3()
          .crossVectors(cameraDirection, objectDirection)
          .setY(0)
          .normalize();

        const maxSize = new Vector(max.x, max.z).applyMatrix(state.selection.transform);
        const sideDistance = maxSize.subtract(center).length();

        const base = new THREE.Vector2(sculptDirection.x, sculptDirection.z)
          .multiplyScalar(sideDistance)
          .add(center);

        const position = new THREE.Vector3(base.x, 0, base.y);
        this._heightLabel.position.copy(position);
        this._heightLabel.rotation.y = -new THREE.Vector2(sculptDirection.x, sculptDirection.z).angle();
      }

      this._heightLabel.visible = this._active;
    }

    if (this._active) {
      this._heightLabel.position.y = (min.y + max.y) / 2;

      this._heightLabel.draw((context, width, height) => {
        context.clearRect(0, 0, width, height);

        // context.fillStyle = 'rgba(0, 0, 0, 0.5)';
        // context.fillRect(0, 0, width, height);

        context.save();
        context.translate(5, Math.round(height / 2));
        context.rotate(Math.PI * 0.5);
        HEIGHT_LABEL_FONT.drawText(context, humanReadable.distance(max.y - min.y), 0, 0);
        context.restore();
      });

      const scale = this._heightLabel.position.distanceTo(this.camera.getWorldPosition()) / 1000.0;
      this._heightLabel.scale.set(scale, scale, scale);
    }

    this.updateSpriteScale();
  }
  createHandle() {
    this._handleTop = new SpriteHandle(heightHandleTexture, HEIGHT_HANDLE_SCALE);
    this._handleTop.name = 'height-transformer-handle-top';
    this.add(this._handleTop);

    this._handleBottom = new SpriteHandle(heightHandleTexture, HEIGHT_HANDLE_SCALE);
    this._handleBottom.name = 'height-transformer-handle-bottom';
    this.add(this._handleBottom);

    this._handleTranslate = new SpriteHandle(pivitHandleTexture, HEIGHT_HANDLE_SCALE);
    this._handleTranslate.name = 'height-transformer-handle-translate';
    this.add(this._handleTranslate);

    this._heightLabel = new CanvasPlane(HEIGHT_LABEL_WIDTH, HEIGHT_LABEL_HEIGHT);
    this._heightLabel.visible = false;
    this.add(this._heightLabel);
  }
  includesHandle(intersections) {
    if (!this._handleTop) return '';
    const objects = intersections.map(({ object }) => object);

    if (objects.indexOf(this._handleTop) !== -1) return 'top';
    if (objects.indexOf(this._handleBottom) !== -1) return 'bottom';
    if (objects.indexOf(this._handleTranslate) !== -1) return 'translate';
    return '';
  }
}
