var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import assert from '../utils/assert';
import { Framebuffer, ShaderCache } from 'luma.gl';
import seer from 'seer';
import Layer from './layer';
import { drawLayers as _drawLayers } from './draw-layers';
import { pickObject as _pickObject, pickVisibleObjects } from './pick-layers';
import { LIFECYCLE } from '../lifecycle/constants';
import { deepEqual } from '../utils/deep-equal';
import View from '../views/view';
import MapView from '../views/map-view';
import Viewport from '../viewports/viewport';
import log from '../utils/log';
import { flatten } from '../utils/flatten';
import { Stats } from 'probe.gl';

import { setPropOverrides, layerEditListener, seerInitListener, initLayerInSeer, updateLayerInSeer } from './seer-integration';

var LOG_PRIORITY_LIFECYCLE = 2;
var LOG_PRIORITY_LIFECYCLE_MINOR = 4;

var INITIAL_VIEW_STATE = { latitude: 0, longitude: 0, zoom: 1 };

var initialContext = {
  uniforms: {},
  viewports: [],
  viewport: null,
  layerFilter: null,
  viewportChanged: true,
  pickingFBO: null,
  useDevicePixels: true,
  lastPickedInfo: {
    index: -1,
    layerId: null
  }
};

var layerName = function layerName(layer) {
  return layer instanceof Layer ? '' + layer : !layer ? 'null' : 'invalid';
};

