import { Vector2, Vector3 } from "three";

const STATE = {
  NONE: -1,
  ROTATE: 0,
  PAN: 1,
  ZOOM: 2,
  ZOOM_PAN: 3,
  ZOOM_ROTATE: 4,
};
const HANDLE = {
  ROTATE: 0,
  PAN: 1,
  ZOOM: 2,
  ZOOM_PAN: 3,
  ZOOM_ROTATE: 4,
};

const pointers = [];
const pointerPositions = {};

export default class FloorplanControls {
  constructor(camera, dom, player) {
    this.camera = camera;
    this.domElement = dom;
    this.domElement.style.touchAction = "none"; // disable touch scroll
    this.player = player;

    this.panSpeed = 1;
    this.zoomSpeed = 1;
    this.rotateSpeed = 1;

    this.maxDistance = 100;
    this.minDistance = 0.1;
    this.maxZoom = 500;
    this.minZoom = 5;

    this.target = new Vector3();

    this.state = STATE.NONE;

    this.rotateStart = new Vector2();
    this.rotateEnd = new Vector2();

    this.panStart = new Vector2();
    this.panEnd = new Vector2();

    this.zoomStart = new Vector2();

    this.locked = false; //禁止用户操作
    this.enabled = true; //禁止update
    this.enablePan = true;
    this.enableRotate = true;
    this.enableZoom = true;

    this.touchesEvent = {
      ONE: HANDLE.PAN,
      TWO: HANDLE.ZOOM,
    };
    this.mouseEvent = {
      LEFT: HANDLE.PAN,
      RIGHT: HANDLE.ROTATE,
      WHEEL: HANDLE.ZOOM,
    };

    this.onBindEvent();
  }

  onBindEvent = () => {
    this.domElement.addEventListener(
      "pointerdown",
      this.onPointerDown.bind(this)
    );
    this.domElement.addEventListener("pointerup", this.onPointerUp.bind(this));
    this.domElement.addEventListener(
      "pointermove",
      this.onPointerMove.bind(this)
    );
    this.domElement.addEventListener(
      "pointercancel",
      this.onPointerUp.bind(this)
    );

    this.domElement.addEventListener(
      "mousewheel",
      this.onMouseWheel.bind(this),
      { passive: false }
    );

    this.domElement.addEventListener("contextmenu", this.onPreventDefault);
  };

  addPointer = (event) => {
    pointers.push(event);
  };
  removePointer = (event) => {
    for (let i = 0; i < pointers.length; i++) {
      if (pointers[i].pointerId == event.pointerId) {
        pointers.splice(i, 1);
        return;
      }
    }
  };
  isTrackingPointer = (event) => {
    for (let i = 0; i < pointers.length; i++) {
      if (pointers[i] == event.pointerId) return true;
    }
    return false;
  };
  trackPointer = (event) => {
    let position = pointerPositions[event.pointerId];
    if (position === undefined) {
      position = new Vector2();
      pointerPositions[event.pointerId] = position;
    }
    position.set(event.pageX, event.pageY);
  };
  getSecondPointerPosition = (event) => {
    const pointerId =
      event.pointerId === pointers[0].pointerId
        ? pointers[1].pointerId
        : pointers[0].pointerId;
    return pointerPositions[pointerId];
  };

  // pointer event
  onPointerDown = (event) => {
    if (this.locked) return;
    if (pointers.length === 0) {
      this.domElement.setPointerCapture(event.pointerId);
    }
    if (this.isTrackingPointer(event)) return;
    this.addPointer(event);
    if (event.pointerType === "touch") {
      this.onTouchStart(event);
    } else {
      this.onMouseDown(event);
    }
  };
  onPointerUp = (event) => {
    if (this.locked) return;
    this.removePointer(event);
    if (pointers.length === 0) {
      this.domElement.releasePointerCapture(event.pointerId);
      this.state = STATE.NONE;
    } else if (pointers.length === 1) {
      const pointerId = pointers[0].pointerId;
      const position = pointerPositions[pointerId];
      this.onTouchStart({
        pointerId: pointerId,
        pageX: position.x,
        pageY: position.y,
      });
    }
  };
  onPointerMove = (event) => {
    if (this.locked) return;
    if (event.pointerType === "touch") {
      this.onTouchMove(event);
    } else {
      this.onMouseMove(event);
    }
  };

