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

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

var _vector = require("../core/utils/vector");

var vec2Copy = _vector.copy;

/**
 * @class qrenderer.graphic.GraphicStates
 * 
 * States machine for managing graphic states
 * 
 * @docauthor 大漠穷秋 <damoqiongqiu@126.com>
 */
var transitionProperties = ['position', 'rotation', 'scale', 'style', 'shape'];

var TransitionObject = function TransitionObject(opts) {
  if (typeof opts === 'string') {
    this._fromStr(opts);
  } else if (opts) {
    opts.property && (this.property = opts.property);
    opts.duration != null && (this.duration = opts.duration);
    opts.easing && (this.easing = opts.easing);
    opts.delay && (this.delay = opts.delay);
  }

  if (this.property !== '*') {
    this.property = this.property.split(',');
  } else {
    this.property = transitionProperties;
  }
};

TransitionObject.prototype = {
  constructor: TransitionObject,

  /**
   * List of all transition properties. Splitted by comma. Must not have spaces in the string.
   * e.g. 'position,style.color'. '*' will match all the valid properties.
   * @property {String}
   * @default *
   */
  property: '*',

  /**
   * @property {String}
   * @default 'Linear'
   */
  easing: 'Linear',

  /**
   * @property {Number}
   * @default 'number'
   */
  duration: 500,

  /**
   * @property {Number}
   */
  delay: 0,
  _fromStr: function _fromStr(str) {
    var arr = str.split(/\s+/g);
    this.property = arr[0];
    this.duration = +arr[1];
    this.delay = +arr[2];
    this.easing = arr[3];
  }
};
/**
 * @method constructor GraphicStates
 * @property {Number} [qlevel]
 * @property {Number} [z]
 * @property {Array<Number>} {position}
 * @property {Array<Number>|number} {rotation}
 * @property {Array<Number>} {scale}
 * @property {Object} style
 * @property {Function} onenter
 * @property {Function} onleave
 * @property {Function} ontransition
 * @property {Array<IGraphicStateTransition|string>} transition Transition object or a string descriptor like '* 30 0 Linear'
 */

var GraphicStates = function GraphicStates(opts) {
  opts = opts || {};
  this._states = {};
  /**
   * @property _el
   */

  this._el = opts.el;
  this._subStates = [];
  this._transitionAnimationProcess = [];

  if (opts.initialState) {
    this._initialState = opts.initialState;
  }

  var optsStates = opts.states;

  if (optsStates) {
    for (var name in optsStates) {
      if (optsStates.hasOwnProperty(name)) {
        var state = optsStates[name];

        this._addState(name, state);
      }
    }
  }

  this.setState(this._initialState);
};

