import { clamp } from '@math.gl/core';
import Controller from './controller';
import ViewState from './view-state';
import { normalizeViewportProps } from '@math.gl/web-mercator';
import assert from '../utils/assert';
import LinearInterpolator from '../transitions/linear-interpolator';
const PITCH_MOUSE_THRESHOLD = 5;
const PITCH_ACCEL = 1.2;
export class MapState extends ViewState {
  constructor(options) {
    const {
      width,
      height,
      latitude,
      longitude,
      zoom,
      bearing = 0,
      pitch = 0,
      altitude = 1.5,
      position = [0, 0, 0],
      maxZoom = 20,
      minZoom = 0,
      maxPitch = 60,
      minPitch = 0,
      startPanLngLat,
      startZoomLngLat,
      startRotatePos,
      startBearing,
      startPitch,
      startZoom,
      normalize = true
    } = options;
    assert(Number.isFinite(longitude));
    assert(Number.isFinite(latitude));
    assert(Number.isFinite(zoom));
    super({
      width,
      height,
      latitude,
      longitude,
      zoom,
      bearing,
      pitch,
      altitude,
      maxZoom,
      minZoom,
      maxPitch,
      minPitch,
      normalize,
      position
    }, {
      startPanLngLat,
      startZoomLngLat,
      startRotatePos,
      startBearing,
      startPitch,
      startZoom
    });
    this.makeViewport = void 0;
    this.makeViewport = options.makeViewport;
  }

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

  pan({
    pos,
    startPos
  }) {
    const startPanLngLat = this.getState().startPanLngLat || this._unproject(startPos);

    if (!startPanLngLat) {
      return this;
    }

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

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

  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();

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

    let newRotation;

    if (pos) {
      newRotation = this._getNewRotation(pos, startRotatePos, startPitch, startBearing);
    } else {
      newRotation = {
        bearing: startBearing + deltaAngleX,
        pitch: startPitch + deltaAngleY
      };
    }

    return this._getUpdatedState(newRotation);
  }

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

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

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

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

    if (!startZoomLngLat) {
      return this;
    }

    const {
      maxZoom,
      minZoom
    } = this.getViewportProps();
    let zoom = startZoom + Math.log2(scale);
    zoom = clamp(zoom, minZoom, maxZoom);
    const zoomedViewport = this.makeViewport({ ...this.getViewportProps(),
      zoom
    });
    return this._getUpdatedState({
      zoom,
      ...zoomedViewport.panByPosition(startZoomLngLat, pos)
    });
  }

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

  zoomIn(speed = 2) {
    return this._zoomFromCenter(speed);
  }

  zoomOut(speed = 2) {
    return this._zoomFromCenter(1 / speed);
  }

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

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

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

  moveDown(speed = 100) {
    return this._panFromCenter([0, -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
    });
  }

  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 (Math.abs(longitude - fromProps.longitude) > 180) {
      props.longitude = longitude < 0 ? longitude + 360 : longitude - 360;
    }

    return props;
  }

  applyConstraints(props) {
    const {
      maxZoom,
      minZoom,
      zoom
    } = props;
    props.zoom = clamp(zoom, minZoom, maxZoom);
    const {
      maxPitch,
      minPitch,
      pitch
    } = props;
    props.pitch = clamp(pitch, minPitch, maxPitch);
    const {
      normalize = true
    } = props;

    if (normalize) {
      Object.assign(props, normalizeViewportProps(props));
    }

    return props;
  }

  _zoomFromCenter(scale) {
    const {
      width,
      height
    } = this.getViewportProps();
    return this.zoom({
      pos: [width / 2, height / 2],
      scale
    });
  }

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

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

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

  _getNewRotation(pos, startPos, startPitch, startBearing) {
    const deltaX = pos[0] - startPos[0];
    const deltaY = pos[1] - startPos[1];
    const centerY = pos[1];
    const startY = startPos[1];
    const {
      width,
      height
    } = this.getViewportProps();
    const deltaScaleX = deltaX / width;
    let deltaScaleY = 0;

    if (deltaY > 0) {
      if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) {
        deltaScaleY = deltaY / (startY - height) * PITCH_ACCEL;
      }
    } else if (deltaY < 0) {
      if (startY > PITCH_MOUSE_THRESHOLD) {
        deltaScaleY = 1 - centerY / startY;
      }
    }

    deltaScaleY = clamp(deltaScaleY, -1, 1);
    const {
      minPitch,
      maxPitch
    } = this.getViewportProps();
    const bearing = startBearing + 180 * deltaScaleX;
    let pitch = startPitch;

    if (deltaScaleY > 0) {
      pitch = startPitch + deltaScaleY * (maxPitch - startPitch);
    } else if (deltaScaleY < 0) {
      pitch = startPitch - deltaScaleY * (minPitch - startPitch);
    }

    return {
      pitch,
      bearing
    };
  }

}
export default class MapController extends Controller {
  constructor(...args) {
    super(...args);
    this.ControllerState = MapState;
    this.transition = {
      transitionDuration: 300,
      transitionInterpolator: new LinearInterpolator({
        transitionProps: {
          compare: ['longitude', 'latitude', 'zoom', 'bearing', 'pitch', 'position'],
          required: ['longitude', 'latitude', 'zoom']
        }
      })
    };
    this.dragMode = 'pan';
  }

  setProps(props) {
    props.position = props.position || [0, 0, 0];
    const oldProps = this.props;
    super.setProps(props);
    const dimensionChanged = !oldProps || oldProps.height !== props.height;

    if (dimensionChanged) {
      this.updateViewport(new this.ControllerState({
        makeViewport: this.makeViewport,
        ...props,
        ...this.state
      }));
    }
  }

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