  //touch event
  onTouchStart = (event) => {
    this.trackPointer(event);
    switch (pointers.length) {
      case 1:
        switch (this.touchesEvent.ONE) {
          case HANDLE.ROTATE: //rotate
            if (this.enableRotate === false) return;
            this.handleTouchStartRotate();
            this.state = STATE.ROTATE;
            break;
          case HANDLE.PAN: //pan
            if (this.enablePan === false) return;
            this.handleTouchStartPan();
            this.state = STATE.PAN;
            break;
          default:
            state = STATE.NONE;
        }
        break;
      case 2:
        switch (this.touchesEvent.TWO) {
          case HANDLE.ZOOM: //zoom
            if (this.enableZoom === false) return;
            this.handleTouchStartZoom();
            this.state = STATE.ZOOM;
            break;
          case HANDLE.ZOOM_PAN: //zoom_pan
            if (this.enableZoom === false && this.enablePan === false) return;
            this.handleTouchStartZoom();
            this.handleTouchStartPan();
            this.state = STATE.ZOOM_PAN;
            break;
          //todo case HANDLE.ZOOM_ROTATE:
          default:
            state = STATE.NONE;
        }
        break;
      default:
        this.state = STATE.NONE;
    }
  };
  onTouchMove = (event) => {
    this.trackPointer(event);
    switch (this.state) {
      case STATE.ROTATE:
        if (this.enableRotate === false) return;
        this.handleTouchMoveRotate(event);
        break;
      case STATE.PAN:
        if (this.enablePan === false) return;
        this.handleTouchMovePan(event);
        break;
      case STATE.ZOOM:
        if (this.enableZoom === false) return;
        this.handleTouchMoveZoom(event);
        break;
      case STATE.ZOOM_PAN:
        if (this.enableZoom) this.handleTouchMoveZoom(event);
        if (this.enablePan) this.handleTouchMovePan(event);
        break;
      //todo case STATE.ZOOM_ROTATE:
      default:
        this.state = STATE.NONE;
    }
  };

  //mouse event
  onMouseDown = (event) => {
    if (this.locked) return;
    switch (event.button) {
      case 0: //left
        switch (this.mouseEvent.LEFT) {
          case HANDLE.PAN:
            if (this.enablePan === false) return;
            this.handleMouseDownPan(event);
            this.state = STATE.PAN;
            break;
          case HANDLE.ROTATE:
            if (this.enablePan === false) return;
            this.handleMouseDownRotate(event);
            this.state = STATE.ROTATE;
            break;
          default:
            this.state = STATE.NONE;
        }
        break;
      case 2: //right
        switch (this.mouseEvent.RIGHT) {
          case HANDLE.PAN:
            if (this.enablePan === false) return;
            this.handleMouseDownPan(event);
            this.state = STATE.PAN;
            break;
          case HANDLE.ROTATE:
            if (this.enablePan === false) return;
            this.handleMouseDownRotate(event);
            this.state = STATE.ROTATE;
            break;
          default:
            this.state = STATE.NONE;
        }
        break;
      default:
        this.state = STATE.NONE;
    }
  };
  onMouseMove = (event) => {
    if (this.locked) return;
    switch (this.state) {
      case STATE.PAN:
        if (this.enablePan === false) return;
        this.handleMouseMovePan(event);
        break;
      case STATE.ROTATE:
        if (this.enableRotate === false) return;
        this.handleMouseMoveRotate(event);
        break;
      default:
        this.state = STATE.NONE;
    }
  };
  onMouseWheel = (event) => {
    // console.log("this", this);
    if (this.locked) return;
    if (this.enableZoom === false) return;
    event.preventDefault();
    this.handleMouseWheelZoom(event);
  };
  onPreventDefault = (event) => {
    event.preventDefault();
  };

  //================================handle================================
  //-------------------------rotate-------------------------
  handleTouchStartRotate = () => {
    const position = pointerPositions[pointers[0].pointerId];
    this.rotateStart.set(position.x, position.y);
  };
  handleTouchMoveRotate = (event) => {
    this.rotateEnd.set(event.pageX, event.pageY);
    let rotateDelta = this.rotateEnd
      .clone()
      .sub(this.rotateStart)
      .multiplyScalar(this.rotateSpeed);
    let element = this.domElement;
    let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight;
    let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight;

    this.rotate(rotateX, rotateY);

    this.rotateStart.copy(this.rotateEnd);
  };
  handleMouseDownRotate = (event) => {
    this.rotateStart.set(event.pageX, event.pageY);
  };
  handleMouseMoveRotate = (event) => {
    this.rotateEnd.set(event.pageX, event.pageY);
    let rotateDelta = this.rotateEnd
      .clone()
      .sub(this.rotateStart)
      .multiplyScalar(this.rotateSpeed);
    let element = this.domElement;
    let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight;
    let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight;

    this.rotate(rotateX, rotateY);

    this.rotateStart.copy(this.rotateEnd);
  };
  //-------------------------zoom-------------------------
  handleTouchStartZoom = () => {
    const dx = pointers[0].pageX - pointers[1].pageX;
    const dy = pointers[0].pageY - pointers[1].pageY;
    const distance = Math.sqrt(dx * dx + dy * dy);
    this.zoomStart.set(0, distance);
  };
  handleTouchMoveZoom = (event) => {
    const position = this.getSecondPointerPosition(event);
    const dx = event.pageX - position.x;
    const dy = event.pageY - position.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    let delta = Math.pow(distance / this.zoomStart.y, this.zoomSpeed);

    this.zoom(1 / delta);

    this.zoomStart.set(0, distance);
  };
  handleMouseWheelZoom = (event) => {
    if (event.deltaY > 0) {
      //zoom out
      this.zoom(1.05 * this.zoomSpeed);
    } else {
      //zoom in
      this.zoom(0.95 * this.zoomSpeed);
    }
  };
  //-------------------------pan-------------------------
  handleTouchStartPan = () => {
    if (pointers.length === 1) {
      const position = pointerPositions[pointers[0].pointerId];
      this.panStart.set(position.x, position.y);
    } else {
      const x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
      const y = 0.5 * (pointers[0].pageY + pointers[1].pageY);
      this.panStart.set(x, y);
    }
  };
  handleTouchMovePan = (event) => {
    if (pointers.length === 1) {
      this.panEnd.set(event.pageX, event.pageY);
    } else {
      const position = this.getSecondPointerPosition(event);
      const x = 0.5 * (event.pageX + position.x);
      const y = 0.5 * (event.pageY + position.y);
      this.panEnd.set(x, y);
    }
    let panDelta = this.panEnd.clone().sub(this.panStart);

    this.pan(panDelta);

    this.panStart.copy(this.panEnd);
  };
  handleMouseDownPan = (event) => {
    this.panStart.set(event.pageX, event.pageY);
  };
  handleMouseMovePan = (event) => {
    this.panEnd.set(event.pageX, event.pageY);
    let panDelta = this.panEnd.clone().sub(this.panStart);

    this.pan(panDelta);

    this.panStart.copy(this.panEnd);
  };

