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

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

var _attributeTransitionUtils = require('./attribute-transition-utils');

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

var _log2 = _interopRequireDefault(_log);

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

var _assert2 = _interopRequireDefault(_assert);

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 Transform = _luma.experimental.Transform;


var TRANSITION_STATE = {
  NONE: 0,
  PENDING: 1,
  STARTED: 2,
  ENDED: 3
};

var noop = function noop() {};

var AttributeTransitionManager = function () {
  function AttributeTransitionManager(gl, _ref) {
    var id = _ref.id;

    _classCallCheck(this, AttributeTransitionManager);

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

    this.attributeTransitions = {};
    this.needsRedraw = false;
    this.transform = null;

    if (Transform.isSupported(gl)) {
      this.isSupported = true;
    } else {
      _log2.default.warn('WebGL2 not supported by this browser. Transition animation is disabled.')();
    }
  }

  /* Public methods */

  // Called when attribute manager updates
  // Check the latest attributes for updates.


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

      this.opts = opts;

      if (!this.isSupported) {
        return;
      }

      var needsNewModel = false;
      var attributeTransitions = this.attributeTransitions;

      var changedTransitions = {};

      for (var attributeName in attributes) {
        var transition = this._updateAttribute(attributeName, attributes[attributeName]);

        if (transition) {
          if (!attributeTransitions[attributeName]) {
            // New animated attribute is added
            attributeTransitions[attributeName] = transition;
            needsNewModel = true;
          }
          changedTransitions[attributeName] = transition;
        }
      }

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

        if (!attribute || !attribute.transition) {
          // Animated attribute has been removed
          delete attributeTransitions[_attributeName];
          needsNewModel = true;
        }
      }

      if (needsNewModel) {
        this._createModel();
      } else if (this.transform) {
        var _getBuffers = (0, _attributeTransitionUtils.getBuffers)(changedTransitions),
            sourceBuffers = _getBuffers.sourceBuffers,
            destinationBuffers = _getBuffers.destinationBuffers,
            elementCount = _getBuffers.elementCount;

        this.transform.elementCount = Math.min(this.transform.elementCount, elementCount);
        this.transform.update({
          sourceBuffers: sourceBuffers,
          destinationBuffers: destinationBuffers
        });
      }
    }

    // Returns `true` if attribute is transition-enabled

  }, {
    key: 'hasAttribute',
    value: function hasAttribute(attributeName) {
      return attributeName in this.attributeTransitions;
    }

    // Get all the animated attributes

  }, {
    key: 'getAttributes',
    value: function getAttributes() {
      var animatedAttributes = {};

      for (var attributeName in this.attributeTransitions) {
        var transition = this.attributeTransitions[attributeName];

        if (transition.buffer) {
          animatedAttributes[attributeName] = transition.buffer;
        }
      }

      return animatedAttributes;
    }

    /* eslint-disable max-statements */
    // Called every render cycle, run transform feedback
    // Returns `true` if anything changes

  }, {
    key: 'setCurrentTime',
    value: function setCurrentTime(currentTime) {
      if (!this.transform) {
        return false;
      }

      var uniforms = {};

      var needsRedraw = this.needsRedraw;
      this.needsRedraw = false;

      for (var attributeName in this.attributeTransitions) {
        var transition = this.attributeTransitions[attributeName];

        var time = 1;
        if (transition.state === TRANSITION_STATE.PENDING) {
          transition.startTime = currentTime;
          transition.state = TRANSITION_STATE.STARTED;
          transition.onStart(transition);
        }

        if (transition.state === TRANSITION_STATE.STARTED) {
          time = (currentTime - transition.startTime) / transition.duration;
          if (time >= 1) {
            time = 1;
            transition.state = TRANSITION_STATE.ENDED;
            transition.onEnd(transition);
          }
          needsRedraw = true;
        }
        uniforms[transition.name + 'Time'] = transition.easing(time);
      }

      if (needsRedraw) {
        this.transform.run({ uniforms: uniforms });
      }

      return needsRedraw;
    }
    /* eslint-enable max-statements */

    /* Private methods */

    // Check an attributes for updates
    // Returns a transition object if a new transition is triggered.

  }, {
    key: '_updateAttribute',
    value: function _updateAttribute(attributeName, attribute) {
      var settings = this._getTransitionSettings(attribute);

      if (settings) {
        var hasChanged = void 0;
        var transition = this.attributeTransitions[attributeName];
        if (transition) {
          hasChanged = attribute.changed;
        } else {
          // New animated attributes have been added
          transition = { name: attributeName, attribute: attribute };
          hasChanged = true;
        }

        if (hasChanged) {
          this._triggerTransition(transition, settings);
          return transition;
        }
      }

      return null;
    }

    // Create a model for the transform feedback

  }, {
    key: '_createModel',
    value: function _createModel() {
      if (this.transform) {
        this.transform.delete();
      }
      this.transform = new Transform(this.gl, Object.assign({}, (0, _attributeTransitionUtils.getBuffers)(this.attributeTransitions), (0, _attributeTransitionUtils.getShaders)(this.attributeTransitions)));
    }

    // get current values of an attribute, clipped/padded to the size of the new buffer

  }, {
    key: '_getCurrentAttributeState',
    value: function _getCurrentAttributeState(transition) {
      var attribute = transition.attribute,
          buffer = transition.buffer;
      var value = attribute.value,
          type = attribute.type,
          size = attribute.size;


      if (buffer) {
        // If new buffer is bigger than old buffer, back fill with destination values
        var oldBufferData = new Float32Array(value);
        buffer.getData({ dstData: oldBufferData });
        // Hack/Xiaoji: WebGL2 throws error if TransformFeedback does not render to
        // a buffer of type Float32Array.
        // Therefore we need to read data as a Float32Array then re-cast to attribute type
        if (!(value instanceof Float32Array)) {
          oldBufferData = new value.constructor(oldBufferData);
        }
        return { size: size, type: type, value: oldBufferData };
      }
      return { size: size, type: type, value: value };
    }

    // Returns transition settings object if transition is enabled, otherwise `null`

  }, {
    key: '_getTransitionSettings',
    value: function _getTransitionSettings(attribute) {
      var opts = this.opts;
      var transition = attribute.transition,
          accessor = attribute.accessor;


      if (!transition) {
        return null;
      }

      return Array.isArray(accessor) ? accessor.map(function (a) {
        return opts[a];
      }).find(Boolean) : opts[accessor];
    }

    // Normalizes transition settings object, merge with default settings

  }, {
    key: '_normalizeTransitionSettings',
    value: function _normalizeTransitionSettings(settings) {
      // Shorthand: use duration instead of parameter object
      if (Number.isFinite(settings)) {
        settings = { duration: settings };
      }

      // Check if settings is valid
      (0, _assert2.default)(settings && settings.duration > 0);

      return {
        duration: settings.duration,
        easing: settings.easing || function (t) {
          return t;
        },
        onStart: settings.onStart || noop,
        onEnd: settings.onEnd || noop,
        onInterrupt: settings.onInterrupt || noop
      };
    }

    // Start a new transition using the current settings
    // Updates transition state and from/to buffer

  }, {
    key: '_triggerTransition',
    value: function _triggerTransition(transition, settings) {
      this.needsRedraw = true;

      var attribute = transition.attribute,
          buffer = transition.buffer;
      var value = attribute.value,
          size = attribute.size;


      var transitionSettings = this._normalizeTransitionSettings(settings);

      var needsNewBuffer = !buffer || transition.bufferSize < value.length;

      // Attribute descriptor to transition from
      // _getCurrentAttributeState must be called before the current buffer is deleted
      var fromState = this._getCurrentAttributeState(transition);

      // Attribute descriptor to transition to
      // Pre-converting to buffer to reuse in the case where no transition is needed
      var toState = new _luma.Buffer(this.gl, { size: size, data: value });

      if (needsNewBuffer) {
        if (buffer) {
          buffer.delete();
        }

        transition.buffer = new _luma.Buffer(this.gl, {
          size: size,
          instanced: attribute.instanced,
          // WebGL2 throws error if `value` is not cast to Float32Array:
          // `transformfeedback buffers : buffer or buffer range not large enough`
          data: new Float32Array(value.length),
          usage: _luma.GL.DYNAMIC_COPY
        });
        transition.bufferSize = value.length;
      }

      Object.assign(transition, transitionSettings);
      if (transition.fromState) {
        transition.fromState.delete();
      }
      transition.fromState = new _luma.Buffer(this.gl, Object.assign({}, fromState, {
        data: fromState.value
      }));
      if (transition.toState) {
        transition.toState.delete();
      }
      transition.toState = toState;

      // Reset transition state
      if (transition.state === TRANSITION_STATE.STARTED) {
        transition.onInterrupt(transition);
      }
      transition.state = TRANSITION_STATE.PENDING;
    }
  }]);

  return AttributeTransitionManager;
}();

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