var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

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 _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.

/* global window */
import { COORDINATE_SYSTEM } from './constants';
import AttributeManager from './attribute-manager';
import { removeLayerInSeer } from './seer-integration';
import { LIFECYCLE } from '../lifecycle/constants';
import { createProps } from '../lifecycle/create-props';
import { diffProps as _diffProps } from '../lifecycle/props';
import { count } from '../utils/count';
import log from '../utils/log';
import { GL, withParameters } from 'luma.gl';
import assert from '../utils/assert';

import LayerState from './layer-state';

var LOG_PRIORITY_UPDATE = 1;
var EMPTY_PROPS = Object.freeze({});
var noop = function noop() {};

var defaultProps = {
  // data: Special handling for null, see below
  dataComparator: null,
  updateTriggers: {}, // Update triggers: a core change detection mechanism in deck.gl
  numInstances: undefined,

  visible: true,
  pickable: false,
  opacity: { type: 'number', min: 0, max: 1, value: 0.8 },

  onHover: noop,
  onClick: noop,

  coordinateSystem: COORDINATE_SYSTEM.LNGLAT,
  coordinateOrigin: [0, 0, 0],

  parameters: {},
  uniforms: {},
  framebuffer: null,

  animation: null, // Passed prop animation functions to evaluate props

  // Offset depth based on layer index to avoid z-fighting.
  // Negative values pull layer towards the camera
  // https://www.opengl.org/archives/resources/faq/technical/polygonoffset.htm
  getPolygonOffset: function getPolygonOffset(_ref) {
    var layerIndex = _ref.layerIndex;
    return [0, -layerIndex * 100];
  },

  // Selection/Highlighting
  highlightedObjectIndex: null,
  autoHighlight: false,
  highlightColor: [0, 0, 128, 128]
};

var counter = 0;