var LayerManager = function () {
  // eslint-disable-next-line
  function LayerManager(gl) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        eventManager = _ref.eventManager,
        stats = _ref.stats;

    _classCallCheck(this, LayerManager);

    // Currently deck.gl expects the DeckGL.layers array to be different
    // whenever React rerenders. If the same layers array is used, the
    // LayerManager's diffing algorithm will generate a fatal error and
    // break the rendering.

    // `this.lastRenderedLayers` stores the UNFILTERED layers sent
    // down to LayerManager, so that `layers` reference can be compared.
    // If it's the same across two React render calls, the diffing logic
    // will be skipped.
    this.lastRenderedLayers = [];
    this.prevLayers = [];
    this.layers = [];

    this.oldContext = {};
    this.context = Object.assign({}, initialContext, {
      gl: gl,
      // Enabling luma.gl Program caching using private API (_cachePrograms)
      shaderCache: new ShaderCache({ gl: gl, _cachePrograms: true }),
      layerManager: this,
      stats: stats || new Stats({ id: 'deck.gl' })
    });

    // List of view descriptors, gets re-evaluated when width/height changes
    this.width = 100;
    this.height = 100;
    this.views = [];
    this.viewState = INITIAL_VIEW_STATE;
    this.viewsChanged = true;
    this.viewports = []; // Generated viewports
    this._needsRedraw = 'Initial render';
    this._needsUpdate = false;

    // Event handling
    this._pickingRadius = 0;

    this._eventManager = null;
    this._onLayerClick = null;
    this._onLayerHover = null;
    this._onClick = this._onClick.bind(this);
    this._onPointerMove = this._onPointerMove.bind(this);
    this._onPointerLeave = this._onPointerLeave.bind(this);
    this._pickAndCallback = this._pickAndCallback.bind(this);

    // Seer integration
    this._initSeer = this._initSeer.bind(this);
    this._editSeer = this._editSeer.bind(this);

    Object.seal(this);

    seerInitListener(this._initSeer);
    layerEditListener(this._editSeer);

    if (eventManager) {
      this._initEventHandling(eventManager);
    }

    // Init with default map viewport
    this.setViews();
  }

  /**
   * Method to call when the layer manager is not needed anymore.
   *
   * Currently used in the <DeckGL> componentWillUnmount lifecycle to unbind Seer listeners.
   */


  _createClass(LayerManager, [{
    key: 'finalize',
    value: function finalize() {
      seer.removeListener(this._initSeer);
      seer.removeListener(this._editSeer);
    }

    // Check if a redraw is needed

  }, {
    key: 'needsRedraw',
    value: function needsRedraw() {
      var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          _ref2$clearRedrawFlag = _ref2.clearRedrawFlags,
          clearRedrawFlags = _ref2$clearRedrawFlag === undefined ? true : _ref2$clearRedrawFlag;

      return this._checkIfNeedsRedraw(clearRedrawFlags);
    }

    // Check if a deep update of all layers is needed

  }, {
    key: 'needsUpdate',
    value: function needsUpdate() {
      return this._needsUpdate;
    }

    // Layers will be redrawn (in next animation frame)

  }, {
    key: 'setNeedsRedraw',
    value: function setNeedsRedraw(reason) {
      this._needsRedraw = this._needsRedraw || reason;
    }

    // Layers will be updated deeply (in next animation frame)
    // Potentially regenerating attributes and sub layers

  }, {
    key: 'setNeedsUpdate',
    value: function setNeedsUpdate(reason) {
      this._needsUpdate = this._needsUpdate || reason;
    }

    // Gets an (optionally) filtered list of layers

  }, {
    key: 'getLayers',
    value: function getLayers() {
      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          _ref3$layerIds = _ref3.layerIds,
          layerIds = _ref3$layerIds === undefined ? null : _ref3$layerIds;

      // Filtering by layerId compares beginning of strings, so that sublayers will be included
      // Dependes on the convention of adding suffixes to the parent's layer name
      return layerIds ? this.layers.filter(function (layer) {
        return layerIds.find(function (layerId) {
          return layer.id.indexOf(layerId) === 0;
        });
      }) : this.layers;
    }
  }, {
    key: 'getViews',
    value: function getViews() {
      return this.views;
    }

    // Get a set of viewports for a given width and height
    // TODO - Intention is for deck.gl to autodeduce width and height and drop the need for props

  }, {
    key: 'getViewports',
    value: function getViewports() {
      this._rebuildViewportsFromViews();
      this.context.viewport = this.viewports[0];
      return this.viewports;
    }

    /**
     * Set parameters needed for layer rendering and picking.
     * Parameters are to be passed as a single object, with the following values:
     * @param {Boolean} useDevicePixels
     */
    /* eslint-disable complexity */

  }, {
    key: 'setParameters',
    value: function setParameters(parameters) {
      if ('eventManager' in parameters) {
        this._initEventHandling(parameters.eventManager);
      }

      if ('pickingRadius' in parameters || 'onLayerClick' in parameters || 'onLayerHover' in parameters) {
        this._setEventHandlingParameters(parameters);
      }

      if ('width' in parameters || 'height' in parameters) {
        this.setSize(parameters.width, parameters.height);
      }

      if ('views' in parameters) {
        this.setViews(parameters.views);
      }

      // TODO - support multiple view states
      if ('viewState' in parameters) {
        this.setViewState(parameters.viewState);
      }

      // TODO - For now we set layers before viewports to preservenchangeFlags
      if ('layers' in parameters) {
        this.setLayers(parameters.layers);
      }

      if ('layerFilter' in parameters) {
        this.context.layerFilter = parameters.layerFilter;
        if (this.context.layerFilter !== parameters.layerFilter) {
          this.setNeedsRedraw('layerFilter changed');
        }
      }

      if ('drawPickingColors' in parameters) {
        if (this.context.drawPickingColors !== parameters.drawPickingColors) {
          this.setNeedsRedraw('drawPickingColors changed');
        }
      }

      Object.assign(this.context, parameters);
    }
    /* eslint-enable complexity */

  }, {
    key: 'setSize',
    value: function setSize(width, height) {
      assert(Number.isFinite(width) && Number.isFinite(height));
      if (width !== this.width || height !== this.height) {
        this.viewsChanged = true;
        this.width = width;
        this.height = height;
      }
    }

    // Update the view descriptor list and set change flag if needed
    // Does not actually rebuild the `Viewport`s until `getViewports` is called

  }, {
    key: 'setViews',
    value: function setViews(views) {
      // For now, we default to a full screen map view port
      // TODO - apps may want to specify an empty view list...
      if (!views || views.length === 0) {
        views = [new MapView({ id: 'default-view' })];
      }

      // Ensure any "naked" Viewports are wrapped in View instances
      views = flatten(views, { filter: Boolean }).map(function (view) {
        return view instanceof Viewport ? new View({ viewportInstance: view }) : view;
      });

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

      this.views = views;
      this.viewsChanged = this.viewsChanged || viewsChanged;
    }
  }, {
    key: 'setViewState',
    value: function setViewState(viewState) {
      if (viewState) {
        var viewStateChanged = deepEqual(viewState, this.viewState);
        this.viewState = viewState;
        this.viewsChanged = true || viewStateChanged;
      } else {
        log.warn('viewState is not valid')();
      }
    }

    // Supply a new layer list, initiating sublayer generation and layer matching

  }, {
    key: 'setLayers',
    value: function setLayers(newLayers) {
      this.getViewports();
      assert(this.context.viewport, 'LayerManager.updateLayers: viewport not set');

      // TODO - something is generating state updates that cause rerender of the same
      if (newLayers === this.lastRenderedLayers) {
        log.log(3, 'Ignoring layer update due to layer array not changed')();
        return this;
      }
      this.lastRenderedLayers = newLayers;

      newLayers = flatten(newLayers, { filter: Boolean });

      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = newLayers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var layer = _step.value;

          layer.context = this.context;
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      this.prevLayers = this.layers;

      var _updateLayers2 = this._updateLayers({
        oldLayers: this.prevLayers,
        newLayers: newLayers
      }),
          error = _updateLayers2.error,
          generatedLayers = _updateLayers2.generatedLayers;

      this.layers = generatedLayers;

      // Throw first error found, if any
      if (error) {
        throw error;
      }
      return this;
    }

    // Update layers from last cycle if `setNeedsUpdate()` has been called

  }, {
    key: 'updateLayers',
    value: function updateLayers() {
      // NOTE: For now, even if only some layer has changed, we update all layers
      // to ensure that layer id maps etc remain consistent even if different
      // sublayers are rendered
      var reason = this.needsUpdate();
      if (reason) {
        this.setNeedsRedraw('updating layers: ' + reason);
        // HACK - Call with a copy of lastRenderedLayers to trigger a full update
        this.setLayers([].concat(_toConsumableArray(this.lastRenderedLayers)));
      }
    }

    //
    // METHODS FOR LAYERS
    //

    // Draw all layers in all views

  }, {
    key: 'drawLayers',
    value: function drawLayers() {
      var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          _ref4$pass = _ref4.pass,
          pass = _ref4$pass === undefined ? 'render to screen' : _ref4$pass,
          _ref4$redrawReason = _ref4.redrawReason,
          redrawReason = _ref4$redrawReason === undefined ? 'unknown reason' : _ref4$redrawReason;

      var _context = this.context,
          gl = _context.gl,
          useDevicePixels = _context.useDevicePixels,
          drawPickingColors = _context.drawPickingColors;

      // render this viewport

      _drawLayers(gl, {
        layers: this.layers,
        viewports: this.getViewports(),
        onViewportActive: this._activateViewport.bind(this),
        useDevicePixels: useDevicePixels,
        drawPickingColors: drawPickingColors,
        pass: pass,
        layerFilter: this.context.layerFilter,
        redrawReason: redrawReason
      });
    }

    // Pick the closest info at given coordinate

  }, {
    key: 'pickObject',
    value: function pickObject(_ref5) {
      var x = _ref5.x,
          y = _ref5.y,
          mode = _ref5.mode,
          _ref5$radius = _ref5.radius,
          radius = _ref5$radius === undefined ? 0 : _ref5$radius,
          layerIds = _ref5.layerIds,
          layerFilter = _ref5.layerFilter;
      var _context2 = this.context,
          gl = _context2.gl,
          useDevicePixels = _context2.useDevicePixels;


      var layers = this.getLayers({ layerIds: layerIds });

      return _pickObject(gl, {
        // User params
        x: x,
        y: y,
        radius: radius,
        layers: layers,
        mode: mode,
        layerFilter: layerFilter,
        // Injected params
        viewports: this.getViewports(),
        onViewportActive: this._activateViewport.bind(this),
        pickingFBO: this._getPickingBuffer(),
        lastPickedInfo: this.context.lastPickedInfo,
        useDevicePixels: useDevicePixels
      });
    }

    // Get all unique infos within a bounding box

  }, {
    key: 'pickObjects',
    value: function pickObjects(_ref6) {
      var x = _ref6.x,
          y = _ref6.y,
          width = _ref6.width,
          height = _ref6.height,
          layerIds = _ref6.layerIds,
          layerFilter = _ref6.layerFilter;
      var _context3 = this.context,
          gl = _context3.gl,
          useDevicePixels = _context3.useDevicePixels;


      var layers = this.getLayers({ layerIds: layerIds });

      return pickVisibleObjects(gl, {
        x: x,
        y: y,
        width: width,
        height: height,
        layers: layers,
        layerFilter: layerFilter,
        mode: 'pickObjects',
        viewports: this.getViewports(),
        onViewportActive: this._activateViewport.bind(this),
        pickingFBO: this._getPickingBuffer(),
        useDevicePixels: useDevicePixels
      });
    }

    //
    // DEPRECATED METHODS in V5.1
    //

  }, {
    key: 'setViewports',
    value: function setViewports(viewports) {
      log.deprecated('setViewport', 'setViews')();
      this.setViews(viewports);
      return this;
    }

    //
    // DEPRECATED METHODS in V5
    //

  }, {
    key: 'setViewport',
    value: function setViewport(viewport) {
      log.deprecated('setViewport', 'setViews')();
      this.setViews([viewport]);
      return this;
    }

    //
    // PRIVATE METHODS
    //

  }, {
    key: '_checkIfNeedsRedraw',
    value: function _checkIfNeedsRedraw(clearRedrawFlags) {
      var redraw = this._needsRedraw;
      if (clearRedrawFlags) {
        this._needsRedraw = false;
      }

      // This layers list doesn't include sublayers, relying on composite layers
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = this.layers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var layer = _step2.value;

          // Call every layer to clear their flags
          var layerNeedsRedraw = layer.getNeedsRedraw({ clearRedrawFlags: clearRedrawFlags });
          redraw = redraw || layerNeedsRedraw;
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2.return) {
            _iterator2.return();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      return redraw;
    }

    // Rebuilds viewports from descriptors towards a certain window size

  }, {
    key: '_rebuildViewportsFromViews',
    value: function _rebuildViewportsFromViews() {
      if (this.viewsChanged) {
        var width = this.width,
            height = this.height,
            views = this.views,
            viewState = this.viewState;

        var newViewports = views.map(function (view) {
          return view.makeViewport({ width: width, height: height, viewState: viewState });
        });

        this.setNeedsRedraw('Viewport(s) changed');

        // Ensure one viewport is activated, layers may expect it
        // TODO - handle empty viewport list (using dummy viewport), or assert
        // const oldViewports = this.context.viewports;

        var viewport = newViewports[0];
        assert(viewport instanceof Viewport, 'Invalid viewport');

        this.context.viewports = newViewports;
        this._activateViewport(viewport);
        // }

        // We've just rebuilt the viewports to match the descriptors, so clear the flag
        this.viewports = newViewports;
        this.viewsChanged = false;
      }
    }

    // Check if viewport array has changed, returns true if any change
    // Note that descriptors can be the same

  }, {
    key: '_diffViews',
    value: function _diffViews(newViews, oldViews) {
      if (newViews.length !== oldViews.length) {
        return true;
      }

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

    /**
     * @param {Object} eventManager   A source of DOM input events
     */

  }, {
    key: '_initEventHandling',
    value: function _initEventHandling(eventManager) {
      this._eventManager = eventManager;

      // TODO: add/remove handlers on demand at runtime, not all at once on init.
      // Consider both top-level handlers like onLayerClick/Hover
      // and per-layer handlers attached to individual layers.
      // https://github.com/uber/deck.gl/issues/634
      this._eventManager.on({
        click: this._onClick,
        pointermove: this._onPointerMove,
        pointerleave: this._onPointerLeave
      });
    }

    // Set parameters for input event handling.

  }, {
    key: '_setEventHandlingParameters',
    value: function _setEventHandlingParameters(_ref7) {
      var pickingRadius = _ref7.pickingRadius,
          onLayerClick = _ref7.onLayerClick,
          onLayerHover = _ref7.onLayerHover;

      if (!isNaN(pickingRadius)) {
        this._pickingRadius = pickingRadius;
      }
      if (typeof onLayerClick !== 'undefined') {
        this._onLayerClick = onLayerClick;
      }
      if (typeof onLayerHover !== 'undefined') {
        this._onLayerHover = onLayerHover;
      }
      this._validateEventHandling();
    }

    // Make a viewport "current" in layer context, primed for draw

  }, {
    key: '_activateViewport',
    value: function _activateViewport(viewport) {
      var oldViewport = this.context.viewport;
      var viewportChanged = !oldViewport || !viewport.equals(oldViewport);

      if (viewportChanged) {
        Object.assign(this.oldContext, this.context);
        this.context.viewport = viewport;
        this.context.viewportChanged = true;
        this.context.uniforms = {};
        log.log(4, 'Viewport', viewport)();

        // Update layers states
        // Let screen space layers update their state based on viewport
        // TODO - reimplement viewport change detection (single viewport optimization)
        // TODO - don't set viewportChanged during setViews?
        if (this.context.viewportChanged) {
          var _iteratorNormalCompletion3 = true;
          var _didIteratorError3 = false;
          var _iteratorError3 = undefined;

          try {
            for (var _iterator3 = this.layers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
              var layer = _step3.value;

              layer.setChangeFlags({ viewportChanged: 'Viewport changed' });
              this._updateLayer(layer);
            }
          } catch (err) {
            _didIteratorError3 = true;
            _iteratorError3 = err;
          } finally {
            try {
              if (!_iteratorNormalCompletion3 && _iterator3.return) {
                _iterator3.return();
              }
            } finally {
              if (_didIteratorError3) {
                throw _iteratorError3;
              }
            }
          }
        }
      }

      assert(this.context.viewport, 'LayerManager: viewport not set');

      return this;
    }
  }, {
    key: '_getPickingBuffer',
    value: function _getPickingBuffer() {
      var gl = this.context.gl;
      // Create a frame buffer if not already available

      this.context.pickingFBO = this.context.pickingFBO || new Framebuffer(gl);
      // Resize it to current canvas size (this is a noop if size hasn't changed)
      this.context.pickingFBO.resize({ width: gl.canvas.width, height: gl.canvas.height });
      return this.context.pickingFBO;
    }

    // Match all layers, checking for caught errors
    // To avoid having an exception in one layer disrupt other layers
    // TODO - mark layers with exceptions as bad and remove from rendering cycle?

  }, {
    key: '_updateLayers',
    value: function _updateLayers(_ref8) {
      var oldLayers = _ref8.oldLayers,
          newLayers = _ref8.newLayers;

      // Create old layer map
      var oldLayerMap = {};
      var _iteratorNormalCompletion4 = true;
      var _didIteratorError4 = false;
      var _iteratorError4 = undefined;

      try {
        for (var _iterator4 = oldLayers[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
          var oldLayer = _step4.value;

          if (oldLayerMap[oldLayer.id]) {
            log.warn('Multiple old layers with same id ' + layerName(oldLayer))();
          } else {
            oldLayerMap[oldLayer.id] = oldLayer;
          }
        }

        // Allocate array for generated layers
      } catch (err) {
        _didIteratorError4 = true;
        _iteratorError4 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion4 && _iterator4.return) {
            _iterator4.return();
          }
        } finally {
          if (_didIteratorError4) {
            throw _iteratorError4;
          }
        }
      }

      var generatedLayers = [];

      // Match sublayers
      var error = this._updateSublayersRecursively({
        newLayers: newLayers,
        oldLayerMap: oldLayerMap,
        generatedLayers: generatedLayers
      });

      // Finalize unmatched layers
      var error2 = this._finalizeOldLayers(oldLayerMap);

      this._needsUpdate = false;

      var firstError = error || error2;
      return { error: firstError, generatedLayers: generatedLayers };
    }

    // Note: adds generated layers to `generatedLayers` array parameter

  }, {
    key: '_updateSublayersRecursively',
    value: function _updateSublayersRecursively(_ref9) {
      var newLayers = _ref9.newLayers,
          oldLayerMap = _ref9.oldLayerMap,
          generatedLayers = _ref9.generatedLayers;

      var error = null;

      var _iteratorNormalCompletion5 = true;
      var _didIteratorError5 = false;
      var _iteratorError5 = undefined;

      try {
        for (var _iterator5 = newLayers[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
          var newLayer = _step5.value;

          newLayer.context = this.context;

          // Given a new coming layer, find its matching old layer (if any)
          var oldLayer = oldLayerMap[newLayer.id];
          if (oldLayer === null) {
            // null, rather than undefined, means this id was originally there
            log.warn('Multiple new layers with same id ' + layerName(newLayer))();
          }
          // Remove the old layer from candidates, as it has been matched with this layer
          oldLayerMap[newLayer.id] = null;

          var sublayers = null;

          // We must not generate exceptions until after layer matching is complete
          try {
            if (!oldLayer) {
              this._initializeLayer(newLayer);
              initLayerInSeer(newLayer); // Initializes layer in seer chrome extension (if connected)
            } else {
              this._transferLayerState(oldLayer, newLayer);
              this._updateLayer(newLayer);
              updateLayerInSeer(newLayer); // Updates layer in seer chrome extension (if connected)
            }
            generatedLayers.push(newLayer);

            // Call layer lifecycle method: render sublayers
            sublayers = newLayer.isComposite && newLayer.getSubLayers();
            // End layer lifecycle method: render sublayers
          } catch (err) {
            log.warn('error during matching of ' + layerName(newLayer), err);
            error = error || err; // Record first exception
          }

          if (sublayers) {
            this._updateSublayersRecursively({
              newLayers: sublayers,
              oldLayerMap: oldLayerMap,
              generatedLayers: generatedLayers
            });
          }
        }
      } catch (err) {
        _didIteratorError5 = true;
        _iteratorError5 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion5 && _iterator5.return) {
            _iterator5.return();
          }
        } finally {
          if (_didIteratorError5) {
            throw _iteratorError5;
          }
        }
      }

      return error;
    }

    // Finalize any old layers that were not matched

  }, {
    key: '_finalizeOldLayers',
    value: function _finalizeOldLayers(oldLayerMap) {
      var error = null;
      for (var layerId in oldLayerMap) {
        var layer = oldLayerMap[layerId];
        if (layer) {
          error = error || this._finalizeLayer(layer);
        }
      }
      return error;
    }

    // EXCEPTION SAFE LAYER ACCESS

    // Initializes a single layer, calling layer methods

  }, {
    key: '_initializeLayer',
    value: function _initializeLayer(layer) {
      log.log(LOG_PRIORITY_LIFECYCLE, 'initializing ' + layerName(layer))();

      var error = null;
      try {
        layer._initialize();
        layer.lifecycle = LIFECYCLE.INITIALIZED;
      } catch (err) {
        log.warn('error while initializing ' + layerName(layer) + '\n', err)();
        error = error || err;
        // TODO - what should the lifecycle state be here? LIFECYCLE.INITIALIZATION_FAILED?
      }

      // Set back pointer (used in picking)
      layer.internalState.layer = layer;

      // Save layer on model for picking purposes
      // store on model.userData rather than directly on model
      var _iteratorNormalCompletion6 = true;
      var _didIteratorError6 = false;
      var _iteratorError6 = undefined;

      try {
        for (var _iterator6 = layer.getModels()[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
          var model = _step6.value;

          model.userData.layer = layer;
        }
      } catch (err) {
        _didIteratorError6 = true;
        _iteratorError6 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion6 && _iterator6.return) {
            _iterator6.return();
          }
        } finally {
          if (_didIteratorError6) {
            throw _iteratorError6;
          }
        }
      }

      return error;
    }
  }, {
    key: '_transferLayerState',
    value: function _transferLayerState(oldLayer, newLayer) {
      if (newLayer !== oldLayer) {
        log.log(LOG_PRIORITY_LIFECYCLE_MINOR, 'matched ' + layerName(newLayer), oldLayer, '->', newLayer)();
        newLayer.lifecycle = LIFECYCLE.MATCHED;
        oldLayer.lifecycle = LIFECYCLE.AWAITING_GC;
        newLayer._transferState(oldLayer);
      } else {
        log.log(LOG_PRIORITY_LIFECYCLE_MINOR, 'Matching layer is unchanged ' + newLayer.id)();
        newLayer.lifecycle = LIFECYCLE.MATCHED;
        newLayer.oldProps = newLayer.props;
      }
    }

    // Updates a single layer, cleaning all flags

  }, {
    key: '_updateLayer',
    value: function _updateLayer(layer) {
      log.log(LOG_PRIORITY_LIFECYCLE_MINOR, 'updating ' + layer + ' because: ' + layer.printChangeFlags())();
      var error = null;
      try {
        layer._update();
      } catch (err) {
        log.warn('error during update of ' + layerName(layer), err)();
        // Save first error
        error = err;
      }
      return error;
    }

    // Finalizes a single layer

  }, {
    key: '_finalizeLayer',
    value: function _finalizeLayer(layer) {
      assert(layer.lifecycle !== LIFECYCLE.AWAITING_FINALIZATION);
      layer.lifecycle = LIFECYCLE.AWAITING_FINALIZATION;
      var error = null;
      this.setNeedsRedraw('finalized ' + layerName(layer));
      try {
        layer._finalize();
      } catch (err) {
        log.warn('error during finalization of ' + layerName(layer), err)();
        error = err;
      }
      layer.lifecycle = LIFECYCLE.FINALIZED;
      log.log(LOG_PRIORITY_LIFECYCLE, 'finalizing ' + layerName(layer));
      return error;
    }

    /**
     * Warn if a deck-level mouse event has been specified,
     * but no layers are `pickable`.
     */

  }, {
    key: '_validateEventHandling',
    value: function _validateEventHandling() {
      if (this.onLayerClick || this.onLayerHover) {
        if (this.layers.length && !this.layers.some(function (layer) {
          return layer.props.pickable;
        })) {
          log.warn('You have supplied a top-level input event handler (e.g. `onLayerClick`), ' + 'but none of your layers have set the `pickable` flag.')();
        }
      }
    }

    /**
     * Route click events to layers.
     * `pickLayer` will call the `onClick` prop of any picked layer,
     * and `onLayerClick` is called directly from here
     * with any picking info generated by `pickLayer`.
     * @param {Object} event  An object encapsulating an input event,
     *                        with the following shape:
     *                        {Object: {x, y}} offsetCenter: center of the event
     *                        {Object} srcEvent:             native JS Event object
     */

  }, {
    key: '_onClick',
    value: function _onClick(event) {
      if (!event.offsetCenter) {
        // Do not trigger onHover callbacks when click position is invalid.
        return;
      }
      this._pickAndCallback({
        callback: this._onLayerClick,
        event: event,
        mode: 'click'
      });
    }

    /**
     * Route click events to layers.
     * `pickLayer` will call the `onHover` prop of any picked layer,
     * and `onLayerHover` is called directly from here
     * with any picking info generated by `pickLayer`.
     * @param {Object} event  An object encapsulating an input event,
     *                        with the following shape:
     *                        {Object: {x, y}} offsetCenter: center of the event
     *                        {Object} srcEvent:             native JS Event object
     */

  }, {
    key: '_onPointerMove',
    value: function _onPointerMove(event) {
      if (event.leftButton || event.rightButton) {
        // Do not trigger onHover callbacks if mouse button is down.
        return;
      }
      this._pickAndCallback({
        callback: this._onLayerHover,
        event: event,
        mode: 'hover'
      });
    }
  }, {
    key: '_onPointerLeave',
    value: function _onPointerLeave(event) {
      this.pickObject({
        x: -1,
        y: -1,
        radius: this._pickingRadius,
        mode: 'hover'
      });
    }
  }, {
    key: '_pickAndCallback',
    value: function _pickAndCallback(options) {
      var pos = options.event.offsetCenter;
      var radius = this._pickingRadius;
      var selectedInfos = this.pickObject({ x: pos.x, y: pos.y, radius: radius, mode: options.mode });
      if (options.callback) {
        var firstInfo = selectedInfos.find(function (info) {
          return info.index >= 0;
        }) || null;
        // As per documentation, send null value when no valid object is picked.
        options.callback(firstInfo, selectedInfos, options.event.srcEvent);
      }
    }

    // SEER INTEGRATION

    /**
     * Called upon Seer initialization, manually sends layers data.
     */

  }, {
    key: '_initSeer',
    value: function _initSeer() {
      this.layers.forEach(function (layer) {
        initLayerInSeer(layer);
        updateLayerInSeer(layer);
      });
    }

    /**
     * On Seer property edition, set override and update layers.
     */

  }, {
    key: '_editSeer',
    value: function _editSeer(payload) {
      if (payload.type !== 'edit' || payload.valuePath[0] !== 'props') {
        return;
      }

      setPropOverrides(payload.itemKey, payload.valuePath.slice(1), payload.value);
      var newLayers = this.layers.map(function (layer) {
        return new layer.constructor(layer.props);
      });
      this.updateLayers({ newLayers: newLayers });
    }
  }]);

  return LayerManager;
}();

export default LayerManager;
//# sourceMappingURL=layer-manager.js.map