import Controller from './controller';
import ViewState from './view-state';
import { mod } from '../utils/math-utils';
import LinearInterpolator from '../transitions/linear-interpolator';
import { Vector3, _SphericalCoordinates as SphericalCoordinates, clamp } from '@math.gl/core';
const MOVEMENT_SPEED = 20;

class FirstPersonState extends ViewState {
  constructor(options) {
    const {
      width,
      height,
      position = [0, 0, 0],
      bearing = 0,
      pitch = 0,
      longitude = null,
      latitude = null,
      maxPitch = 90,
      minPitch = -90,
      startRotatePos,
      startBearing,
      startPitch,
      startZoomPosition
    } = options;
    super({
      width,
      height,
      position,
      bearing,
      pitch,
      longitude,
      latitude,
      maxPitch,
      minPitch
    }, {
      startRotatePos,
      startBearing,
      startPitch,
      startZoomPosition
    });
  }

  panStart() {
    return this;
  }

  pan() {
    return this;
  }

  panEnd() {
    return this;
  }

  rotateStart({
    pos
  }) {
    return this._getUpdatedState({
      startRotatePos: pos,
      startBearing: this.getViewportProps().bearing,
      startPitch: this.getViewportProps().pitch
    });
  }

  rotate({
    pos,
    deltaAngleX = 0,
    deltaAngleY = 0
  }) {
    const {
      startRotatePos,
      startBearing,
      startPitch
    } = this.getState();
    const {
      width,
      height
    } = this.getViewportProps();

    if (!startRotatePos || startBearing === undefined || startPitch === undefined) {
      return this;
    }

    let newRotation;

    if (pos) {
      const deltaScaleX = (pos[0] - startRotatePos[0]) / width;
      const deltaScaleY = (pos[1] - startRotatePos[1]) / height;
      newRotation = {
        bearing: startBearing - deltaScaleX * 180,
        pitch: startPitch - deltaScaleY * 90
      };
    } else {
      newRotation = {
        bearing: startBearing - deltaAngleX,
        pitch: startPitch - deltaAngleY
      };
    }

    return this._getUpdatedState(newRotation);
  }

  rotateEnd() {
    return this._getUpdatedState({
      startRotatePos: null,
      startBearing: null,
      startPitch: null
    });
  }

  zoomStart() {
    return this._getUpdatedState({
      startZoomPosition: this.getViewportProps().position
    });
  }

  zoom({
    scale
  }) {
    let {
      startZoomPosition
    } = this.getState();

    if (!startZoomPosition) {
      startZoomPosition = this.getViewportProps().position;
    }

    const direction = this.getDirection();
    return this._move(direction, Math.log2(scale) * MOVEMENT_SPEED, startZoomPosition);
  }

  zoomEnd() {
    return this._getUpdatedState({
      startZoomPosition: null
    });
  }

  moveLeft(speed = MOVEMENT_SPEED) {
    const direction = this.getDirection(true);
    return this._move(direction.rotateZ({
      radians: Math.PI / 2
    }), speed);
  }

  moveRight(speed = MOVEMENT_SPEED) {
    const direction = this.getDirection(true);
    return this._move(direction.rotateZ({
      radians: -Math.PI / 2
    }), speed);
  }

  moveUp(speed = MOVEMENT_SPEED) {
    const direction = this.getDirection(true);
    return this._move(direction, speed);
  }

  moveDown(speed = MOVEMENT_SPEED) {
    const direction = this.getDirection(true);
    return this._move(direction.negate(), speed);
  }

  rotateLeft(speed = 15) {
    return this._getUpdatedState({
      bearing: this.getViewportProps().bearing - speed
    });
  }

  rotateRight(speed = 15) {
    return this._getUpdatedState({
      bearing: this.getViewportProps().bearing + speed
    });
  }

  rotateUp(speed = 10) {
    return this._getUpdatedState({
      pitch: this.getViewportProps().pitch + speed
    });
  }

  rotateDown(speed = 10) {
    return this._getUpdatedState({
      pitch: this.getViewportProps().pitch - speed
    });
  }

  zoomIn(speed = 2) {
    return this.zoom({
      scale: speed
    });
  }

  zoomOut(speed = 2) {
    return this.zoom({
      scale: 1 / speed
    });
  }

  shortestPathFrom(viewState) {
    const fromProps = viewState.getViewportProps();
    const props = { ...this.getViewportProps()
    };
    const {
      bearing,
      longitude
    } = props;

    if (Math.abs(bearing - fromProps.bearing) > 180) {
      props.bearing = bearing < 0 ? bearing + 360 : bearing - 360;
    }

    if (longitude !== null && fromProps.longitude !== null && Math.abs(longitude - fromProps.longitude) > 180) {
      props.longitude = longitude < 0 ? longitude + 360 : longitude - 360;
    }

    return props;
  }

  _move(direction, speed, fromPosition = this.getViewportProps().position) {
    const delta = direction.scale(speed);
    return this._getUpdatedState({
      position: new Vector3(fromPosition).add(delta)
    });
  }

  getDirection(use2D = false) {
    const spherical = new SphericalCoordinates({
      bearing: this.getViewportProps().bearing,
      pitch: use2D ? 90 : 90 + this.getViewportProps().pitch
    });
    const direction = spherical.toVector3().normalize();
    return direction;
  }

  _getUpdatedState(newProps) {
    return new FirstPersonState({ ...this.getViewportProps(),
      ...this.getState(),
      ...newProps
    });
  }

  applyConstraints(props) {
    const {
      pitch,
      maxPitch,
      minPitch,
      longitude,
      bearing
    } = props;
    props.pitch = clamp(pitch, minPitch, maxPitch);

    if (longitude !== null && (longitude < -180 || longitude > 180)) {
      props.longitude = mod(longitude + 180, 360) - 180;
    }

    if (bearing < -180 || bearing > 180) {
      props.bearing = mod(bearing + 180, 360) - 180;
    }

    return props;
  }

}

export default class FirstPersonController extends Controller {
  constructor(...args) {
    super(...args);
    this.ControllerState = FirstPersonState;
    this.transition = {
      transitionDuration: 300,
      transitionInterpolator: new LinearInterpolator(['position', 'pitch', 'bearing'])
    };
  }

}
//# sourceMappingURL=first-person-controller.js.map