GraphicStates.prototype = {
  constructor: GraphicStates,

  /**
   * All other state will be extended from initial state
   * @property {String}
   * @private
   */
  _initialState: 'normal',

  /**
   * Current state
   * @property {String}
   * @private
   */
  _currentState: '',
  el: function el() {
    return this._el;
  },
  _addState: function _addState(name, state) {
    this._states[name] = state;

    if (state.transition) {
      state.transition = new TransitionObject(state.transition);
    } // Extend from initial state


    if (name !== this._initialState) {
      this._extendFromInitial(state);
    } else {
      var el = this._el; // setState 的时候自带的 style 和 shape 都会被直接覆盖
      // 所以这边先把自带的 style 和 shape 扩展到初始状态中

      dataUtil.merge(state.style, el.style, false, false);

      if (state.shape) {
        dataUtil.merge(state.shape, el.shape, false, true);
      } else {
        state.shape = dataUtil.clone(el.shape, true);
      }

      for (var _name in this._states) {
        if (this._states.hasOwnProperty(_name)) {
          this._extendFromInitial(this._states[_name]);
        }
      }
    }
  },
  _extendFromInitial: function _extendFromInitial(state) {
    var initialState = this._states[this._initialState];

    if (initialState && state !== initialState) {
      dataUtil.merge(state, initialState, false, true);
    }
  },
  setState: function setState(name, silent) {
    if (name === this._currentState && !this.transiting()) {
      return;
    }

    var state = this._states[name];

    if (state) {
      this._stopTransition();

      if (!silent) {
        var prevState = this._states[this._currentState];

        if (prevState) {
          prevState.onleave && prevState.onleave.call(this);
        }

        state.onenter && state.onenter.call(this);
      }

      this._currentState = name;

      if (this._el) {
        var el = this._el; // Setting attributes

        if (state.qlevel != null) {
          el.qlevel = state.qlevel;
        }

        if (state.z != null) {
          el.z = state.z;
        } // SRT


        state.position && vec2Copy(el.position, state.position);
        state.scale && vec2Copy(el.scale, state.scale);

        if (state.rotation != null) {
          el.rotation = state.rotation;
        } // Style


        if (state.style) {
          var initialState = this._states[this._initialState];
          el.style = new Style();

          if (initialState) {
            el.style.extendStyle(initialState.style, false);
          }

          if ( // Not initial state
          name !== this._initialState // Not copied from initial state in _extendFromInitial method
          && initialState.style !== state.style) {
            el.style.extendStyle(state.style, true);
          }
        }

        if (state.shape) {
          el.shape = dataUtil.clone(state.shape, true);
        }

        el.dirty();
      }
    }

    for (var i = 0; i < this._subStates.length; i++) {
      this._subStates.setState(name);
    }
  },
  getState: function getState() {
    return this._currentState;
  },
  transitionState: function transitionState(target, done) {
    if (target === this._currentState && !this.transiting()) {
      return;
    }

    var state = this._states[target];
    var styleShapeReg = /$[style|shape]\./;
    var self = this; // Animation 去重

    var propPathMap = {};

    if (state) {
      self._stopTransition();

      var el = self._el;

      if (state.transition && el && el.__qr) {
        // El can be animated
        var transitionCfg = state.transition;
        var property = transitionCfg.property;
        var animatingCount = 0;

        var animationDone = function animationDone() {
          animatingCount--;

          if (animatingCount === 0) {
            self.setState(target);
            done && done();
          }
        };

        for (var i = 0; i < property.length; i++) {
          var propName = property[i]; // Animating all the properties in style or shape

          if (propName === 'style' || propName === 'shape') {
            if (state[propName]) {
              for (var key in state[propName]) {
                /* eslint-disable max-depth */
                if (!state[propName].hasOwnProperty(key)) {
                  continue;
                }

                var path = propName + '.' + key;

                if (propPathMap[path]) {
                  continue;
                }
                /* eslint-enable max-depth */


                propPathMap[path] = 1;
                animatingCount += self._animProp(state, propName, key, transitionCfg, animationDone);
              }
            }
          } else {
            if (propPathMap[propName]) {
              continue;
            }

            propPathMap[propName] = 1; // Animating particular property in style or style

            if (propName.match(styleShapeReg)) {
              // remove 'style.', 'shape.' prefix
              var subProp = propName.slice(0, 5);
              propName = propName.slice(6);
              animatingCount += self._animProp(state, subProp, propName, transitionCfg, animationDone);
            } else {
              animatingCount += self._animProp(state, '', propName, transitionCfg, animationDone);
            }
          }
        } // No transition properties


        if (animatingCount === 0) {
          self.setState(target);
          done && done();
        }
      } else {
        self.setState(target);
        done && done();
      }
    }

    var subStates = self._subStates;

    for (var _i = 0; _i < subStates.length; _i++) {
      subStates.transitionState(target);
    }
  },

  /**
   * Do transition animation of particular property
   * @param {Object} state
   * @param {String} subPropKey
   * @param {String} key
   * @param {Object} transitionCfg
   * @param {Function} done
   * @private
   */
  _animProp: function _animProp(state, subPropKey, key, transitionCfg, done) {
    var el = this._el;
    var stateObj = subPropKey ? state[subPropKey] : state;
    var elObj = subPropKey ? el[subPropKey] : el;
    var availableProp = stateObj && key in stateObj && elObj && key in elObj;
    var taps = this._transitionAnimationProcess;

    if (availableProp) {
      var obj = {};

      if (stateObj[key] === elObj[key]) {
        return 0;
      }

      obj[key] = stateObj[key];
      var animationProcess = el.animate(subPropKey).when(transitionCfg.duration, obj).delay(transitionCfg.dealy).done(function () {
        var idx = dataUtil.indexOf(taps, 1);

        if (idx > 0) {
          taps.splice(idx, 1);
        }

        done();
      }).start(transitionCfg.easing);
      taps.push(animationProcess);
      return 1;
    }

    return 0;
  },
  _stopTransition: function _stopTransition() {
    var taps = this._transitionAnimationProcess;

    for (var i = 0; i < taps.length; i++) {
      taps[i].stop();
    }

    taps.length = 0;
  },
  transiting: function transiting() {
    return this._transitionAnimationProcess.length > 0;
  },
  addSubStates: function addSubStates(states) {
    this._subStates.push(states);
  },
  removeSubStates: function removeSubStates(states) {
    var idx = dataUtil.indexOf(this._subStates, states);

    if (idx >= 0) {
      this._subStates.splice(states, 1);
    }
  }
};
var _default = GraphicStates;
module.exports = _default;