import * as THREE from 'three';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import { Message } from 'element-ui';

var _this = null;
var MeasureHeight = function (renderer, scene, camera, width, height, options = {}) {
  this.renderer = renderer;
  this.scene = scene;
  this.camera = camera;
  this.pickRoots = options.pickRoots || null;
  this.pointArray = [];
  this.raycaster = new THREE.Raycaster();
  this.points = [];
  this.polyline = [];
  this.labels = [];
  this.tempPoints = undefined;
  this.tempLine = undefined;
  this.tempLabel = undefined;
  this.tipsLabel = undefined;
  this.isCompleted = false;
  this.timer = null;
  this.width = width;
  this.height = height;
  this.firstTime = 0;
  this.plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
  this.measureName = 'measureObj';
};

MeasureHeight.prototype = {
  start() {
    _this = this;
    this.renderer.domElement.style.cursor = 'crosshair';
    this.renderer.domElement.addEventListener('mousedown', this.mousedown, false);
    this.renderer.domElement.addEventListener('mouseup', this.click, false);
    this.renderer.domElement.addEventListener('contextmenu', this.rightClick, false);
  },
  updateParams(width, height) {
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(width, height, true);
    this.width = width;
    this.height = height;
  },
  getPickRoots() {
    const roots = typeof _this.pickRoots === 'function' ? _this.pickRoots() : _this.pickRoots;
    if (Array.isArray(roots) && roots.length > 0) {
      return roots.filter(Boolean);
    }
    if (roots) {
      return [roots];
    }
    return _this.scene && _this.scene.children ? _this.scene.children : [];
  },
  isExcludedIntersection(object) {
    let current = object;
    while (current) {
      const userData = current.userData || {};
      if (current.visible === false) return true;
      if (current.name === _this.measureName || userData.isMeasureObject === true) return true;
      if (userData.transformControlHelper === true || userData.outlineProxy === true) return true;
      if (current.isCamera || current.isLight) return true;
      if (
        typeof current.type === 'string' &&
        (/Helper$/i.test(current.type) ||
          current.type === 'CSS2DObject' ||
          current.type === 'TransformControlsRoot')
      ) {
        return true;
      }
      current = current.parent;
    }
    return false;
  },
  markMeasureObject(object) {
    object.name = this.measureName;
    if (!object.userData) {
      object.userData = {};
    }
    object.userData.isMeasureObject = true;
    return object;
  },
  getPosition(e, options = {}) {
    const mouse = new THREE.Vector2();
    const elRect = this.renderer.domElement.getBoundingClientRect();
    const canvasX = e.clientX - elRect.left;
    const canvasY = e.clientY - elRect.top;

    mouse.x = (canvasX / elRect.width) * 2.0 - 1.0;
    mouse.y = -(canvasY / elRect.height) * 2.0 + 1.0;

    _this.raycaster.setFromCamera(mouse, this.camera);
    const intersects = _this.raycaster
      .intersectObjects(_this.getPickRoots(), true)
      .filter(item => item && item.object && !_this.isExcludedIntersection(item.object));
    if (intersects.length > 0) {
      return { point: intersects[0].point, isModel: true };
    }

    if (options.allowPlaneFallback === false) {
      return null;
    }

    if (_this.pointArray && _this.pointArray.length > 0) {
      const lastPoint =
        _this.pointArray.length === 1
          ? _this.pointArray[0]
          : _this.pointArray[_this.pointArray.length - 2];
      const cameraDir = new THREE.Vector3();
      _this.camera.getWorldDirection(cameraDir);
      const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(cameraDir, lastPoint);
      const target = new THREE.Vector3();
      _this.raycaster.ray.intersectPlane(plane, target);
      if (target) {
        return { point: target, isModel: false };
      }
    }

    const target = new THREE.Vector3();
    _this.raycaster.ray.intersectPlane(_this.plane, target);
    if (target) {
      return { point: target, isModel: false };
    }
    return null;
  },
  createLine(p1, p2, config = { color: 0xff0000 }) {
    const lineMaterial = new THREE.LineBasicMaterial({
      color: config.color,
      linewidth: 15,
      depthTest: false,
      depthWrite: false,
      transparent: true,
    });
    const lineGeometry = new THREE.BufferGeometry().setFromPoints([p1, p2]);
    const line = new THREE.Line(lineGeometry, lineMaterial);
    this.markMeasureObject(line);
    line.renderOrder = 999;
    line.frustumCulled = false;
    return line;
  },
  createLabel(name, text, position) {
    const div = document.createElement('div');
    div.className = name;
    div.textContent = text;
    const divLabel = new CSS2DObject(div);
    this.markMeasureObject(divLabel);
    divLabel.position.set(position.x, position.y, position.z);
    return divLabel;
  },
  getXozPlanePoint(point) {
    return new THREE.Vector3(point.x, 0, point.z);
  },
  createHeightMeasure(point) {
    const planePoint = _this.getXozPlanePoint(point);
    const height = Math.abs(point.y - planePoint.y);
    const labelPosition = new THREE.Vector3(point.x, (point.y + planePoint.y) / 2, point.z);
    const circleTag = _this.createLabel('circle-tag', '', point);
    const line = _this.createLine(point.clone(), planePoint.clone());
    const label = _this.createLabel(
      'measure-label',
      `${_this.numberToString(height)}`,
      labelPosition
    );

    _this.points.push(circleTag);
    _this.polyline.push(line);
    _this.labels.push(label);
    _this.scene.add(circleTag);
    _this.scene.add(line);
    _this.scene.add(label);
  },
  createTipsLabel(label, position) {
    const div = document.createElement('div');
    div.className = 'tips-label';
    div.textContent = label;
    const tipsLabel = new CSS2DObject(div);
    this.markMeasureObject(tipsLabel);
    tipsLabel.position.set(position.x + 0.1, position.y, position.z + 0.05);
    return tipsLabel;
  },
  mousedown() {
    this.firstTime = new Date().getTime();
  },
  click(e) {
    const lastTime = new Date().getTime();
    if (lastTime - this.firstTime < 300) {
      clearTimeout(_this.timer);
      _this.timer = setTimeout(() => {
        _this.isCompleted = false;
        const positionResult = _this.getPosition(e, { allowPlaneFallback: false });
        if (!positionResult || !positionResult.isModel) {
          Message.warning('请点击模型进行测量');
          return;
        }
        if (positionResult) {
          const { point } = positionResult;
          // if (_this.tipsLabel) {
          //   _this.tipsLabel.position.set(point.x + 0.1, point.y, point.z + 0.05);
          // } else {
          //   _this.tipsLabel = _this.createTipsLabel('点击测量', point);
          //   _this.scene.add(_this.tipsLabel);
          // }
          _this.createHeightMeasure(point);
        }
      }, 0);
    }
  },
  rightClick(e) {
    _this.preventContextMenu(e);
    // if (_this.tipsLabel) {
    //   _this.scene.remove(_this.tipsLabel);
    //   _this.tipsLabel = undefined;
    // }
    clearTimeout(_this.timer);
    const positionResult = _this.getPosition(e);
    if (positionResult) {
      _this.isCompleted = true;
    }
  },
  close(isClear) {
    this.renderer.domElement.removeEventListener('mousedown', this.mousedown);
    this.renderer.domElement.removeEventListener('mouseup', this.click);
    this.renderer.domElement.removeEventListener('contextmenu', this.rightClick);
    if (!isClear) {
      this.remove(this.points);
      this.remove(this.polyline);
      this.remove(this.labels);
      this.pointArray.splice(0);
      this.points.splice(0);
      this.polyline.splice(0);
      this.labels.splice(0);
      this.scene.remove(this.tipsLabel);
      this.tipsLabel = undefined;
      this.renderer.domElement.style.cursor = 'pointer';
      this.firstTime = 0;
    }
  },
  clear() {
    this.remove(this.points);
    this.remove(this.polyline);
    this.remove(this.labels);
    this.pointArray.splice(0);
    this.points.splice(0);
    this.polyline.splice(0);
    this.labels.splice(0);
    this.scene.remove(this.tipsLabel);
    this.tipsLabel = undefined;
  },
  remove(array) {
    for (let index = 0; index < array.length; index++) {
      const element = array[index];
      if (element.geometry) {
        element.geometry.dispose();
      }
      this.scene.remove(element);
    }
  },
  numberToString(num) {
    if (num < 0.0001) {
      return num.toString();
    }
    let fractionDigits = 2;
    if (num < 0.01) {
      fractionDigits = 4;
    } else if (num < 0.1) {
      fractionDigits = 3;
    }
    return num.toFixed(fractionDigits);
  },
  preventContextMenu(event) {
    event.preventDefault();
  },
};
export default {
  MeasureHeight,
};
