import { deepEqual } from '../utils/deep-equal';
import log from '../utils/log';
import { flatten } from '../utils/flatten';
export default class ViewManager {
  constructor(props) {
    this.width = void 0;
    this.height = void 0;
    this.views = void 0;
    this.viewState = void 0;
    this.controllers = void 0;
    this.timeline = void 0;
    this._viewports = void 0;
    this._viewportMap = void 0;
    this._isUpdating = void 0;
    this._needsRedraw = void 0;
    this._needsUpdate = void 0;
    this._eventManager = void 0;
    this._eventCallbacks = void 0;
    this.views = [];
    this.width = 100;
    this.height = 100;
    this.viewState = {};
    this.controllers = {};
    this.timeline = props.timeline;
    this._viewports = [];
    this._viewportMap = {};
    this._isUpdating = false;
    this._needsRedraw = 'First render';
    this._needsUpdate = 'Initialize';
    this._eventManager = props.eventManager;
    this._eventCallbacks = {
      onViewStateChange: props.onViewStateChange,
      onInteractionStateChange: props.onInteractionStateChange
    };
    Object.seal(this);
    this.setProps(props);
  }

  finalize() {
    for (const key in this.controllers) {
      const controller = this.controllers[key];

      if (controller) {
        controller.finalize();
      }
    }

    this.controllers = {};
  }

  needsRedraw(opts = {
    clearRedrawFlags: false
  }) {
    const redraw = this._needsRedraw;

    if (opts.clearRedrawFlags) {
      this._needsRedraw = false;
    }

    return redraw;
  }

  setNeedsUpdate(reason) {
    this._needsUpdate = this._needsUpdate || reason;
    this._needsRedraw = this._needsRedraw || reason;
  }

  updateViewStates() {
    for (const viewId in this.controllers) {
      const controller = this.controllers[viewId];

      if (controller) {
        controller.updateTransition();
      }
    }
  }

  getViewports(rect) {
    if (rect) {
      return this._viewports.filter(viewport => viewport.containsPixel(rect));
    }

    return this._viewports;
  }

  getViews() {
    const viewMap = {};
    this.views.forEach(view => {
      viewMap[view.id] = view;
    });
    return viewMap;
  }

  getView(viewId) {
    return this.views.find(view => view.id === viewId);
  }

  getViewState(viewOrViewId) {
    const view = typeof viewOrViewId === 'string' ? this.getView(viewOrViewId) : viewOrViewId;
    const viewState = view && this.viewState[view.getViewStateId()] || this.viewState;
    return view ? view.filterViewState(viewState) : viewState;
  }

  getViewport(viewId) {
    return this._viewportMap[viewId];
  }

  unproject(xyz, opts) {
    const viewports = this.getViewports();
    const pixel = {
      x: xyz[0],
      y: xyz[1]
    };

    for (let i = viewports.length - 1; i >= 0; --i) {
      const viewport = viewports[i];

      if (viewport.containsPixel(pixel)) {
        const p = xyz.slice();
        p[0] -= viewport.x;
        p[1] -= viewport.y;
        return viewport.unproject(p, opts);
      }
    }

    return null;
  }

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

    if (props.viewState) {
      this._setViewState(props.viewState);
    }

    if ('width' in props || 'height' in props) {
      this._setSize(props.width, props.height);
    }

    if (!this._isUpdating) {
      this._update();
    }
  }

  _update() {
    this._isUpdating = true;

    if (this._needsUpdate) {
      this._needsUpdate = false;

      this._rebuildViewports();
    }

    if (this._needsUpdate) {
      this._needsUpdate = false;

      this._rebuildViewports();
    }

    this._isUpdating = false;
  }

  _setSize(width, height) {
    if (width !== this.width || height !== this.height) {
      this.width = width;
      this.height = height;
      this.setNeedsUpdate('Size changed');
    }
  }

  _setViews(views) {
    views = flatten(views, Boolean);

    const viewsChanged = this._diffViews(views, this.views);

    if (viewsChanged) {
      this.setNeedsUpdate('views changed');
    }

    this.views = views;
  }

  _setViewState(viewState) {
    if (viewState) {
      const viewStateChanged = !deepEqual(viewState, this.viewState, 3);

      if (viewStateChanged) {
        this.setNeedsUpdate('viewState changed');
      }

      this.viewState = viewState;
    } else {
      log.warn('missing `viewState` or `initialViewState`')();
    }
  }

  _onViewStateChange(viewId, event) {
    if (this._eventCallbacks.onViewStateChange) {
      this._eventCallbacks.onViewStateChange({ ...event,
        viewId
      });
    }
  }

  _createController(view, props) {
    const Controller = props.type;
    const controller = new Controller({
      timeline: this.timeline,
      eventManager: this._eventManager,
      onViewStateChange: this._onViewStateChange.bind(this, props.id),
      onStateChange: this._eventCallbacks.onInteractionStateChange,
      makeViewport: viewState => {
        var _this$getView;

        return (_this$getView = this.getView(view.id)) === null || _this$getView === void 0 ? void 0 : _this$getView.makeViewport({
          viewState,
          width: this.width,
          height: this.height
        });
      }
    });
    return controller;
  }

  _updateController(view, viewState, viewport, controller) {
    const controllerProps = view.controller;

    if (controllerProps && viewport) {
      const resolvedProps = { ...viewState,
        ...controllerProps,
        id: view.id,
        x: viewport.x,
        y: viewport.y,
        width: viewport.width,
        height: viewport.height
      };

      if (!controller || controller.constructor !== controllerProps.type) {
        controller = this._createController(view, resolvedProps);
      }

      if (controller) {
        controller.setProps(resolvedProps);
      }

      return controller;
    }

    return null;
  }

  _rebuildViewports() {
    const {
      views
    } = this;
    const oldControllers = this.controllers;
    this._viewports = [];
    this.controllers = {};
    let invalidateControllers = false;

    for (let i = views.length; i--;) {
      const view = views[i];
      const viewState = this.getViewState(view);
      const viewport = view.makeViewport({
        viewState,
        width: this.width,
        height: this.height
      });
      let oldController = oldControllers[view.id];
      const hasController = Boolean(view.controller);

      if (hasController && !oldController) {
        invalidateControllers = true;
      }

      if ((invalidateControllers || !hasController) && oldController) {
        oldController.finalize();
        oldController = null;
      }

      this.controllers[view.id] = this._updateController(view, viewState, viewport, oldController);

      if (viewport) {
        this._viewports.unshift(viewport);
      }
    }

    for (const id in oldControllers) {
      const oldController = oldControllers[id];

      if (oldController && !this.controllers[id]) {
        oldController.finalize();
      }
    }

    this._buildViewportMap();
  }

  _buildViewportMap() {
    this._viewportMap = {};

    this._viewports.forEach(viewport => {
      if (viewport.id) {
        this._viewportMap[viewport.id] = this._viewportMap[viewport.id] || viewport;
      }
    });
  }

  _diffViews(newViews, oldViews) {
    if (newViews.length !== oldViews.length) {
      return true;
    }

    return newViews.some((_, i) => !newViews[i].equals(oldViews[i]));
  }

}
//# sourceMappingURL=view-manager.js.map