import { clamp } from '@math.gl/core';
import Controller from './controller';
import ViewState from './view-state';
import { mod } from '../utils/math-utils';
import LinearInterpolator from '../transitions/linear-interpolator';
export class OrbitState extends ViewState {
  constructor(options) {
    const {
      width,
      height,
      rotationX = 0,
      rotationOrbit = 0,
      target = [0, 0, 0],
      zoom = 0,
      minRotationX = -90,
      maxRotationX = 90,
      minZoom = -Infinity,
      maxZoom = Infinity,
      startPanPosition,
      startRotatePos,
      startRotationX,
      startRotationOrbit,
      startZoomPosition,
      startZoom
    } = options;
    super({
      width,
      height,
      rotationX,
      rotationOrbit,
      target,
      zoom,
      minRotationX,
      maxRotationX,
      minZoom,
      maxZoom
    }, {
      startPanPosition,
      startRotatePos,
      startRotationX,
      startRotationOrbit,
      startZoomPosition,
      startZoom
    });
    this.makeViewport = void 0;
    this.makeViewport = options.makeViewport;
  }

  panStart({
    pos
  }) {
    return this._getUpdatedState({
      startPanPosition: this._unproject(pos)
    });
  }

  pan({
    pos,
    startPosition
  }) {
    const startPanPosition = this.getState().startPanPosition || startPosition;

    if (!startPanPosition) {
      return this;
    }

    const viewport = this.makeViewport(this.getViewportProps());
    const newProps = viewport.panByPosition(startPanPosition, pos);
    return this._getUpdatedState(newProps);
  }

  panEnd() {
    return this._getUpdatedState({
      startPanPosition: null
    });
  }

  rotateStart({
    pos
  }) {
    return this._getUpdatedState({
      startRotatePos: pos,
      startRotationX: this.getViewportProps().rotationX,
      startRotationOrbit: this.getViewportProps().rotationOrbit
    });
  }

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

    if (!startRotatePos || startRotationX === undefined || startRotationOrbit === undefined) {
      return this;
    }

    let newRotation;

    if (pos) {
      let deltaScaleX = (pos[0] - startRotatePos[0]) / width;
      const deltaScaleY = (pos[1] - startRotatePos[1]) / height;

      if (startRotationX < -90 || startRotationX > 90) {
        deltaScaleX *= -1;
      }

      newRotation = {
        rotationX: startRotationX + deltaScaleY * 180,
        rotationOrbit: startRotationOrbit + deltaScaleX * 180
      };
    } else {
      newRotation = {
        rotationX: startRotationX + deltaAngleY,
        rotationOrbit: startRotationOrbit + deltaAngleX
      };
    }

    return this._getUpdatedState(newRotation);
  }

  rotateEnd() {
    return this._getUpdatedState({
      startRotationX: null,
      startRotationOrbit: null
    });
  }

  shortestPathFrom(viewState) {
    const fromProps = viewState.getViewportProps();
    const props = { ...this.getViewportProps()
    };
    const {
      rotationOrbit
    } = props;

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

    return props;
  }

  zoomStart({
    pos
  }) {
    return this._getUpdatedState({
      startZoomPosition: this._unproject(pos),
      startZoom: this.getViewportProps().zoom
    });
  }

  zoom({
    pos,
    startPos,
    scale
  }) {
    let {
      startZoom,
      startZoomPosition
    } = this.getState();

    if (!startZoomPosition) {
      startZoom = this.getViewportProps().zoom;
      startZoomPosition = this._unproject(startPos) || this._unproject(pos);
    }

    if (!startZoomPosition) {
      return this;
    }

    const newZoom = this._calculateNewZoom({
      scale,
      startZoom
    });

    const zoomedViewport = this.makeViewport({ ...this.getViewportProps(),
      zoom: newZoom
    });
    return this._getUpdatedState({
      zoom: newZoom,
      ...zoomedViewport.panByPosition(startZoomPosition, pos)
    });
  }

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

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

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

  moveLeft(speed = 50) {
    return this._panFromCenter([-speed, 0]);
  }

  moveRight(speed = 50) {
    return this._panFromCenter([speed, 0]);
  }

  moveUp(speed = 50) {
    return this._panFromCenter([0, -speed]);
  }

  moveDown(speed = 50) {
    return this._panFromCenter([0, speed]);
  }

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

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

  rotateUp(speed = 10) {
    return this._getUpdatedState({
      rotationX: this.getViewportProps().rotationX - speed
    });
  }

  rotateDown(speed = 10) {
    return this._getUpdatedState({
      rotationX: this.getViewportProps().rotationX + speed
    });
  }

  _unproject(pos) {
    const viewport = this.makeViewport(this.getViewportProps());
    return pos && viewport.unproject(pos);
  }

  _calculateNewZoom({
    scale,
    startZoom
  }) {
    const {
      maxZoom,
      minZoom
    } = this.getViewportProps();

    if (startZoom === undefined) {
      startZoom = this.getViewportProps().zoom;
    }

    const zoom = startZoom + Math.log2(scale);
    return clamp(zoom, minZoom, maxZoom);
  }

  _panFromCenter(offset) {
    const {
      width,
      height,
      target
    } = this.getViewportProps();
    return this.pan({
      startPosition: target,
      pos: [width / 2 + offset[0], height / 2 + offset[1]]
    });
  }

  _getUpdatedState(newProps) {
    return new this.constructor({
      makeViewport: this.makeViewport,
      ...this.getViewportProps(),
      ...this.getState(),
      ...newProps
    });
  }

  applyConstraints(props) {
    const {
      maxZoom,
      minZoom,
      zoom,
      maxRotationX,
      minRotationX,
      rotationOrbit
    } = props;
    props.zoom = Array.isArray(zoom) ? [clamp(zoom[0], minZoom, maxZoom), clamp(zoom[1], minZoom, maxZoom)] : clamp(zoom, minZoom, maxZoom);
    props.rotationX = clamp(props.rotationX, minRotationX, maxRotationX);

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

    return props;
  }

}
export default class OrbitController extends Controller {
  constructor(...args) {
    super(...args);
    this.ControllerState = OrbitState;
    this.transition = {
      transitionDuration: 300,
      transitionInterpolator: new LinearInterpolator({
        transitionProps: {
          compare: ['target', 'zoom', 'rotationX', 'rotationOrbit'],
          required: ['target', 'zoom']
        }
      })
    };
  }

}
//# sourceMappingURL=orbit-controller.js.map