import TransitionManager from './transition-manager';
import LinearInterpolator from '../transitions/linear-interpolator';
const NO_TRANSITION_PROPS = {
  transitionDuration: 0
};
const DEFAULT_INERTIA = 300;

const INERTIA_EASING = t => 1 - (1 - t) * (1 - t);

const EVENT_TYPES = {
  WHEEL: ['wheel'],
  PAN: ['panstart', 'panmove', 'panend'],
  PINCH: ['pinchstart', 'pinchmove', 'pinchend'],
  TRIPLE_PAN: ['tripanstart', 'tripanmove', 'tripanend'],
  DOUBLE_TAP: ['doubletap'],
  KEYBOARD: ['keydown']
};
const pinchEventWorkaround = {};
export default class Controller {
  constructor(opts) {
    this.props = void 0;
    this.state = {};
    this.transitionManager = void 0;
    this.eventManager = void 0;
    this.onViewStateChange = void 0;
    this.onStateChange = void 0;
    this.makeViewport = void 0;
    this._controllerState = void 0;
    this._events = {};
    this._interactionState = {
      isDragging: false
    };
    this._customEvents = [];
    this._eventStartBlocked = null;
    this._panMove = false;
    this.invertPan = false;
    this.dragMode = 'rotate';
    this.inertia = 0;
    this.scrollZoom = true;
    this.dragPan = true;
    this.dragRotate = true;
    this.doubleClickZoom = true;
    this.touchZoom = true;
    this.touchRotate = false;
    this.keyboard = true;
    this.transitionManager = new TransitionManager({ ...opts,
      getControllerState: props => new this.ControllerState(props),
      onViewStateChange: this._onTransition.bind(this),
      onStateChange: this._setInteractionState.bind(this)
    });
    this.handleEvent = this.handleEvent.bind(this);
    this.eventManager = opts.eventManager;

    this.onViewStateChange = opts.onViewStateChange || (() => {});

    this.onStateChange = opts.onStateChange || (() => {});

    this.makeViewport = opts.makeViewport;
  }

  set events(customEvents) {
    this.toggleEvents(this._customEvents, false);
    this.toggleEvents(customEvents, true);
    this._customEvents = customEvents;

    if (this.props) {
      this.setProps(this.props);
    }
  }

  finalize() {
    for (const eventName in this._events) {
      if (this._events[eventName]) {
        var _this$eventManager;

        (_this$eventManager = this.eventManager) === null || _this$eventManager === void 0 ? void 0 : _this$eventManager.off(eventName, this.handleEvent);
      }
    }

    this.transitionManager.finalize();
  }

  handleEvent(event) {
    this._controllerState = undefined;
    const eventStartBlocked = this._eventStartBlocked;

    switch (event.type) {
      case 'panstart':
        return eventStartBlocked ? false : this._onPanStart(event);

      case 'panmove':
        return this._onPan(event);

      case 'panend':
        return this._onPanEnd(event);

      case 'pinchstart':
        return eventStartBlocked ? false : this._onPinchStart(event);

      case 'pinchmove':
        return this._onPinch(event);

      case 'pinchend':
        return this._onPinchEnd(event);

      case 'tripanstart':
        return eventStartBlocked ? false : this._onTriplePanStart(event);

      case 'tripanmove':
        return this._onTriplePan(event);

      case 'tripanend':
        return this._onTriplePanEnd(event);

      case 'doubletap':
        return this._onDoubleTap(event);

      case 'wheel':
        return this._onWheel(event);

      case 'keydown':
        return this._onKeyDown(event);

      default:
        return false;
    }
  }

  get controllerState() {
    this._controllerState = this._controllerState || new this.ControllerState({
      makeViewport: this.makeViewport,
      ...this.props,
      ...this.state
    });
    return this._controllerState;
  }

  getCenter(event) {
    const {
      x,
      y
    } = this.props;
    const {
      offsetCenter
    } = event;
    return [offsetCenter.x - x, offsetCenter.y - y];
  }

  isPointInBounds(pos, event) {
    const {
      width,
      height
    } = this.props;

    if (event && event.handled) {
      return false;
    }

    const inside = pos[0] >= 0 && pos[0] <= width && pos[1] >= 0 && pos[1] <= height;

    if (inside && event) {
      event.stopPropagation();
    }

    return inside;
  }

  isFunctionKeyPressed(event) {
    const {
      srcEvent
    } = event;
    return Boolean(srcEvent.metaKey || srcEvent.altKey || srcEvent.ctrlKey || srcEvent.shiftKey);
  }

  isDragging() {
    return this._interactionState.isDragging || false;
  }

  blockEvents(timeout) {
    const timer = setTimeout(() => {
      if (this._eventStartBlocked === timer) {
        this._eventStartBlocked = null;
      }
    }, timeout);
    this._eventStartBlocked = timer;
  }

