'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

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; }; }(); // 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.

/* eslint-disable guard-for-in */


exports.glArrayFromType = glArrayFromType;

var _log = require('../utils/log');

var _log2 = _interopRequireDefault(_log);

var _luma = require('luma.gl');

var _assert = require('../utils/assert');

var _assert2 = _interopRequireDefault(_assert);

var _attributeTransitionManager = require('./attribute-transition-manager');

var _attributeTransitionManager2 = _interopRequireDefault(_attributeTransitionManager);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

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

var LOG_START_END_PRIORITY = 2;
var LOG_DETAIL_PRIORITY = 3;

function noop() {}

/* eslint-disable complexity */
function glArrayFromType(glType) {
  var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
      _ref$clamped = _ref.clamped,
      clamped = _ref$clamped === undefined ? true : _ref$clamped;

  // Sorted in some order of likelihood to reduce amount of comparisons
  switch (glType) {
    case _luma.GL.FLOAT:
      return Float32Array;
    case _luma.GL.UNSIGNED_SHORT:
    case _luma.GL.UNSIGNED_SHORT_5_6_5:
    case _luma.GL.UNSIGNED_SHORT_4_4_4_4:
    case _luma.GL.UNSIGNED_SHORT_5_5_5_1:
      return Uint16Array;
    case _luma.GL.UNSIGNED_INT:
      return Uint32Array;
    case _luma.GL.UNSIGNED_BYTE:
      return clamped ? Uint8ClampedArray : Uint8Array;
    case _luma.GL.BYTE:
      return Int8Array;
    case _luma.GL.SHORT:
      return Int16Array;
    case _luma.GL.INT:
      return Int32Array;
    default:
      throw new Error('Failed to deduce type from array');
  }
}
/* eslint-enable complexity */

// Default loggers
var logFunctions = {
  savedMessages: null,
  timeStart: null,

  onLog: function onLog(_ref2) {
    var level = _ref2.level,
        message = _ref2.message;

    _log2.default.log(level, message)();
  },
  onUpdateStart: function onUpdateStart(_ref3) {
    var level = _ref3.level,
        id = _ref3.id,
        numInstances = _ref3.numInstances;

    logFunctions.savedMessages = [];
    logFunctions.timeStart = new Date();
  },
  onUpdate: function onUpdate(_ref4) {
    var level = _ref4.level,
        message = _ref4.message;

    if (logFunctions.savedMessages) {
      logFunctions.savedMessages.push(message);
    }
  },
  onUpdateEnd: function onUpdateEnd(_ref5) {
    var level = _ref5.level,
        id = _ref5.id,
        numInstances = _ref5.numInstances;

    var timeMs = Math.round(new Date() - logFunctions.timeStart);
    var time = timeMs + 'ms';
    _log2.default.group(level, 'Updated attributes for ' + numInstances + ' instances in ' + id + ' in ' + time, {
      collapsed: true
    })();
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

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

        _log2.default.log(level, message)();
      }
    } catch (err) {
      _didIteratorError = true;
      _iteratorError = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator.return) {
          _iterator.return();
        }
      } finally {
        if (_didIteratorError) {
          throw _iteratorError;
        }
      }
    }

    _log2.default.groupEnd(level, 'Updated attributes for ' + numInstances + ' instances in ' + id + ' in ' + time)();
    logFunctions.savedMessages = null;
  }
};