var Layer = function () {
  function Layer() /* ...propObjects */{
    _classCallCheck(this, Layer);

    // Merge supplied props with default props and freeze them.
    /* eslint-disable prefer-spread */
    this.props = createProps.apply(this, arguments);
    /* eslint-enable prefer-spread */

    // Define all members before layer is sealed
    this.id = this.props.id; // The layer's id, used for matching with layers from last render cycle
    this.oldProps = EMPTY_PROPS; // Props from last render used for change detection
    this.count = counter++; // Keep track of how many layer instances you are generating
    this.lifecycle = LIFECYCLE.NO_STATE; // Helps track and debug the life cycle of the layers
    this.parentLayer = null; // reference to the composite layer parent that rendered this layer
    this.context = null; // Will reference layer manager's context, contains state shared by layers
    this.state = null; // Will be set to the shared layer state object during layer matching
    this.internalState = null;

    // Seal the layer
    Object.seal(this);
  }

  // clone this layer with modified props


  _createClass(Layer, [{
    key: 'clone',
    value: function clone(newProps) {
      return new this.constructor(Object.assign({}, this.props, newProps));
    }
  }, {
    key: 'toString',
    value: function toString() {
      var className = this.constructor.layerName || this.constructor.name;
      return className + '({id: \'' + this.props.id + '\'})';
    }
  }, {
    key: 'setState',


    // Public API

    // Updates selected state members and marks the object for redraw
    value: function setState(updateObject) {
      Object.assign(this.state, updateObject);
      this.setNeedsRedraw();
    }

    // Sets the redraw flag for this layer, will trigger a redraw next animation frame

  }, {
    key: 'setNeedsRedraw',
    value: function setNeedsRedraw() {
      var redraw = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

      if (this.internalState) {
        this.internalState.needsRedraw = redraw;
      }
    }

    // This layer needs a deep update
    // TODO - Need to align with existing needsUpdate before uncommenting
    // For now async props will call layerManager directly
    // setNeedsUpdate() {
    //   this.context.layerManager.setNeedsUpdate(String(this));
    // }

    // Checks state of attributes and model

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

      return this._getNeedsRedraw(clearRedrawFlags);
    }

    // Checks if layer attributes needs updating

  }, {
    key: 'needsUpdate',
    value: function needsUpdate() {
      // Call subclass lifecycle method
      return this.shouldUpdateState(this._getUpdateParams());
      // End lifecycle method
    }

    // Returns true if the layer is pickable and visible.

  }, {
    key: 'isPickable',
    value: function isPickable() {
      return this.props.pickable && this.props.visible;
    }

    // Return an array of models used by this layer, can be overriden by layer subclass

  }, {
    key: 'getModels',
    value: function getModels() {
      return this.state && (this.state.models || (this.state.model ? [this.state.model] : []));
    }

    // TODO - Gradually phase out, does not support multi model layers

  }, {
    key: 'getSingleModel',
    value: function getSingleModel() {
      return this.state && this.state.model;
    }
  }, {
    key: 'getAttributeManager',
    value: function getAttributeManager() {
      return this.internalState && this.internalState.attributeManager;
    }

    // Returns the most recent layer that matched to this state
    // (When reacting to an async event, this layer may no longer be the latest)

  }, {
    key: 'getCurrentLayer',
    value: function getCurrentLayer() {
      return this.internalState && this.internalState.layer;
    }

    // Use iteration (the only required capability on data) to get first element
    // deprecated since we are effectively only supporting Arrays

  }, {
    key: 'getFirstObject',
    value: function getFirstObject() {
      var data = this.props.data;
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

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

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

      return null;
    }

    // PROJECTION METHODS

    // Projects a point with current map state (lat, lon, zoom, pitch, bearing)
    // TODO - need to be extended to work with COORDINATE_SYSTEM.METERS,IDENTITY
    // TODO - need to be extended to work with multiple `views`

  }, {
    key: 'project',
    value: function project(lngLat) {
      var viewport = this.context.viewport;

      assert(Array.isArray(lngLat));
      return viewport.project(lngLat);
    }
  }, {
    key: 'unproject',
    value: function unproject(xy) {
      var viewport = this.context.viewport;

      assert(Array.isArray(xy));
      return viewport.unproject(xy);
    }
  }, {
    key: 'projectFlat',
    value: function projectFlat(lngLat) {
      var viewport = this.context.viewport;

      assert(Array.isArray(lngLat));
      return viewport.projectFlat(lngLat);
    }
  }, {
    key: 'unprojectFlat',
    value: function unprojectFlat(xy) {
      var viewport = this.context.viewport;

      assert(Array.isArray(xy));
      return viewport.unprojectFlat(xy);
    }

    // TODO - needs to refer to context for devicePixels setting

  }, {
    key: 'screenToDevicePixels',
    value: function screenToDevicePixels(screenPixels) {
      log.deprecated('screenToDevicePixels', 'DeckGL prop useDevicePixels for conversion')();
      var devicePixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
      return screenPixels * devicePixelRatio;
    }

    // Returns the picking color that doesn't match any subfeature
    // Use if some graphics do not belong to any pickable subfeature
    // @return {Array} - a black color

  }, {
    key: 'nullPickingColor',
    value: function nullPickingColor() {
      return [0, 0, 0];
    }

    // Returns the picking color that doesn't match any subfeature
    // Use if some graphics do not belong to any pickable subfeature

  }, {
    key: 'encodePickingColor',
    value: function encodePickingColor(i) {
      assert((i + 1 >> 24 & 255) === 0, 'index out of picking color range');
      return [i + 1 & 255, i + 1 >> 8 & 255, i + 1 >> 8 >> 8 & 255];
    }

    // Returns the index corresponding to a picking color that doesn't match any subfeature
    // @param {Uint8Array} color - color array to be decoded
    // @return {Array} - the decoded picking color

  }, {
    key: 'decodePickingColor',
    value: function decodePickingColor(color) {
      assert(color instanceof Uint8Array);

      var _color = _slicedToArray(color, 3),
          i1 = _color[0],
          i2 = _color[1],
          i3 = _color[2];
      // 1 was added to seperate from no selection


      var index = i1 + i2 * 256 + i3 * 65536 - 1;
      return index;
    }

    // //////////////////////////////////////////////////
    // LIFECYCLE METHODS, overridden by the layer subclasses

    // Called once to set up the initial state
    // App can create WebGL resources

  }, {
    key: 'initializeState',
    value: function initializeState() {
      throw new Error('Layer ' + this + ' has not defined initializeState');
    }

    // Let's layer control if updateState should be called

  }, {
    key: 'shouldUpdateState',
    value: function shouldUpdateState(_ref3) {
      var oldProps = _ref3.oldProps,
          props = _ref3.props,
          oldContext = _ref3.oldContext,
          context = _ref3.context,
          changeFlags = _ref3.changeFlags;

      return changeFlags.propsOrDataChanged;
    }

    // Default implementation, all attributes will be invalidated and updated
    // when data changes

  }, {
    key: 'updateState',
    value: function updateState(_ref4) {
      var oldProps = _ref4.oldProps,
          props = _ref4.props,
          oldContext = _ref4.oldContext,
          context = _ref4.context,
          changeFlags = _ref4.changeFlags;

      var attributeManager = this.getAttributeManager();
      if (changeFlags.dataChanged && attributeManager) {
        attributeManager.invalidateAll();
      }
    }

    // Called once when layer is no longer matched and state will be discarded
    // App can destroy WebGL resources here

  }, {
    key: 'finalizeState',
    value: function finalizeState() {
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

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

          model.delete();
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2.return) {
            _iterator2.return();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }
    }

    // If state has a model, draw it with supplied uniforms

  }, {
    key: 'draw',
    value: function draw(opts) {
      var _iteratorNormalCompletion3 = true;
      var _didIteratorError3 = false;
      var _iteratorError3 = undefined;

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

          model.draw(opts);
        }
      } catch (err) {
        _didIteratorError3 = true;
        _iteratorError3 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion3 && _iterator3.return) {
            _iterator3.return();
          }
        } finally {
          if (_didIteratorError3) {
            throw _iteratorError3;
          }
        }
      }
    }

    // called to populate the info object that is passed to the event handler
    // @return null to cancel event

  }, {
    key: 'getPickingInfo',
    value: function getPickingInfo(_ref5) {
      var info = _ref5.info,
          mode = _ref5.mode;
      var index = info.index;


      if (index >= 0) {
        // If props.data is an indexable array, get the object
        if (Array.isArray(this.props.data)) {
          info.object = this.props.data[index];
        }
      }

      return info;
    }

    // END LIFECYCLE METHODS
    // //////////////////////////////////////////////////

    // INTERNAL METHODS

    // Default implementation of attribute invalidation, can be redefined

  }, {
    key: 'invalidateAttribute',
    value: function invalidateAttribute() {
      var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'all';
      var diffReason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

      var attributeManager = this.getAttributeManager();
      if (!attributeManager) {
        return;
      }

      if (name === 'all') {
        log.log(LOG_PRIORITY_UPDATE, 'updateTriggers invalidating all attributes: ' + diffReason)();
        attributeManager.invalidateAll();
      } else {
        log.log(LOG_PRIORITY_UPDATE, 'updateTriggers invalidating attribute ' + name + ': ' + diffReason)();
        attributeManager.invalidate(name);
      }
    }

    // Calls attribute manager to update any WebGL attributes

  }, {
    key: 'updateAttributes',
    value: function updateAttributes(props) {
      var attributeManager = this.getAttributeManager();
      if (!attributeManager) {
        return;
      }

      // Figure out data length
      var numInstances = this.getNumInstances(props);

      attributeManager.update({
        data: props.data,
        numInstances: numInstances,
        props: props,
        transitions: props.transitions,
        buffers: props,
        context: this,
        // Don't worry about non-attribute props
        ignoreUnknownAttributes: true
      });

      var model = this.getSingleModel();
      if (model) {
        var changedAttributes = attributeManager.getChangedAttributes({ clearChangedFlags: true });
        model.setAttributes(changedAttributes);
      }
    }

    // Update attribute transition

  }, {
    key: 'updateTransition',
    value: function updateTransition() {
      var model = this.getSingleModel();
      var attributeManager = this.getAttributeManager();
      var isInTransition = attributeManager && attributeManager.updateTransition();

      if (model && isInTransition) {
        model.setAttributes(attributeManager.getChangedAttributes({ transition: true }));
      }
    }
  }, {
    key: 'calculateInstancePickingColors',
    value: function calculateInstancePickingColors(attribute, _ref6) {
      var numInstances = _ref6.numInstances;
      var value = attribute.value,
          size = attribute.size;
      // add 1 to index to seperate from no selection

      for (var i = 0; i < numInstances; i++) {
        var pickingColor = this.encodePickingColor(i);
        value[i * size + 0] = pickingColor[0];
        value[i * size + 1] = pickingColor[1];
        value[i * size + 2] = pickingColor[2];
      }
    }

    // Deduces numer of instances. Intention is to support:
    // - Explicit setting of numInstances
    // - Auto-deduction for ES6 containers that define a size member
    // - Auto-deduction for Classic Arrays via the built-in length attribute
    // - Auto-deduction via arrays

  }, {
    key: 'getNumInstances',
    value: function getNumInstances(props) {
      props = props || this.props;

      // First check if the layer has set its own value
      if (this.state && this.state.numInstances !== undefined) {
        return this.state.numInstances;
      }

      // Check if app has provided an explicit value
      if (props.numInstances !== undefined) {
        return props.numInstances;
      }

      // Use container library to get a count for any ES6 container or object
      var data = this.props.data;

      return count(data);
    }

    // LAYER MANAGER API
    // Should only be called by the deck.gl LayerManager class

    // Called by layer manager when a new layer is found
    /* eslint-disable max-statements */

  }, {
    key: '_initialize',
    value: function _initialize() {
      assert(arguments.length === 0);
      assert(this.context.gl);

      this._initState();

      // Call subclass lifecycle methods
      this.initializeState(this.context);
      // End subclass lifecycle methods

      // TODO deprecated, for backwards compatibility with older layers
      // in case layer resets state
      this.state.attributeManager = this.getAttributeManager();

      // initializeState callback tends to clear state
      this.setChangeFlags({ dataChanged: true, propsChanged: true, viewportChanged: true });

      this._updateState(this._getUpdateParams());

      var model = this.getSingleModel();
      if (model) {
        model.id = this.props.id;
        model.program.id = this.props.id + '-program';
        model.geometry.id = this.props.id + '-geometry';
        model.setAttributes(this.getAttributeManager().getAttributes());
      }

      // Last but not least, update any sublayers
      if (this.isComposite) {
        this._renderLayers();
      }

      this.clearChangeFlags();
    }

    // Called by layer manager
    // if this layer is new (not matched with an existing layer) oldProps will be empty object

  }, {
    key: '_update',
    value: function _update() {
      assert(arguments.length === 0);

      // Call subclass lifecycle method
      var stateNeedsUpdate = this.needsUpdate();
      // End lifecycle method

      var updateParams = this._getUpdateParams();

      if (stateNeedsUpdate) {
        this._updateState(updateParams);
      }

      // Render or update previously rendered sublayers
      if (this.isComposite) {
        this._renderLayers(stateNeedsUpdate);
      }

      this.clearChangeFlags();
      // Release old props for GC once update is complete
      this.oldProps = EMPTY_PROPS;
    }
    /* eslint-enable max-statements */

  }, {
    key: '_updateState',
    value: function _updateState(updateParams) {
      // Call subclass lifecycle methods
      this.updateState(updateParams);
      // End subclass lifecycle methods

      // Add any subclass attributes
      this.updateAttributes(this.props);
      this._updateBaseUniforms();
      this._updateModuleSettings();

      // Note: Automatic instance count update only works for single layers
      if (this.state.model) {
        this.state.model.setInstanceCount(this.getNumInstances());
      }
    }

    // Called by manager when layer is about to be disposed
    // Note: not guaranteed to be called on application shutdown

  }, {
    key: '_finalize',
    value: function _finalize() {
      assert(this.internalState && this.state);
      assert(arguments.length === 0);
      // Call subclass lifecycle method
      this.finalizeState(this.context);
      // End lifecycle method
      removeLayerInSeer(this.id);
    }

    // Calculates uniforms

  }, {
    key: 'drawLayer',
    value: function drawLayer(_ref7) {
      var _this = this;

      var _ref7$moduleParameter = _ref7.moduleParameters,
          moduleParameters = _ref7$moduleParameter === undefined ? null : _ref7$moduleParameter,
          _ref7$uniforms = _ref7.uniforms,
          uniforms = _ref7$uniforms === undefined ? {} : _ref7$uniforms,
          _ref7$parameters = _ref7.parameters,
          parameters = _ref7$parameters === undefined ? {} : _ref7$parameters;

      if (!uniforms.picking_uActive) {
        this.updateTransition();
      }

      // TODO/ib - hack move to luma Model.draw
      if (moduleParameters) {
        var _iteratorNormalCompletion4 = true;
        var _didIteratorError4 = false;
        var _iteratorError4 = undefined;

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

            model.updateModuleSettings(moduleParameters);
          }
        } catch (err) {
          _didIteratorError4 = true;
          _iteratorError4 = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion4 && _iterator4.return) {
              _iterator4.return();
            }
          } finally {
            if (_didIteratorError4) {
              throw _iteratorError4;
            }
          }
        }
      }

      // Apply polygon offset to avoid z-fighting
      // TODO - move to draw-layers
      var getPolygonOffset = this.props.getPolygonOffset;

      var offsets = getPolygonOffset && getPolygonOffset(uniforms) || [0, 0];
      parameters.polygonOffset = offsets;

      // Call subclass lifecycle method
      withParameters(this.context.gl, parameters, function () {
        _this.draw({ moduleParameters: moduleParameters, uniforms: uniforms, parameters: parameters, context: _this.context });
      });
      // End lifecycle method
    }

    // {uniforms = {}, ...opts}

  }, {
    key: 'pickLayer',
    value: function pickLayer(opts) {
      // Call subclass lifecycle method
      return this.getPickingInfo(opts);
      // End lifecycle method
    }

    // Helper methods

  }, {
    key: 'getChangeFlags',
    value: function getChangeFlags() {
      return this.internalState.changeFlags;
    }

    // Dirty some change flags, will be handled by updateLayer
    /* eslint-disable complexity */

  }, {
    key: 'setChangeFlags',
    value: function setChangeFlags(flags) {
      var _this2 = this;

      this.internalState.changeFlags = this.internalState.changeFlags || {};
      var changeFlags = this.internalState.changeFlags;

      // Update primary flags
      if (flags.dataChanged && !changeFlags.dataChanged) {
        changeFlags.dataChanged = flags.dataChanged;
        log.log(LOG_PRIORITY_UPDATE + 1, function () {
          return 'dataChanged: ' + flags.dataChanged + ' in ' + _this2.id;
        })();
      }
      if (flags.updateTriggersChanged && !changeFlags.updateTriggersChanged) {
        changeFlags.updateTriggersChanged = changeFlags.updateTriggersChanged && flags.updateTriggersChanged ? Object.assign({}, flags.updateTriggersChanged, changeFlags.updateTriggersChanged) : flags.updateTriggersChanged || changeFlags.updateTriggersChanged;
        log.log(LOG_PRIORITY_UPDATE + 1, function () {
          return 'updateTriggersChanged: ' + (Object.keys(flags.updateTriggersChanged).join(', ') + ' in ' + _this2.id);
        })();
      }
      if (flags.propsChanged && !changeFlags.propsChanged) {
        changeFlags.propsChanged = flags.propsChanged;
        log.log(LOG_PRIORITY_UPDATE + 1, function () {
          return 'propsChanged: ' + flags.propsChanged + ' in ' + _this2.id;
        })();
      }
      if (flags.viewportChanged && !changeFlags.viewportChanged) {
        changeFlags.viewportChanged = flags.viewportChanged;
        log.log(LOG_PRIORITY_UPDATE + 2, function () {
          return 'viewportChanged: ' + flags.viewportChanged + ' in ' + _this2.id;
        })();
      }

      // Update composite flags
      var propsOrDataChanged = flags.dataChanged || flags.updateTriggersChanged || flags.propsChanged;
      changeFlags.propsOrDataChanged = changeFlags.propsOrDataChanged || propsOrDataChanged;
      changeFlags.somethingChanged = changeFlags.somethingChanged || propsOrDataChanged || flags.viewportChanged;
    }
    /* eslint-enable complexity */

    // Clear all changeFlags, typically after an update

  }, {
    key: 'clearChangeFlags',
    value: function clearChangeFlags() {
      this.internalState.changeFlags = {
        // Primary changeFlags, can be strings stating reason for change
        dataChanged: false,
        propsChanged: false,
        updateTriggersChanged: false,
        viewportChanged: false,

        // Derived changeFlags
        propsOrDataChanged: false,
        somethingChanged: false
      };
    }
  }, {
    key: 'printChangeFlags',
    value: function printChangeFlags() {
      var flags = this.internalState.changeFlags;
      return '' + (flags.dataChanged ? 'data ' : '') + (flags.propsChanged ? 'props ' : '') + (flags.updateTriggersChanged ? 'triggers ' : '') + (flags.viewportChanged ? 'viewport' : '');
    }

    // Compares the layers props with old props from a matched older layer
    // and extracts change flags that describe what has change so that state
    // can be update correctly with minimal effort
    // TODO - arguments for testing only

  }, {
    key: 'diffProps',
    value: function diffProps() {
      var newProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;
      var oldProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.oldProps;

      var changeFlags = _diffProps(newProps, oldProps);

      // iterate over changedTriggers
      if (changeFlags.updateTriggersChanged) {
        for (var key in changeFlags.updateTriggersChanged) {
          if (changeFlags.updateTriggersChanged[key]) {
            this._activeUpdateTrigger(key);
          }
        }
      }

      return this.setChangeFlags(changeFlags);
    }

    // PRIVATE METHODS

  }, {
    key: '_getUpdateParams',
    value: function _getUpdateParams() {
      return {
        props: this.props,
        oldProps: this.oldProps,
        context: this.context,
        oldContext: this.oldContext || {},
        changeFlags: this.internalState.changeFlags
      };
    }

    // Checks state of attributes and model

  }, {
    key: '_getNeedsRedraw',
    value: function _getNeedsRedraw(clearRedrawFlags) {
      // this method may be called by the render loop as soon a the layer
      // has been created, so guard against uninitialized state
      if (!this.internalState) {
        return false;
      }

      var redraw = false;
      redraw = redraw || this.internalState.needsRedraw && this.id;
      this.internalState.needsRedraw = this.internalState.needsRedraw && !clearRedrawFlags;

      // TODO - is attribute manager needed? - Model should be enough.
      var attributeManager = this.getAttributeManager();
      var attributeManagerNeedsRedraw = attributeManager && attributeManager.getNeedsRedraw({ clearRedrawFlags: clearRedrawFlags });
      redraw = redraw || attributeManagerNeedsRedraw;

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

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

          var modelNeedsRedraw = model.getNeedsRedraw({ clearRedrawFlags: clearRedrawFlags });
          if (modelNeedsRedraw && typeof modelNeedsRedraw !== 'string') {
            modelNeedsRedraw = 'model ' + model.id;
          }
          redraw = redraw || modelNeedsRedraw;
        }
      } catch (err) {
        _didIteratorError5 = true;
        _iteratorError5 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion5 && _iterator5.return) {
            _iterator5.return();
          }
        } finally {
          if (_didIteratorError5) {
            throw _iteratorError5;
          }
        }
      }

      return redraw;
    }
  }, {
    key: '_initState',
    value: function _initState() {
      assert(!this.internalState && !this.state);

      var attributeManager = new AttributeManager(this.context.gl, {
        id: this.props.id,
        stats: this.context.stats
      });

      // All instanced layers get instancePickingColors attribute by default
      // Their shaders can use it to render a picking scene
      // TODO - this slightly slows down non instanced layers
      attributeManager.addInstanced({
        instancePickingColors: {
          type: GL.UNSIGNED_BYTE,
          size: 3,
          update: this.calculateInstancePickingColors
        }
      });

      this.internalState = new LayerState({
        attributeManager: attributeManager
      });
      this.state = {};
      // TODO deprecated, for backwards compatibility with older layers
      this.state.attributeManager = this.getAttributeManager();
    }

    // Called by layer manager to transfer state from an old layer

  }, {
    key: '_transferState',
    value: function _transferState(oldLayer) {
      var state = oldLayer.state,
          internalState = oldLayer.internalState,
          props = oldLayer.props;

      assert(state && internalState);

      // Move state
      state.layer = this;
      this.state = state;
      this.internalState = internalState;
      // Note: We keep the state ref on old layers to support async actions
      // oldLayer.state = null;

      // Keep a temporary ref to the old props, for prop comparison
      this.oldProps = props;

      // Update model layer reference
      var _iteratorNormalCompletion6 = true;
      var _didIteratorError6 = false;
      var _iteratorError6 = undefined;

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

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

      this.diffProps();
    }

    // Operate on each changed triggers, will be called when an updateTrigger changes

  }, {
    key: '_activeUpdateTrigger',
    value: function _activeUpdateTrigger(propName) {
      this.invalidateAttribute(propName);
    }

    //  Helper to check that required props are supplied

  }, {
    key: '_checkRequiredProp',
    value: function _checkRequiredProp(propertyName, condition) {
      var value = this.props[propertyName];
      if (value === undefined) {
        throw new Error('Property ' + propertyName + ' undefined in layer ' + this);
      }
      if (condition && !condition(value)) {
        throw new Error('Bad property ' + propertyName + ' in layer ' + this);
      }
    }
  }, {
    key: '_updateBaseUniforms',
    value: function _updateBaseUniforms() {
      var uniforms = {
        // apply gamma to opacity to make it visually "linear"
        opacity: Math.pow(this.props.opacity, 1 / 2.2),
        ONE: 1.0
      };
      var _iteratorNormalCompletion7 = true;
      var _didIteratorError7 = false;
      var _iteratorError7 = undefined;

      try {
        for (var _iterator7 = this.getModels()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
          var model = _step7.value;

          model.setUniforms(uniforms);
        }

        // TODO - set needsRedraw on the model(s)?
      } catch (err) {
        _didIteratorError7 = true;
        _iteratorError7 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion7 && _iterator7.return) {
            _iterator7.return();
          }
        } finally {
          if (_didIteratorError7) {
            throw _iteratorError7;
          }
        }
      }

      this.setNeedsRedraw();
    }
  }, {
    key: '_updateModuleSettings',
    value: function _updateModuleSettings() {
      var settings = {
        pickingHighlightColor: this.props.highlightColor
      };
      var _iteratorNormalCompletion8 = true;
      var _didIteratorError8 = false;
      var _iteratorError8 = undefined;

      try {
        for (var _iterator8 = this.getModels()[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
          var model = _step8.value;

          model.updateModuleSettings(settings);
        }
      } catch (err) {
        _didIteratorError8 = true;
        _iteratorError8 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion8 && _iterator8.return) {
            _iterator8.return();
          }
        } finally {
          if (_didIteratorError8) {
            throw _iteratorError8;
          }
        }
      }
    }

    // DEPRECATED METHODS

    // Updates selected state members and marks the object for redraw

  }, {
    key: 'setUniforms',
    value: function setUniforms(uniformMap) {
      var _iteratorNormalCompletion9 = true;
      var _didIteratorError9 = false;
      var _iteratorError9 = undefined;

      try {
        for (var _iterator9 = this.getModels()[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {
          var model = _step9.value;

          model.setUniforms(uniformMap);
        }

        // TODO - set needsRedraw on the model(s)?
      } catch (err) {
        _didIteratorError9 = true;
        _iteratorError9 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion9 && _iterator9.return) {
            _iterator9.return();
          }
        } finally {
          if (_didIteratorError9) {
            throw _iteratorError9;
          }
        }
      }

      this.setNeedsRedraw();
      log.deprecated('layer.setUniforms', 'model.setUniforms')();
    }
  }, {
    key: 'stats',
    get: function get() {
      return this.internalState.stats;
    }
  }]);

  return Layer;
}();

export default Layer;


Layer.layerName = 'Layer';
Layer.defaultProps = defaultProps;
//# sourceMappingURL=layer.js.map