  setProps(props) {
    if (props.dragMode) {
      this.dragMode = props.dragMode;
    }

    this.props = props;

    if (!('transitionInterpolator' in props)) {
      props.transitionInterpolator = this._getTransitionProps().transitionInterpolator;
    }

    this.transitionManager.processViewStateChange(props);
    const {
      inertia
    } = props;
    this.inertia = Number.isFinite(inertia) ? inertia : inertia === true ? DEFAULT_INERTIA : 0;
    const {
      scrollZoom = true,
      dragPan = true,
      dragRotate = true,
      doubleClickZoom = true,
      touchZoom = true,
      touchRotate = false,
      keyboard = true
    } = props;
    const isInteractive = Boolean(this.onViewStateChange);
    this.toggleEvents(EVENT_TYPES.WHEEL, isInteractive && scrollZoom);
    this.toggleEvents(EVENT_TYPES.PAN, isInteractive);
    this.toggleEvents(EVENT_TYPES.PINCH, isInteractive && (touchZoom || touchRotate));
    this.toggleEvents(EVENT_TYPES.TRIPLE_PAN, isInteractive && touchRotate);
    this.toggleEvents(EVENT_TYPES.DOUBLE_TAP, isInteractive && doubleClickZoom);
    this.toggleEvents(EVENT_TYPES.KEYBOARD, isInteractive && keyboard);
    this.scrollZoom = scrollZoom;
    this.dragPan = dragPan;
    this.dragRotate = dragRotate;
    this.doubleClickZoom = doubleClickZoom;
    this.touchZoom = touchZoom;
    this.touchRotate = touchRotate;
    this.keyboard = keyboard;
  }

  updateTransition() {
    this.transitionManager.updateTransition();
  }

  toggleEvents(eventNames, enabled) {
    if (this.eventManager) {
      eventNames.forEach(eventName => {
        if (this._events[eventName] !== enabled) {
          this._events[eventName] = enabled;

          if (enabled) {
            this.eventManager.on(eventName, this.handleEvent);
          } else {
            this.eventManager.off(eventName, this.handleEvent);
          }
        }
      });
    }
  }

  updateViewport(newControllerState, extraProps = null, interactionState = {}) {
    const viewState = { ...newControllerState.getViewportProps(),
      ...extraProps
    };
    const changed = this.controllerState !== newControllerState;
    this.state = newControllerState.getState();

    this._setInteractionState(interactionState);

    if (changed) {
      const oldViewState = this.controllerState && this.controllerState.getViewportProps();

      if (this.onViewStateChange) {
        this.onViewStateChange({
          viewState,
          interactionState: this._interactionState,
          oldViewState
        });
      }
    }
  }

  _onTransition(params) {
    this.onViewStateChange({ ...params,
      interactionState: this._interactionState
    });
  }

  _setInteractionState(newStates) {
    Object.assign(this._interactionState, newStates);
    this.onStateChange(this._interactionState);
  }