var AttributeManager = function () {
  _createClass(AttributeManager, null, [{
    key: 'setDefaultLogFunctions',

    /**
     * Sets log functions to help trace or time attribute updates.
     * Default logging uses deck logger.
     *
     * `onLog` is called for each attribute.
     *
     * To enable detailed control of timming and e.g. hierarchical logging,
     * hooks are also provided for update start and end.
     *
     * @param {Object} [opts]
     * @param {String} [opts.onLog=] - called to print
     * @param {String} [opts.onUpdateStart=] - called before update() starts
     * @param {String} [opts.onUpdateEnd=] - called after update() ends
     */
    value: function setDefaultLogFunctions() {
      var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          onLog = _ref6.onLog,
          onUpdateStart = _ref6.onUpdateStart,
          onUpdate = _ref6.onUpdate,
          onUpdateEnd = _ref6.onUpdateEnd;

      if (onLog !== undefined) {
        logFunctions.onLog = onLog || noop;
      }
      if (onUpdateStart !== undefined) {
        logFunctions.onUpdateStart = onUpdateStart || noop;
      }
      if (onUpdate !== undefined) {
        logFunctions.onUpdate = onUpdate || noop;
      }
      if (onUpdateEnd !== undefined) {
        logFunctions.onUpdateEnd = onUpdateEnd || noop;
      }
    }

    /**
     * @classdesc
     * Automated attribute generation and management. Suitable when a set of
     * vertex shader attributes are generated by iteration over a data array,
     * and updates to these attributes are needed either when the data itself
     * changes, or when other data relevant to the calculations change.
     *
     * - First the application registers descriptions of its dynamic vertex
     *   attributes using AttributeManager.add().
     * - Then, when any change that affects attributes is detected by the
     *   application, the app will call AttributeManager.invalidate().
     * - Finally before it renders, it calls AttributeManager.update() to
     *   ensure that attributes are automatically rebuilt if anything has been
     *   invalidated.
     *
     * The application provided update functions describe how attributes
     * should be updated from a data array and are expected to traverse
     * that data array (or iterable) and fill in the attribute's typed array.
     *
     * Note that the attribute manager intentionally does not do advanced
     * change detection, but instead makes it easy to build such detection
     * by offering the ability to "invalidate" each attribute separately.
     *
     * Summary:
     * - keeps track of valid state for each attribute
     * - auto reallocates attributes when needed
     * - auto updates attributes with registered updater functions
     * - allows overriding with application supplied buffers
     *
     * Limitations:
     * - There are currently no provisions for only invalidating a range of
     *   indices in an attribute.
     *
     * @class
     * @param {Object} [props]
     * @param {String} [props.id] - identifier (for debugging)
     */

  }]);

  function AttributeManager(gl) {
    var _ref7 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        _ref7$id = _ref7.id,
        id = _ref7$id === undefined ? 'attribute-manager' : _ref7$id,
        stats = _ref7.stats;

    _classCallCheck(this, AttributeManager);

    this.id = id;
    this.gl = gl;

    this.attributes = {};
    this.updateTriggers = {};
    this.accessors = {};
    this.allocedInstances = -1;
    this.needsRedraw = true;

    this.userData = {};
    this.stats = stats;

    this.attributeTransitionManger = new _attributeTransitionManager2.default(gl, {
      id: id + '-transitions'
    });

    // For debugging sanity, prevent uninitialized members
    Object.seal(this);
  }

  /**
   * Adds attributes
   * Takes a map of attribute descriptor objects
   * - keys are attribute names
   * - values are objects with attribute fields
   *
   * attribute.size - number of elements per object
   * attribute.updater - number of elements
   * attribute.instanced=0 - is this is an instanced attribute (a.k.a. divisor)
   * attribute.noAlloc=false - if this attribute should not be allocated
   *
   * @example
   * attributeManager.add({
   *   positions: {size: 2, update: calculatePositions}
   *   colors: {size: 3, update: calculateColors}
   * });
   *
   * @param {Object} attributes - attribute map (see above)
   * @param {Object} updaters - separate map of update functions (deprecated)
   */


  _createClass(AttributeManager, [{
    key: 'add',
    value: function add(attributes) {
      var updaters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

      this._add(attributes, updaters);
    }

    /**
     * Removes attributes
     * Takes an array of attribute names and delete them from
     * the attribute map if they exists
     *
     * @example
     * attributeManager.remove(['position']);
     *
     * @param {Object} attributeNameArray - attribute name array (see above)
     */

  }, {
    key: 'remove',
    value: function remove(attributeNameArray) {
      for (var i = 0; i < attributeNameArray.length; i++) {
        var name = attributeNameArray[i];
        if (this.attributes[name] !== undefined) {
          delete this.attributes[name];
        }
      }
    }

    /* Marks an attribute for update
     * @param {string} triggerName: attribute or accessor name
     */

  }, {
    key: 'invalidate',
    value: function invalidate(triggerName) {
      var invalidatedAttributes = this._invalidateTrigger(triggerName);

      // For performance tuning
      logFunctions.onLog({
        level: LOG_DETAIL_PRIORITY,
        message: 'invalidated attributes ' + invalidatedAttributes + ' (' + triggerName + ') for ' + this.id,
        id: this.identifier
      });
    }
  }, {
    key: 'invalidateAll',
    value: function invalidateAll() {
      for (var attributeName in this.attributes) {
        this.attributes[attributeName].needsUpdate = true;
      }

      // For performance tuning
      logFunctions.onLog({
        level: LOG_DETAIL_PRIORITY,
        message: 'invalidated all attributes for ' + this.id,
        id: this.identifier
      });
    }
  }, {
    key: '_invalidateTrigger',
    value: function _invalidateTrigger(triggerName) {
      var attributes = this.attributes,
          updateTriggers = this.updateTriggers;

      var invalidatedAttributes = updateTriggers[triggerName];

      if (!invalidatedAttributes) {
        var message = 'invalidating non-existent trigger ' + triggerName + ' for ' + this.id + '\n';
        message += 'Valid triggers: ' + Object.keys(attributes).join(', ');
        _log2.default.warn(message, invalidatedAttributes)();
      } else {
        invalidatedAttributes.forEach(function (name) {
          var attribute = attributes[name];
          if (attribute) {
            attribute.needsUpdate = true;
          }
        });
      }
      return invalidatedAttributes;
    }

    /**
     * Ensure all attribute buffers are updated from props or data.
     *
     * Note: Any preallocated buffers in "buffers" matching registered attribute
     * names will be used. No update will happen in this case.
     * Note: Calls onUpdateStart and onUpdateEnd log callbacks before and after.
     *
     * @param {Object} opts - options
     * @param {Object} opts.data - data (iterable object)
     * @param {Object} opts.numInstances - count of data
     * @param {Object} opts.buffers = {} - pre-allocated buffers
     * @param {Object} opts.props - passed to updaters
     * @param {Object} opts.context - Used as "this" context for updaters
     */

  }, {
    key: 'update',
    value: function update() {
      var _ref8 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          data = _ref8.data,
          numInstances = _ref8.numInstances,
          transitions = _ref8.transitions,
          _ref8$props = _ref8.props,
          props = _ref8$props === undefined ? {} : _ref8$props,
          _ref8$buffers = _ref8.buffers,
          buffers = _ref8$buffers === undefined ? {} : _ref8$buffers,
          _ref8$context = _ref8.context,
          context = _ref8$context === undefined ? {} : _ref8$context,
          _ref8$ignoreUnknownAt = _ref8.ignoreUnknownAttributes,
          ignoreUnknownAttributes = _ref8$ignoreUnknownAt === undefined ? false : _ref8$ignoreUnknownAt;

      // First apply any application provided buffers
      this._checkExternalBuffers({ buffers: buffers, ignoreUnknownAttributes: ignoreUnknownAttributes });
      this._setExternalBuffers(buffers);

      // Only initiate alloc/update (and logging) if actually needed
      if (this._analyzeBuffers({ numInstances: numInstances })) {
        logFunctions.onUpdateStart({ level: LOG_START_END_PRIORITY, id: this.id, numInstances: numInstances });
        if (this.stats) {
          this.stats.timeStart('attribute updates', this.id);
        }
        this._updateBuffers({ numInstances: numInstances, data: data, props: props, context: context });
        if (this.stats) {
          this.stats.timeEnd('attribute updates', this.id);
        }
        logFunctions.onUpdateEnd({ level: LOG_START_END_PRIORITY, id: this.id, numInstances: numInstances });
      }

      this.attributeTransitionManger.update(this.attributes, transitions);
    }

    /**
     * Returns all attribute descriptors
     * Note: Format matches luma.gl Model/Program.setAttributes()
     * @return {Object} attributes - descriptors
     */

  }, {
    key: 'getAttributes',
    value: function getAttributes() {
      var attributes = {};
      for (var attributeName in this.attributes) {
        var attribute = this.attributes[attributeName];
        attributes[attributeName] = attribute.buffer || attribute;
      }

      return attributes;
    }

    /**
     * Returns changed attribute descriptors
     * This indicates which WebGLBuggers need to be updated
     * @return {Object} attributes - descriptors
     */

  }, {
    key: 'getChangedAttributes',
    value: function getChangedAttributes(_ref9) {
      var _ref9$transition = _ref9.transition,
          transition = _ref9$transition === undefined ? false : _ref9$transition,
          _ref9$clearChangedFla = _ref9.clearChangedFlags,
          clearChangedFlags = _ref9$clearChangedFla === undefined ? false : _ref9$clearChangedFla;
      var attributes = this.attributes,
          attributeTransitionManger = this.attributeTransitionManger;


      if (transition) {
        return attributeTransitionManger.getAttributes();
      }

      var changedAttributes = {};
      for (var attributeName in attributes) {
        var attribute = attributes[attributeName];
        if (attribute.changed) {
          attribute.changed = attribute.changed && !clearChangedFlags;

          // Only return non-transition attributes
          if (!attributeTransitionManger.hasAttribute(attributeName)) {
            changedAttributes[attributeName] = attribute.buffer || attribute;
          }
        }
      }
      return changedAttributes;
    }

    /**
     * Returns the redraw flag, optionally clearing it.
     * Redraw flag will be set if any attributes attributes changed since
     * flag was last cleared.
     *
     * @param {Object} [opts]
     * @param {String} [opts.clearRedrawFlags=false] - whether to clear the flag
     * @return {false|String} - reason a redraw is needed.
     */

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

      var redraw = this.needsRedraw;
      this.needsRedraw = this.needsRedraw && !clearRedrawFlags;
      return redraw && this.id;
    }

    /**
     * Sets the redraw flag.
     * @param {Boolean} redraw=true
     * @return {AttributeManager} - for chaining
     */

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

      this.needsRedraw = true;
      return this;
    }

    // DEPRECATED METHODS

    /**
     * Adds attributes
     * @param {Object} attributes - attribute map (see above)
     * @param {Object} updaters - separate map of update functions (deprecated)
     */

  }, {
    key: 'addInstanced',
    value: function addInstanced(attributes) {
      var updaters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

      this._add(attributes, updaters, { instanced: 1 });
    }

    // PROTECTED METHODS - Only to be used by collaborating classes, not by apps

    /**
     * Returns object containing all accessors as keys, with non-null values
     * @return {Object} - accessors object
     */

  }, {
    key: 'getAccessors',
    value: function getAccessors() {
      return this.updateTriggers;
    }

    // PRIVATE METHODS

    // Used to register an attribute

  }, {
    key: '_add',
    value: function _add(attributes) {
      var updaters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

      var _extraProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

      var newAttributes = {};

      for (var attributeName in attributes) {
        // support for separate update function map
        // For now, just copy any attributes from that map into the main map
        // TODO - Attribute maps are a deprecated feature, remove
        if (attributeName in updaters) {
          attributes[attributeName] = Object.assign({}, attributes[attributeName], updaters[attributeName]);
        }

        var attribute = attributes[attributeName];

        var isGeneric = attribute.isGeneric || false;
        var isIndexed = attribute.isIndexed || attribute.elements;
        var size = attribute.elements && 1 || attribute.size;
        var value = attribute.value || null;

        // Initialize the attribute descriptor, with WebGL and metadata fields
        var attributeData = Object.assign({
          // Ensure that fields are present before Object.seal()
          target: undefined,
          buffer: null,
          userData: {} // Reserved for application
        },
        // Metadata
        attribute, {
          // State
          isExternalBuffer: false,
          needsAlloc: false,
          needsUpdate: false,
          changed: false,

          // Luma fields
          isGeneric: isGeneric,
          isIndexed: isIndexed,
          size: size,
          value: value
        }, _extraProps);
        // Sanity - no app fields on our attributes. Use userData instead.
        Object.seal(attributeData);

        // Check all fields and generate helpful error messages
        this._validateAttributeDefinition(attributeName, attributeData);

        // Add to both attributes list (for registration with model)
        newAttributes[attributeName] = attributeData;
      }

      Object.assign(this.attributes, newAttributes);

      this._mapUpdateTriggersToAttributes();
    }

    // build updateTrigger name to attribute name mapping

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

      var triggers = {};

      var _loop = function _loop(attributeName) {
        var attribute = _this.attributes[attributeName];
        var accessor = attribute.accessor;

        // Backards compatibility: allow attribute name to be used as update trigger key

        triggers[attributeName] = [attributeName];

        // use accessor name as update trigger key
        if (typeof accessor === 'string') {
          accessor = [accessor];
        }
        if (Array.isArray(accessor)) {
          accessor.forEach(function (accessorName) {
            if (!triggers[accessorName]) {
              triggers[accessorName] = [];
            }
            triggers[accessorName].push(attributeName);
          });
        }
      };

      for (var attributeName in this.attributes) {
        _loop(attributeName);
      }

      this.updateTriggers = triggers;
    }
  }, {
    key: '_validateAttributeDefinition',
    value: function _validateAttributeDefinition(attributeName, attribute) {
      (0, _assert2.default)(attribute.size >= 1 && attribute.size <= 4, 'Attribute definition for ' + attributeName + ' invalid size');

      // Check that either 'accessor' or 'update' is a valid function
      var hasUpdater = attribute.noAlloc || typeof attribute.update === 'function' || typeof attribute.accessor === 'string';
      if (!hasUpdater) {
        throw new Error('Attribute ' + attributeName + ' missing update or accessor');
      }
    }

    // Checks that any attribute buffers in props are valid
    // Note: This is just to help app catch mistakes

  }, {
    key: '_checkExternalBuffers',
    value: function _checkExternalBuffers() {
      var _ref11 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          _ref11$buffers = _ref11.buffers,
          buffers = _ref11$buffers === undefined ? {} : _ref11$buffers,
          _ref11$ignoreUnknownA = _ref11.ignoreUnknownAttributes,
          ignoreUnknownAttributes = _ref11$ignoreUnknownA === undefined ? false : _ref11$ignoreUnknownA;

      var attributes = this.attributes;

      for (var attributeName in buffers) {
        var attribute = attributes[attributeName];
        if (!attribute && !ignoreUnknownAttributes) {
          throw new Error('Unknown attribute prop ' + attributeName);
        }
        // const buffer = buffers[attributeName];
        // TODO - check buffer type
      }
    }

    // Set the buffers for the supplied attributes
    // Update attribute buffers from any attributes in props
    // Detach any previously set buffers, marking all
    // Attributes for auto allocation
    /* eslint-disable max-statements, max-depth */

  }, {
    key: '_setExternalBuffers',
    value: function _setExternalBuffers(bufferMap) {
      var attributes = this.attributes,
          numInstances = this.numInstances;

      // Copy the refs of any supplied buffers in the props

      for (var attributeName in attributes) {
        var attribute = attributes[attributeName];
        var buffer = bufferMap[attributeName];

        if (buffer) {
          attribute.isExternalBuffer = true;
          attribute.needsUpdate = false;

          if (buffer instanceof _luma.Buffer) {
            attribute.value = null;
            if (attribute.buffer !== buffer) {
              attribute.buffer = buffer;
              attribute.changed = true;
            }
          } else {
            var ArrayType = glArrayFromType(attribute.type || _luma.GL.FLOAT);
            if (!(buffer instanceof ArrayType)) {
              throw new Error('Attribute ' + attributeName + ' must be of type ' + ArrayType.name);
            }
            if (attribute.auto && buffer.length <= numInstances * attribute.size) {
              throw new Error('Attribute prop array must match length and size');
            }

            attribute.buffer = null;
            if (attribute.value !== buffer) {
              attribute.value = buffer;
              attribute.changed = true;
            }
          }
        } else {
          attribute.isExternalBuffer = false;
        }

        this.needsRedraw |= attribute.changed;
      }
    }
    /* eslint-enable max-statements */

    /* Checks that typed arrays for attributes are big enough
     * sets alloc flag if not
     * @return {Boolean} whether any updates are needed
     */

  }, {
    key: '_analyzeBuffers',
    value: function _analyzeBuffers(_ref12) {
      var numInstances = _ref12.numInstances;
      var attributes = this.attributes;

      (0, _assert2.default)(numInstances !== undefined, 'numInstances not defined');

      // Track whether any allocations or updates are needed
      var needsUpdate = false;

      for (var attributeName in attributes) {
        var attribute = attributes[attributeName];
        if (!attribute.isExternalBuffer) {
          // Do we need to reallocate the attribute's typed array?
          var needsAlloc = attribute.value === null || attribute.value.length / attribute.size < numInstances;
          if (needsAlloc && (attribute.update || attribute.accessor)) {
            attribute.needsAlloc = true;
            needsUpdate = true;
          }
          if (attribute.needsUpdate) {
            needsUpdate = true;
          }
        }
      }

      return needsUpdate;
    }

    /**
     * @private
     * Calls update on any buffers that need update
     * TODO? - If app supplied all attributes, no need to iterate over data
     *
     * @param {Object} opts - options
     * @param {Object} opts.data - data (iterable object)
     * @param {Object} opts.numInstances - count of data
     * @param {Object} opts.buffers = {} - pre-allocated buffers
     * @param {Object} opts.props - passed to updaters
     * @param {Object} opts.context - Used as "this" context for updaters
     */
    /* eslint-disable max-statements, complexity */

  }, {
    key: '_updateBuffers',
    value: function _updateBuffers(_ref13) {
      var numInstances = _ref13.numInstances,
          data = _ref13.data,
          props = _ref13.props,
          context = _ref13.context;
      var attributes = this.attributes;

      // Allocate at least one element to ensure a valid buffer

      var allocCount = Math.max(numInstances, 1);

      for (var attributeName in attributes) {
        var attribute = attributes[attributeName];

        // Allocate a new typed array if needed
        if (attribute.needsAlloc) {
          var ArrayType = glArrayFromType(attribute.type || _luma.GL.FLOAT);
          attribute.value = new ArrayType(attribute.size * allocCount);
          logFunctions.onUpdate({
            level: LOG_DETAIL_PRIORITY,
            message: attributeName + ' allocated ' + allocCount,
            id: this.id
          });
          attribute.needsAlloc = false;
          attribute.needsUpdate = true;
        }
      }

      for (var _attributeName in attributes) {
        var _attribute = attributes[_attributeName];
        // Call updater function if needed
        if (_attribute.needsUpdate) {
          this._updateBuffer({ attribute: _attribute, attributeName: _attributeName, numInstances: numInstances, data: data, props: props, context: context });
        }
      }

      this.allocedInstances = allocCount;
    }
  }, {
    key: '_updateBuffer',
    value: function _updateBuffer(_ref14) {
      var attribute = _ref14.attribute,
          attributeName = _ref14.attributeName,
          numInstances = _ref14.numInstances,
          data = _ref14.data,
          props = _ref14.props,
          context = _ref14.context;
      var update = attribute.update,
          accessor = attribute.accessor;


      var timeStart = new Date();
      if (update) {
        // Custom updater - typically for non-instanced layers
        update.call(context, attribute, { data: data, props: props, numInstances: numInstances });
        this._checkAttributeArray(attribute, attributeName);
      } else if (accessor) {
        // Standard updater
        this._updateBufferViaStandardAccessor({ attribute: attribute, data: data, props: props });
        this._checkAttributeArray(attribute, attributeName);
      } else {
        logFunctions.onUpdate({
          level: LOG_DETAIL_PRIORITY,
          message: attributeName + ' missing update function',
          id: this.id
        });
      }
      var timeMs = Math.round(new Date() - timeStart);
      var time = timeMs + 'ms';
      logFunctions.onUpdate({
        level: LOG_DETAIL_PRIORITY,
        message: attributeName + ' updated ' + numInstances + ' ' + time,
        id: this.id
      });

      attribute.needsUpdate = false;
      attribute.changed = true;
      this.needsRedraw = true;
    }
    /* eslint-enable max-statements */

  }, {
    key: '_updateBufferViaStandardAccessor',
    value: function _updateBufferViaStandardAccessor(_ref15) {
      var attribute = _ref15.attribute,
          data = _ref15.data,
          props = _ref15.props;
      var accessor = attribute.accessor,
          value = attribute.value,
          size = attribute.size;

      var accessorFunc = props[accessor];

      (0, _assert2.default)(typeof accessorFunc === 'function', 'accessor "' + accessor + '" is not a function');

      var _attribute$defaultVal = attribute.defaultValue,
          defaultValue = _attribute$defaultVal === undefined ? [0, 0, 0, 0] : _attribute$defaultVal;

      defaultValue = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
      var i = 0;
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

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

          var objectValue = accessorFunc(object);
          objectValue = Array.isArray(objectValue) ? objectValue : [objectValue];
          /* eslint-disable no-fallthrough, default-case */
          switch (size) {
            case 4:
              value[i + 3] = Number.isFinite(objectValue[3]) ? objectValue[3] : defaultValue[3];
            case 3:
              value[i + 2] = Number.isFinite(objectValue[2]) ? objectValue[2] : defaultValue[2];
            case 2:
              value[i + 1] = Number.isFinite(objectValue[1]) ? objectValue[1] : defaultValue[1];
            case 1:
              value[i + 0] = Number.isFinite(objectValue[0]) ? objectValue[0] : defaultValue[0];
          }
          i += size;
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2.return) {
            _iterator2.return();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }
    }
  }, {
    key: '_checkAttributeArray',
    value: function _checkAttributeArray(attribute, attributeName) {
      var value = attribute.value;

      if (value && value.length >= 4) {
        var valid = Number.isFinite(value[0]) && Number.isFinite(value[1]) && Number.isFinite(value[2]) && Number.isFinite(value[3]);
        if (!valid) {
          throw new Error('Illegal attribute generated for ' + attributeName);
        }
      }
    }

    /**
     * Update attribute transition to the current timestamp
     * Returns `true` if any transition is in progress
     */

  }, {
    key: 'updateTransition',
    value: function updateTransition() {
      var attributeTransitionManger = this.attributeTransitionManger;

      var transitionUpdated = attributeTransitionManger.setCurrentTime(Date.now());
      this.needsRedraw = this.needsRedraw || transitionUpdated;
      return transitionUpdated;
    }
  }]);

  return AttributeManager;
}();

exports.default = AttributeManager;
//# sourceMappingURL=attribute-manager.js.map