  rotate(x, y) {
    let r = y;
    if (Math.abs(x) > Math.abs(y)) r = x;

    let cameraRZ = this.camera.rotation.z;
    cameraRZ += r;
    if (Math.abs(cameraRZ) >= Math.PI * 2) {
      cameraRZ -= Math.sign(cameraRZ) * Math.PI * 2;
    }
    this.camera.rotation.z = cameraRZ;
    this.cameraUpdate();
  }
  zoom(delta) {
    // if(this.camera.isPerspectiveCamera) {
    //   let cameraY = this.camera.position.y
    //   cameraY *= delta
    //   cameraY = Math.max(cameraY, this.minDistance)
    //   cameraY = Math.min(cameraY, this.maxDistance)
    //   this.camera.position.y = cameraY //handle
    // } else if(this.camera.isOrthographicCamera) {
    //   let zoom = this.camera.zoom
    //   zoom *= 1/delta
    //   console.log(zoom)
    //   this.camera.zoom = zoom
    //   this.camera.updateProjectionMatrix()
    // }

    let cameraY = this.camera.position.y;
    cameraY *= delta;
    cameraY = Math.max(cameraY, this.minDistance);
    cameraY = Math.min(cameraY, this.maxDistance);
    this.camera.position.y = cameraY; //handle
    if (this.camera.isOrthographicCamera) {
      let zoom = this.camera.zoom;
      zoom *= 1 / delta;
      zoom = Math.max(zoom, this.minZoom);
      zoom = Math.min(zoom, this.maxZoom);
      this.camera.zoom = zoom;
      this.camera.updateProjectionMatrix();
    }

    this.cameraUpdate();
  }
  pan(delta) {
    const element = this.domElement;
    const matrix = this.camera.matrix.clone();
    const left = new Vector3();
    const up = new Vector3();
    let panDelta = delta.multiplyScalar(this.panSpeed);

    if (this.camera.isPerspectiveCamera) {
      let scalar =
        (2 *
          this.camera.position.y *
          Math.tan(((this.camera.fov / 2) * Math.PI) / 180.0)) /
        element.clientHeight;
      panDelta.multiplyScalar(scalar);

      left.setFromMatrixColumn(matrix, 0);
      left.multiplyScalar(-panDelta.x);
      up.setFromMatrixColumn(matrix, 1);
      up.multiplyScalar(panDelta.y);
    } else if (this.camera.isOrthographicCamera) {
      (panDelta.x =
        (panDelta.x * (this.camera.right - this.camera.left)) /
        this.camera.zoom /
        element.clientWidth),
        this.camera.matrix;
      (panDelta.y =
        (panDelta.y * (this.camera.top - this.camera.bottom)) /
        this.camera.zoom /
        element.clientHeight),
        this.camera.matrix;
      left.setFromMatrixColumn(matrix, 0);
      left.multiplyScalar(-panDelta.x);
      up.setFromMatrixColumn(matrix, 1);
      up.multiplyScalar(panDelta.y);
    } else {
      return;
    }
    this.camera.position.add(left).add(up);
    this.target.set(this.camera.position.x, 0, this.camera.position.z);
    this.cameraUpdate();
  }

  lookAt(target, height) {
    if (!target) return;
    height = height !== undefined ? height : this.camera.position.y;
    this.camera.position.set(target.x, height, target.z);
    this.target.set(target.x, 0, target.z);
    this.camera.lookAt(this.target);
  }

  cameraUpdate = () => {
    this.camera.updateMatrix();
    this.camera.updateProjectionMatrix();
  };

  update = () => {
    if (!this.enabled) return;
  };
}