  _onPanStart(event) {
    const pos = this.getCenter(event);

    if (!this.isPointInBounds(pos, event)) {
      return false;
    }

    let alternateMode = this.isFunctionKeyPressed(event) || event.rightButton || false;

    if (this.invertPan || this.dragMode === 'pan') {
      alternateMode = !alternateMode;
    }

    const newControllerState = this.controllerState[alternateMode ? 'panStart' : 'rotateStart']({
      pos
    });
    this._panMove = alternateMode;
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true
    });
    return true;
  }

  _onPan(event) {
    if (!this.isDragging()) {
      return false;
    }

    return this._panMove ? this._onPanMove(event) : this._onPanRotate(event);
  }

  _onPanEnd(event) {
    if (!this.isDragging()) {
      return false;
    }

    return this._panMove ? this._onPanMoveEnd(event) : this._onPanRotateEnd(event);
  }

  _onPanMove(event) {
    if (!this.dragPan) {
      return false;
    }

    const pos = this.getCenter(event);
    const newControllerState = this.controllerState.pan({
      pos
    });
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true,
      isPanning: true
    });
    return true;
  }

  _onPanMoveEnd(event) {
    const {
      inertia
    } = this;

    if (this.dragPan && inertia && event.velocity) {
      const pos = this.getCenter(event);
      const endPos = [pos[0] + event.velocityX * inertia / 2, pos[1] + event.velocityY * inertia / 2];
      const newControllerState = this.controllerState.pan({
        pos: endPos
      }).panEnd();
      this.updateViewport(newControllerState, { ...this._getTransitionProps(),
        transitionDuration: inertia,
        transitionEasing: INERTIA_EASING
      }, {
        isDragging: false,
        isPanning: true
      });
    } else {
      const newControllerState = this.controllerState.panEnd();
      this.updateViewport(newControllerState, null, {
        isDragging: false,
        isPanning: false
      });
    }

    return true;
  }

  _onPanRotate(event) {
    if (!this.dragRotate) {
      return false;
    }

    const pos = this.getCenter(event);
    const newControllerState = this.controllerState.rotate({
      pos
    });
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true,
      isRotating: true
    });
    return true;
  }

  _onPanRotateEnd(event) {
    const {
      inertia
    } = this;

    if (this.dragRotate && inertia && event.velocity) {
      const pos = this.getCenter(event);
      const endPos = [pos[0] + event.velocityX * inertia / 2, pos[1] + event.velocityY * inertia / 2];
      const newControllerState = this.controllerState.rotate({
        pos: endPos
      }).rotateEnd();
      this.updateViewport(newControllerState, { ...this._getTransitionProps(),
        transitionDuration: inertia,
        transitionEasing: INERTIA_EASING
      }, {
        isDragging: false,
        isRotating: true
      });
    } else {
      const newControllerState = this.controllerState.rotateEnd();
      this.updateViewport(newControllerState, null, {
        isDragging: false,
        isRotating: false
      });
    }

    return true;
  }

  _onWheel(event) {
    if (!this.scrollZoom) {
      return false;
    }

    const pos = this.getCenter(event);

    if (!this.isPointInBounds(pos, event)) {
      return false;
    }

    event.srcEvent.preventDefault();
    const {
      speed = 0.01,
      smooth = false
    } = this.scrollZoom === true ? {} : this.scrollZoom;
    const {
      delta
    } = event;
    let scale = 2 / (1 + Math.exp(-Math.abs(delta * speed)));

    if (delta < 0 && scale !== 0) {
      scale = 1 / scale;
    }

    const newControllerState = this.controllerState.zoom({
      pos,
      scale
    });
    this.updateViewport(newControllerState, { ...this._getTransitionProps({
        around: pos
      }),
      transitionDuration: smooth ? 250 : 1
    }, {
      isZooming: true,
      isPanning: true
    });
    return true;
  }

  _onTriplePanStart(event) {
    const pos = this.getCenter(event);

    if (!this.isPointInBounds(pos, event)) {
      return false;
    }

    const newControllerState = this.controllerState.rotateStart({
      pos
    });
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true
    });
    return true;
  }

  _onTriplePan(event) {
    if (!this.touchRotate) {
      return false;
    }

    if (!this.isDragging()) {
      return false;
    }

    const pos = this.getCenter(event);
    pos[0] -= event.deltaX;
    const newControllerState = this.controllerState.rotate({
      pos
    });
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true,
      isRotating: true
    });
    return true;
  }

  _onTriplePanEnd(event) {
    if (!this.isDragging()) {
      return false;
    }

    const {
      inertia
    } = this;

    if (this.touchRotate && inertia && event.velocityY) {
      const pos = this.getCenter(event);
      const endPos = [pos[0], pos[1] += event.velocityY * inertia / 2];
      const newControllerState = this.controllerState.rotate({
        pos: endPos
      });
      this.updateViewport(newControllerState, { ...this._getTransitionProps(),
        transitionDuration: inertia,
        transitionEasing: INERTIA_EASING
      }, {
        isDragging: false,
        isRotating: true
      });
      this.blockEvents(inertia);
    } else {
      const newControllerState = this.controllerState.rotateEnd();
      this.updateViewport(newControllerState, null, {
        isDragging: false,
        isRotating: false
      });
    }

    return true;
  }

  _onPinchStart(event) {
    const pos = this.getCenter(event);

    if (!this.isPointInBounds(pos, event)) {
      return false;
    }

    const newControllerState = this.controllerState.zoomStart({
      pos
    }).rotateStart({
      pos
    });
    pinchEventWorkaround._startPinchRotation = event.rotation;
    pinchEventWorkaround._lastPinchEvent = event;
    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true
    });
    return true;
  }

  _onPinch(event) {
    if (!this.touchZoom && !this.touchRotate) {
      return false;
    }

    if (!this.isDragging()) {
      return false;
    }

    let newControllerState = this.controllerState;

    if (this.touchZoom) {
      const {
        scale
      } = event;
      const pos = this.getCenter(event);
      newControllerState = newControllerState.zoom({
        pos,
        scale
      });
    }

    if (this.touchRotate) {
      const {
        rotation
      } = event;
      newControllerState = newControllerState.rotate({
        deltaAngleX: pinchEventWorkaround._startPinchRotation - rotation
      });
    }

    this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
      isDragging: true,
      isPanning: this.touchZoom,
      isZooming: this.touchZoom,
      isRotating: this.touchRotate
    });
    pinchEventWorkaround._lastPinchEvent = event;
    return true;
  }

  _onPinchEnd(event) {
    if (!this.isDragging()) {
      return false;
    }

    const {
      inertia
    } = this;
    const {
      _lastPinchEvent
    } = pinchEventWorkaround;

    if (this.touchZoom && inertia && _lastPinchEvent && event.scale !== _lastPinchEvent.scale) {
      const pos = this.getCenter(event);
      let newControllerState = this.controllerState.rotateEnd();
      const z = Math.log2(event.scale);

      const velocityZ = (z - Math.log2(_lastPinchEvent.scale)) / (event.deltaTime - _lastPinchEvent.deltaTime);

      const endScale = Math.pow(2, z + velocityZ * inertia / 2);
      newControllerState = newControllerState.zoom({
        pos,
        scale: endScale
      }).zoomEnd();
      this.updateViewport(newControllerState, { ...this._getTransitionProps({
          around: pos
        }),
        transitionDuration: inertia,
        transitionEasing: INERTIA_EASING
      }, {
        isDragging: false,
        isPanning: this.touchZoom,
        isZooming: this.touchZoom,
        isRotating: false
      });
      this.blockEvents(inertia);
    } else {
      const newControllerState = this.controllerState.zoomEnd().rotateEnd();
      this.updateViewport(newControllerState, null, {
        isDragging: false,
        isPanning: false,
        isZooming: false,
        isRotating: false
      });
    }

    pinchEventWorkaround._startPinchRotation = null;
    pinchEventWorkaround._lastPinchEvent = null;
    return true;
  }

  _onDoubleTap(event) {
    if (!this.doubleClickZoom) {
      return false;
    }

    const pos = this.getCenter(event);

    if (!this.isPointInBounds(pos, event)) {
      return false;
    }

    const isZoomOut = this.isFunctionKeyPressed(event);
    const newControllerState = this.controllerState.zoom({
      pos,
      scale: isZoomOut ? 0.5 : 2
    });
    this.updateViewport(newControllerState, this._getTransitionProps({
      around: pos
    }), {
      isZooming: true,
      isPanning: true
    });
    this.blockEvents(100);
    return true;
  }

  _onKeyDown(event) {
    if (!this.keyboard) {
      return false;
    }

    const funcKey = this.isFunctionKeyPressed(event);
    const {
      zoomSpeed,
      moveSpeed,
      rotateSpeedX,
      rotateSpeedY
    } = this.keyboard === true ? {} : this.keyboard;
    const {
      controllerState
    } = this;
    let newControllerState;
    const interactionState = {};

    switch (event.srcEvent.code) {
      case 'Minus':
        newControllerState = funcKey ? controllerState.zoomOut(zoomSpeed).zoomOut(zoomSpeed) : controllerState.zoomOut(zoomSpeed);
        interactionState.isZooming = true;
        break;

      case 'Equal':
        newControllerState = funcKey ? controllerState.zoomIn(zoomSpeed).zoomIn(zoomSpeed) : controllerState.zoomIn(zoomSpeed);
        interactionState.isZooming = true;
        break;

      case 'ArrowLeft':
        if (funcKey) {
          newControllerState = controllerState.rotateLeft(rotateSpeedX);
          interactionState.isRotating = true;
        } else {
          newControllerState = controllerState.moveLeft(moveSpeed);
          interactionState.isPanning = true;
        }

        break;

      case 'ArrowRight':
        if (funcKey) {
          newControllerState = controllerState.rotateRight(rotateSpeedX);
          interactionState.isRotating = true;
        } else {
          newControllerState = controllerState.moveRight(moveSpeed);
          interactionState.isPanning = true;
        }

        break;

      case 'ArrowUp':
        if (funcKey) {
          newControllerState = controllerState.rotateUp(rotateSpeedY);
          interactionState.isRotating = true;
        } else {
          newControllerState = controllerState.moveUp(moveSpeed);
          interactionState.isPanning = true;
        }

        break;

      case 'ArrowDown':
        if (funcKey) {
          newControllerState = controllerState.rotateDown(rotateSpeedY);
          interactionState.isRotating = true;
        } else {
          newControllerState = controllerState.moveDown(moveSpeed);
          interactionState.isPanning = true;
        }

        break;

      default:
        return false;
    }

    this.updateViewport(newControllerState, this._getTransitionProps(), interactionState);
    return true;
  }

  _getTransitionProps(opts) {
    const {
      transition
    } = this;

    if (!transition || !transition.transitionInterpolator) {
      return NO_TRANSITION_PROPS;
    }

    return opts ? { ...transition,
      transitionInterpolator: new LinearInterpolator({ ...opts,
        ...transition.transitionInterpolator.opts,
        makeViewport: this.controllerState.makeViewport
      })
    } : transition;
  }

}
//# sourceMappingURL=controller.js.map