var colorUtil = require("../utils/color_util");

var dataUtil = require("../utils/data_structure_util");

var Timeline = require("./Timeline");

var _constants = require("../utils/constants");

var mathMin = _constants.mathMin;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/**
 * @class qrenderer.animation.Track
 * There is an one-to-one correspondence between a track and a property of an element.
 * There are many properties on an element, multiple properties can change at the same time during the animation process.
 * Each property naturally becomes a track, all these changing processes will be encapsulated in the Track class.
 * 
 * 
 * Track, 轨道,与元素(Element)上可以用来进行动画的属性一一对应。
 * 元素上存在很多种属性,在动画过程中,可能会有多种属性同时发生变化,
 * 每一种属性天然成为一条动画轨道,把这些轨道上的变化过程封装在 Track 类中。
 * 
 * @author 大漠穷秋 <damoqiongqiu@126.com>
 * @docauthor 大漠穷秋 <damoqiongqiu@126.com>
 */
var Track =
/*#__PURE__*/
function () {
  /**
   * @method constructor Track
   * @param {Object} options 
   */
  function Track(options) {
    _classCallCheck(this, Track);

    this.element = options.element;
    this.path = options.path;
    this.delay = options.delay;
    this.currentValue = null;
    this.isFinished = false;
    this.keyframes = [];
    this.timeline;
  }
  /**
   * @method addKeyFrame
   * Add a key frame.
   * 
   * 
   * 添加关键帧。
   * @param {Object} kf 数据结构为 {time:0,value:0}
   */


  _createClass(Track, [{
    key: "addKeyFrame",
    value: function addKeyFrame(kf) {
      this.keyframes.push(kf);
    }
    /**
     * @method nextFrame
     * Enter the next frame.
     * 
     * 
     * 进入下一帧。
     * @param {Number} time  当前时间
     * @param {Number} delta 时间偏移量
     */

  }, {
    key: "nextFrame",
    value: function nextFrame(time, delta) {
      if (!this.timeline) {
        return;
      }

      var result = this.timeline.nextFrame(time, delta);

      if (dataUtil.isString(result) && result === 'destroy') {
        this.isFinished = true;
      } // console.log(`result=${result}`);


      return result;
    }
    /**
     * @method fire
     * Fire an event.
     * 
     * 
     * 触发事件。
     * @param {String} eventType 
     * @param {Object} arg 
     */

  }, {
    key: "fire",
    value: function fire(eventType, arg) {
      this.timeline.fire(eventType, arg);
    }
    /**
     * @method start
     * Start the animation.
     * 
     * 
     * 开始动画。
     * @param {String} easing 缓动函数名称
     * @param {Boolean} forceAnimate 是否强制开启动画 
     */

  }, {
    key: "start",
    value: function start() {
      var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
      var easing = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
      var forceAnimate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

      var options = this._parseKeyFrames(easing, this.path, loop, forceAnimate); //如果传入的参数不正确,则无法构造实例


      if (!options) {
        return null;
      }

      var timeline = new Timeline(options);
      this.timeline = timeline;
    }
    /**
     * @method stop
     * Stop the animation
     * 
     * 
     * 停止动画。
     * @param {Boolean} forwardToLast 是否快进到最后一帧 
     */

  }, {
    key: "stop",
    value: function stop(forwardToLast) {
      if (forwardToLast) {
        // Move to last frame before stop
        this.timeline && this.timeline.onframe(this.element, 1);
      }
    }
    /**
     * @method pause
     * Pause the animation
     * 
     * 
     * 暂停。
     */

  }, {
    key: "pause",
    value: function pause() {
      this.timeline.pause();
    }
    /**
     * @method resume
     * Resume the animation.
     * 
     * 
     * 重启。
     */

  }, {
    key: "resume",
    value: function resume() {
      this.timeline.resume();
    }
    /**
     * @private
     * @method _parseKeyFrames
     * Parse the keyframes, create the timelines.
     * 
     * 
     * 解析关键帧,创建时间线。
     * @param {String} easing
     * @param {String} path
     * @param {Boolean} forceAnimate 
     */

  }, {
    key: "_parseKeyFrames",
    value: function _parseKeyFrames(easing, path, loop, forceAnimate) {
      var self = this;
      var element = this.element;
      var useSpline = easing === 'spline';
      var kfLength = this.keyframes.length;

      if (!kfLength) {
        return;
      } // Guess data type


      var firstVal = this.keyframes[0].value;
      var isValueArray = dataUtil.isArrayLike(firstVal);
      var isValueColor = false;
      var isValueString = false; // For vertices morphing

      var arrDim = isValueArray ? dataUtil.getArrayDim(this.keyframes) : 0;
      this.keyframes.sort(function (a, b) {
        return a.time - b.time;
      });
      var trackMaxTime = this.keyframes[kfLength - 1].time;
      var kfPercents = [];
      var kfValues = [];
      var prevValue = this.keyframes[0].value;
      var isAllValueEqual = true;

      for (var i = 0; i < kfLength; i++) {
        kfPercents.push(this.keyframes[i].time / trackMaxTime); // Assume value is a color when it is a string

        var value = this.keyframes[i].value; // Check if value is equal, deep check if value is array

        if (!(isValueArray && dataUtil.isArraySame(value, prevValue, arrDim) || !isValueArray && value === prevValue)) {
          isAllValueEqual = false;
        }

        prevValue = value; // Try converting a string to a color array

        if (typeof value === 'string') {
          var colorArray = colorUtil.parse(value);

          if (colorArray) {
            value = colorArray;
            isValueColor = true;
          } else {
            isValueString = true;
          }
        }

        kfValues.push(value);
      }

      if (!forceAnimate && isAllValueEqual) {
        return;
      }

      var lastValue = kfValues[kfLength - 1]; // Polyfill array and NaN value

      for (var _i = 0; _i < kfLength - 1; _i++) {
        if (isValueArray) {
          dataUtil.fillArr(kfValues[_i], lastValue, arrDim);
        } else {
          if (isNaN(kfValues[_i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
            kfValues[_i] = lastValue;
          }
        }
      }

      if (isValueArray) {
        var arr = dataUtil.getAttrByPath(element, path);
        dataUtil.fillArr(arr, lastValue, arrDim);
      } // Cache the key of last frame to speed up when
      // animation playback is sequency


      var lastFrame = 0;
      var lastFramePercent = 0;
      var start;
      var w;
      var p0;
      var p1;
      var p2;
      var p3;
      var rgba = [0, 0, 0, 0];

      var onframe = function onframe(element, percent) {
        // Find the range keyframes
        // kf1-----kf2---------current--------kf3
        // find kf2 and kf3 and do interpolation
        var frame; // In the easing function like elasticOut, percent may less than 0

        if (percent < 0) {
          frame = 0;
        } else if (percent < lastFramePercent) {
          // Start from next key
          // PENDING start from lastFrame ?
          start = mathMin(lastFrame + 1, kfLength - 1);

          for (frame = start; frame >= 0; frame--) {
            if (kfPercents[frame] <= percent) {
              break;
            }
          } // PENDING really need to do this ?


          frame = mathMin(frame, kfLength - 2);
        } else {
          for (frame = lastFrame; frame < kfLength; frame++) {
            if (kfPercents[frame] > percent) {
              break;
            }
          }

          frame = mathMin(frame - 1, kfLength - 2);
        }

        lastFrame = frame;
        lastFramePercent = percent;
        var range = kfPercents[frame + 1] - kfPercents[frame];

        if (range === 0) {
          return;
        } else {
          w = (percent - kfPercents[frame]) / range;
        }

        if (useSpline) {
          p1 = kfValues[frame];
          p0 = kfValues[frame === 0 ? frame : frame - 1];
          p2 = kfValues[frame > kfLength - 2 ? kfLength - 1 : frame + 1];
          p3 = kfValues[frame > kfLength - 3 ? kfLength - 1 : frame + 2];

          if (isValueArray) {
            var _arr = dataUtil.getAttrByPath(element, path);

            dataUtil.catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, _arr, arrDim);
            self.currentValue = _arr;
            element.dirty();
          } else {
            var _value;

            if (isValueColor) {
              _value = dataUtil.catmullRomInterpolateArray(p0, p1, p2, p3, w, w * w, w * w * w, rgba, 1);
              _value = dataUtil.rgba2String(rgba);
            } else if (isValueString) {
              // String is step(0.5)
              _value = dataUtil.interpolateString(p1, p2, w);
            } else {
              _value = dataUtil.catmullRomInterpolate(p0, p1, p2, p3, w, w * w, w * w * w);
            }

            dataUtil.setAttrByPath(element, path, _value);
            self.currentValue = _value;
            element.dirty();
          }
        } else {
          if (isValueArray) {
            var _arr2 = dataUtil.getAttrByPath(element, path);

            dataUtil.interpolateArray(kfValues[frame], kfValues[frame + 1], w, _arr2, arrDim);
            self.currentValue = _arr2;
            element.dirty();
          } else {
            var _value2;

            if (isValueColor) {
              dataUtil.interpolateArray(kfValues[frame], kfValues[frame + 1], w, rgba, 1);
              _value2 = dataUtil.rgba2String(rgba);
            } else if (isValueString) {
              // String is step(0.5)
              _value2 = dataUtil.interpolateString(kfValues[frame], kfValues[frame + 1], w);
            } else {
              _value2 = dataUtil.interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
            }

            dataUtil.setAttrByPath(element, path, _value2);
            self.currentValue = _value2;
            element.dirty();
          }
        }
      };

      var options = {
        element: this.element,
        lifeTime: trackMaxTime,
        loop: loop,
        delay: this.delay,
        onframe: onframe,
        easing: easing && easing !== 'spline' ? easing : 'Linear'
      };
      return options;
    }
  }]);

  return Track;
}();

module.exports = Track;