(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
      (factory((global.Flip = global.Flip || {})));
}(this, (function (exports) {
  'use strict';

  const hasOwnProperty = Object.prototype.hasOwnProperty;

  function forEach(object, callback, thisObj) {
    if (isObj(object)) {
      if (thisObj === void 0) {
        thisObj = object;
      }
      if (object instanceof Array || 'length' in object) {
        let arr = Array.prototype.slice.call(object);
        arr.forEach(callback, thisObj);
      } else {
        for (let name in object) {
          if (hasOwnProperty.call(object, name)) {
            callback.call(thisObj, object[name], name);
          }
        }
      }
    }
    return object;
  }

  function isFunc(any) {
    return typeof any === 'function';
  }

  function isObj(any) {
    return typeof any === 'object' && any;
  }

  function isStr(any) {
    return typeof any === "string";
  }

  /**
   * Created by brian on 8/21/16.
   */
  function arrAdd(array, item) {
    if (array.indexOf(item) == -1) {
      array.push(item);
      return true;
    }
    return false;
  }

  function arrRemove(array, item) {
    var i = array.indexOf(item);
    if (i >= 0) {
      array.splice(i, 1);
      return true;
    }
    return false;
  }

  /**
   * Created by brian on 8/21/16.
   */
  class EventEmitter {

    on(eventName, handler, once = false) {
      addEventListener(this, eventName, handler, once);
      return this;
    }

    once(eventName, handler) {
      return this.on(eventName, handler, true);
    }

    off(eventName, handler) {
      removeEventListener(this, eventName, handler);
      return this;
    }

    emit(eventName, args, thisObj) {
      emitEvent(this, eventName, args, thisObj);
      return this;
    }

  }
  function addEventListener(obj, evtName, handler, once) {
    if (typeof evtName == "string" && evtName && isFunc(handler)) {
      var cbs, hs;
      if (!obj.hasOwnProperty('__callbacks')) {
        obj.__callbacks = {};
      }
      cbs = obj.__callbacks;
      if (!(hs = cbs[evtName])) {
        hs = cbs[evtName] = [];
      }
      return arrAdd(hs, once ? warpOnceFunc(handler) : handler);
    }
    return false;
  }

  function emitEvent(obj, evtName, argArray, thisObj) {
    var callbacks, handlers;
    if (!obj.hasOwnProperty('__callbacks') || !(handlers = (callbacks = obj.__callbacks)[evtName])) {
      return false;
    }
    if (!argArray) {
      argArray = [];
    } else if (!(argArray instanceof Array)) {
      argArray = [argArray];
    }
    if (thisObj === undefined) {
      thisObj = obj;
    }
    return callbacks[evtName] = handlers.reduce(function (next, call) {
      if (isFunc(call) && call.apply(thisObj, argArray) !== -1) {
        next.push(call);
      }
      return next;
    }, []);
  }

  function removeEventListener(obj, evtName, handler) {
    var cbs, hs, i;
    if (evtName === undefined) {
      obj.__callbacks = {};
    } else if ((cbs = obj.__callbacks) && (hs = cbs[evtName]) && hs) {
      if (handler) {
        if ((i = hs.indexOf(handler)) > -1) {
          hs[i] = null;
        }
      } else {
        cbs[evtName] = [];
      }
    }
    return obj;
  }

  function warpOnceFunc(func) {
    return function () {
      func.apply(this, arguments);
      return -1;
    };
  }

  /**
   * Created by brian on 02/12/2016.
   */
  const INVALID_KEY = createPrivateMemberName('isInvalid');

  const DISABLED_KEY$1 = createPrivateMemberName('disabled');

  function setDisposed(target) {
    target[DISABLED_KEY$1] = true;
  }

  function getDisposed(target) {
    return target ? target[DISABLED_KEY$1] : false;
  }

  function createPrivateMemberName(name) {
    return isFunc(window.Symbol) ? Symbol(name) : `_${ name }`;
  }

  function isInvalid(obj) {
    return obj && obj[INVALID_KEY];
  }

  function setInvalid(obj, invalid) {
    if (obj) {
      obj[INVALID_KEY] = invalid;
    }
  }

  /**
   * Created by brian on 8/21/16.
   */
  const DISABLED_KEY = createPrivateMemberName('disabled');
  class PureRender {
    update(e) {
    }

    render(e) {
    }

    dispose(e) {
    }
  }
  /**
   * @memberOf Flip
   */
  class Render extends EventEmitter {

    get parent() {
      return this._parent;
    }

    set parent(parent) {
      if (!parent || !this._parent) {
        this._parent = parent;
      } else {
        throw Error('invalid parent');
      }
    }

    update(e) {
    }

    invalid() {
      if (this.parent) {
        this.parent.invalid();
      }
      setInvalid(this, true);
    }

    render(e) {
      setInvalid(this, false);
    }

    dispose(e) {
      setDisposed(this);
    }

    removeFromParent() {
      if (this.parent) {
        return this.parent.removeChild(this);
      }
      return false;
    }

    get disabled() {
      return this[DISABLED_KEY];
    }

    get disposed() {
      return getDisposed(this);
    }

    set disabled(v) {
      if (this.disabled != v) {
        this[DISABLED_KEY] = v;
        this.invalid();
      }
    }
  }
  function isRender(obj) {
    return obj instanceof PureRender || obj instanceof Render;
  }

  /**
   * Created by brian on 8/28/16.
   */
  const _hasOwnProperty = Object.prototype.hasOwnProperty;

  function hasOwnProperty$1(obj, key) {
    return _hasOwnProperty.call(obj, key);
  }

  function makeOptions(opt, defaults) {
    let ret = Object.create(null);
    opt = isObj(opt) ? opt : {};
    forEach(defaults, (defaultValue, key) => ret[key] = hasOwnProperty$1(opt, key) ? opt[key] : defaultValue);
    return ret;
  }

  function objAssign(target) {
    if (!target) {
      throw Error('target is not object');
    }
    for (let i = 0; i < arguments.length; i++) {
      let source = arguments[i];
      clonePropertiesFrom(target, source);
    }
    return target;
  }

  function clonePropertiesFrom(source, target) {
    forEach(target, (val, key) => source[key] = val);
  }

  /**
   * Created by brian on 8/28/16.
   */
  function capitalizeString(str) {
    if (!str) {
      return '';
    }
    return str[0].toUpperCase() + str.substring(1);
  }

  function stringTemplate(stringTemplate) {
    var arg = arguments,
      r;
    return stringTemplate.replace(/\$\{(\d+)}/g, function ($i, i) {
      return (r = arg[i]) == undefined ? $i : formatNum(r);
    });
  }

  function formatNum(value) {
    return isNaN(value) ? value + '' : Number(value).toFixed(5).replace(/\.0+$/, '');
  }

  const slice = Array.prototype.slice;

  function $$(slt, ele) {
    return slice.apply((ele || document).querySelectorAll(slt));
  }

  /**
   * @memberOf Flip
   */
  function $(slt, ele) {
    return (ele || document).querySelector(slt);
  }

  function createElement(tagName, attributes, innerHTML) {
    let ele = document.createElement(tagName);
    forEach(attributes, function (val, name) {
      ele.setAttribute(name, val);
    });
    if (isStr(innerHTML)) {
      ele.innerHTML = innerHTML;
    }
    return ele;
  }

  /**
   * Created by brian on 14/10/2016.
   */
  function requestAnimationFrame(func) {
    window.requestAnimationFrame(func);
  }

  /**
   * Created by brian on 8/28/16.
   */

  /**
   * Created by brian on 8/28/16.
   */
  const EVENT_INIT = 'init';
  const EVENT_ITERATE = 'iterate';
  const EVENT_REVERSE = 'reverse';
  const EVENT_START = 'start';
  const EVENT_HOLD = 'hold';
  const EVENT_PAUSE = 'pause';
  const EVENT_RESUME = 'resume';
  const EVENT_CANCEL = 'cancel';
  const EVENT_UPDATE = 'update';
  const EVENT_END = 'end';
  const EVENT_RENDER_START = 'renderStart';
  const EVENT_RENDER_END = 'renderEnd';
  const EVENT_FRAME_START = 'frameStart';
  const EVENT_FRAME_END = 'frameEnd';

  /**
   * Created by brian on 16/10/2016.
   */
  class TreeRender extends Render {

    constructor() {
      super();
      this.children = [];
      this._disposableItems = [];
      this.parent = null;
    }

    add(child) {
      return this.addChild(child);
    }

    remove(child) {
      return this.removeChild(child);
    }

    addChild(child) {
      if (isRender(child)) {
        if (getDisposed(child)) {
          throw Error('disposed child should not be added again');
        }
        if (arrAdd(this.children, child)) {
          child.parent = this;
          this.invalid();
          return true;
        }
      }
      return false;
    }

    removeChild(child) {
      if (child.parent === this) {
        arrRemove(this.children, child);
        child.parent = null;
        arrAdd(this._disposableItems, child);
        this.invalid();
        return true;
      }
      return false;
    }

    findChild(find, recursive) {
      let children = this.children;
      let target = children.find(find);
      if (!target && recursive) {
        for (let i = 0; i < children.length; i++) {
          let child = children[i];
          if (child instanceof TreeRender) {
            let target = child.findChild(find, true);
            if (isRender(target)) {
              break;
            } else {
              target = null;
            }
          }
        }
      }
      return target;
    }

    dispose(e) {
      super.dispose(e);
      this.children.forEach(c => c.dispose(e));
      this.disposeRemovedItems(e);
    }

    disposeRemovedItems(e) {
      if (this._disposableItems.length) {
        this._disposableItems.forEach(c => c.dispose(e));
        this._disposableItems = [];
        this.invalid();
      }
    }

    render(state) {
      super.render(state);
      this.renderChildren(state);
    }

    update(e) {
      super.update(e);
      this.updateSelf(e);
      this.updateChildren(e);
    }

    updateSelf(e) {
    }

    updateChildren(e) {
      forEach(this.children, function (child) {
        if (isRender(child) && !child.disabled) {
          child.update(e);
        }
      });
    }

    renderChildren(state) {
      forEach(this.children, function (renderable) {
        if (isRender(renderable) && !renderable.disabled) {
          renderable.render(state);
        }
      });
    }

    invalid() {
      super.invalid();
      if (this.parent) {
        this.parent.invalid();
      }
    }
  }

  /**
   * Created by brian on 8/28/16.
   */
  const TICKS_PER_SECOND = 1000;
  class TimeLine extends EventEmitter {

    constructor() {
      super();
      this.last = this.now = this._stopTime = 0;
      this._startTime = this._lastStop = Date.now();
      this.ticksPerSecond = TICKS_PER_SECOND;
      this._isStop = true;
    }

    stop() {
      if (!this._isStop) {
        this._isStop = true;
        this._lastStop = Date.now();
        return true;
      }
    }

    start() {
      if (this._isStop) {
        this._isStop = false;
        this._stopTime += Date.now() - this._lastStop;
        return true;
      }
    }

    tick() {
      if (!this._isStop) {
        this.last = this.now;
        this.now = Date.now() - this._startTime - this._stopTime;
        return true;
      }
    }

    get advancedTimeInterval() {
      return this.now - this.last;
    }
  }

  /**
   * Created by brian on 8/21/16.
   */
  class RenderTask extends TreeRender {

    constructor(arg) {
      super();
      this.name = arg.name;
      this.timeLine = new TimeLine();
      this.clocks = [];
    }

    init() {
      this.timeLine.start();
    }

    addClock(clock) {
      return arrAdd(this.clocks, clock);
    }

    removeClock(clock) {
      return arrRemove(this.clocks, clock);
    }

    render(state) {
      this.emit(EVENT_RENDER_START, [state]);
      super.render(state);
      this.emit(EVENT_RENDER_END, [state]);
    }

    update(e) {
      this.timeLine.tick();
      if (this.clocks.length) {
        this.clocks.slice().forEach(c => c.update(e));
      }
      this.disposeRemovedItems(e);
      forEach(this.children, function recursive(renderable) {
        if (isRender(renderable) && !renderable.disabled) {
          if (!renderable.disabled) {
            renderable.update(e);
            forEach(renderable.children, recursive);
          }
        }
      });
      this.emit(EVENT_UPDATE, [e]);
    }

    invalid() {
      super.invalid();
      if (this.global) {
        this.global.invalid();
      }
    }
  }

  /**
   * Created by brian on 14/10/2016.
   */
  class RenderApplication {

    renderGlobalInitialized(global) {
    }

    setupRenderState(state) {
    }

    applyRenderState(state) {
    }

    removeFromGlobal(global) {
    }

  }

  const RENDER_STATE_PHRASE_UPDATE = 'update';
  /**
   * Created by brian on 8/28/16.
   */

  const RENDER_STATE_PHRASE_IDLE = 'idle';
  const RENDER_STATE_PHRASE_RENDER = 'render';
  const RENDER_STATE_PHRASE_APPLY = 'apply';
  class RenderState {

    get timeLine() {
      let task = this.task;
      return task ? task.timeLine : null;
    }

    constructor({ task, global }) {
      this.task = task;
      this.global = global;
      this.phrase = RENDER_STATE_PHRASE_IDLE;
    }

    willDisposeClock(clock) {
      this.task.removeClock(clock);
    }

    willDispose(any) {
    }

    dispose() {
    }
  }

  /**
   * Created by brian on 14/10/2016.
   */
  class RenderGlobal extends EventEmitter {

    constructor() {
      super();
      this._tasks = {};
      this.defaultTaskName = 'default';
      this.applications = this.getRenderApplications();
    }

    init() {
      let loop = () => {
        renderGlobal(this);
        requestAnimationFrame(loop);
      };
      requestAnimationFrame(loop);
      this.applications.forEach(a => a.renderGlobalInitialized(this));
      this.initiated = true;
      this.init = () => {
        console.warn('Don not all init() more than once');
        return this;
      };
      return this;
    }

    getRenderApplications() {
      return [];
    }

    add(renderModel) {
      if (isObj(renderModel) && renderModel.global) {
        throw Error('item not an object or belongs to other global', renderModel.global);
      }
      if (renderModel instanceof RenderTask) {
        this._tasks[renderModel.name] = renderModel;
        renderModel.global = this;
        renderModel.init();
        this.invalid();
        return true;
      } else if (renderModel instanceof Render) {
        return this.defaultTask.add(renderModel);
      } else if (renderModel instanceof RenderApplication) {
        if (arrAdd(this.applications, renderModel)) {
          renderModel.global = this;
          if (this.initiated) {
            renderModel.renderGlobalInitialized(this);
          }
          this.invalid();
          return true;
        }
      }
      return false;
    }

    remove(renderModel) {
      if (!isObj(renderModel) || renderModel.global != this) {
        throw Error('only remove added item');
      }
      if (renderModel instanceof RenderApplication) {
        if (arrRemove(this.applications, renderModel)) {
          renderModel.global = null;
          renderModel.removeFromGlobal(this);
          this.invalid();
          return true;
        }
      }
    }

    update(e) {
      e.phrase = RENDER_STATE_PHRASE_UPDATE;
      this.applications.forEach(app => app.setupRenderState(e));
      forEach(this._tasks, task => {
        if (!task.disabled) {
          e.task = task;
          task.update(e);
        }
      });
      e.task = null;
      e.phrase = RENDER_STATE_PHRASE_IDLE;
    }

    render(e) {
      e.phrase = RENDER_STATE_PHRASE_RENDER;
      forEach(this._tasks, task => {
        if (!task.disabled) {
          e.task = task;
          task.render(e);
        }
      });
      e.task = null;
      e.phrase = RENDER_STATE_PHRASE_IDLE;
    }

    invalid() {
      setInvalid(this, true);
    }

    createRenderState() {
      return new RenderState({ global: this });
    }

    applyRenderState(state) {
      state.phrase = RENDER_STATE_PHRASE_APPLY;
      this.applications.forEach(app => app.applyRenderState(state));
      state.phrase = RENDER_STATE_PHRASE_IDLE;
    }

    get defaultTask() {
      let taskName = this.defaultTaskName;
      let task = this._tasks[taskName];
      if (!task) {
        this.add(task = new RenderTask({ name: taskName }));
      }
      return task;
    }
  }
  function renderGlobal(model) {
    let state = model.createRenderState();
    model.emit(EVENT_FRAME_START, [state]);
    model.update(state);
    if (isInvalid(model)) {
      model.emit(EVENT_RENDER_START, [state]);
      model.render(state);
      model.applyRenderState(state);
      model.emit(EVENT_RENDER_END, [state]);
      setInvalid(model, false);
    }
    model.emit(EVENT_FRAME_END, [state]);
    state.dispose();
  }

  /**
   * Created by brian on 18/11/2016.
   */
  function updateStyleSheetWithDiffResult(sheet, diff, ids) {
    //replace
    let nextIds = ids.slice();
    forEach(diff.replace, (style, id) => {
      let index = nextIds.indexOf(id);
      if (index !== -1) {
        sheet.deleteRule(index);
        sheet.insertRule(style, index);
      } else {
        throw Error('replace id not found');
      }
    });
    //add
    forEach(diff.add, (style, id) => {
      sheet.insertRule(style, nextIds.length);
      if (!arrAdd(nextIds, id)) {
        throw Error('add id fail');
      }
    });
    //remove
    forEach(diff.remove, id => {
      let index = nextIds.indexOf(id);
      if (arrRemove(nextIds, id)) {
        sheet.deleteRule(index);
      } else {
        throw Error('remove id fail');
      }
    });
    return nextIds;
  }

  function diffCSSResultMap(pre, next) {
    let addMap = {};
    let replaceMap = {};
    let removeKeys = Object.getOwnPropertyNames(pre);
    forEach(next, (text, key) => {
      if (pre.hasOwnProperty(key)) {
        if (pre[key] != text) {
          replaceMap[key] = text;
        }
      } else {
        addMap[key] = text;
      }
      arrRemove(removeKeys, key);
    });
    return {
      add: addMap,
      replace: replaceMap,
      remove: removeKeys
    };
  }

  /**
   * Created by brian on 14/10/2016.
   */
  class CSSRenderApplication extends RenderApplication {

    constructor() {
      super();
      this._persistElement = createElement('style', { 'data-flip': 'persist' });
      this._frameElement = createElement('style', { 'data-flip': 'frame' });
      this._lastCSSResult = {};
      this._lastCSSIds = [];
      this._persistIndies = [];
    }

    applyRenderState(state) {
      let diff = diffCSSResultMap(this._lastCSSResult, state.cssResults);
      this._lastCSSIds = updateStyleSheetWithDiffResult(this._frameElement.sheet, diff, this._lastCSSIds);
      this._lastCSSResult = objAssign({}, state.cssResults);
      state.persistStyleTexts.forEach(text => this.applyCSSText(text));
    }

    applyCSSText(cssText) {
      let styleSheet = this._persistElement.sheet;
      let index = -1;
      let self = this;
      if (isValidString(cssText)) {
        let reusableIndies = this._persistIndies;
        if (reusableIndies.length) {
          index = reusableIndies.pop();
          styleSheet.deleteRule(index);
        } else {
          index = styleSheet.cssRules.length;
        }
        index = reusableIndies.length ? reusableIndies.pop() : styleSheet.cssRules.length;
        styleSheet.insertRule(cssText, index);
      }
      return function cancel() {
        if (index > -1) {
          styleSheet.deleteRule(index);
          styleSheet.insertRule('*{}', index);
          self._persistIndies.push(index);
        }
        self = styleSheet = null;
      };
    }

    setupRenderState(state) {
      state.cssResults = {};
      state.persistStyleTexts = [];
    }

    renderGlobalInitialized(global) {
      if (!this.elementAppended) {
        let head = document.head;
        head.appendChild(this._persistElement);
        head.appendChild(this._frameElement);
        this.elementAppended = true;
      }
    }
  }

  function isValidString(str) {
    return str && typeof str === "string";
  }

  /**
   * Created by brian on 16/11/2016.
   */
  const instance = new RenderGlobal();
  const instanceCSSRenderApplication = new CSSRenderApplication();
  instance.add(instanceCSSRenderApplication);

  /**
   * Created by brian on 8/28/16.
   */
  const EASE = {
    linear(t) {
      return t;
    },
    zeroStep(t) {
      return t <= 0 ? 0 : 1;
    },
    halfStep(t) {
      return t < .5 ? 0 : 1;
    },
    oneStep(t) {
      return t >= 1 ? 1 : 0;
    },
    random() {
      return Math.random();
    },
    randomLimit(t) {
      return Math.random() * t;
    }
  };
  const pow = Math.pow;
  const PI = Math.PI;
  const IN_OUT_FUNCS = {
    back: function (t) {
      return t * t * (3 * t - 2);
    },
    elastic: function (t) {
      return t === 0 || t === 1 ? t : -pow(2, 8 * (t - 1)) * Math.sin(((t - 1) * 80 - 7.5) * PI / 15);
    },
    sine: function (t) {
      return 1 - Math.cos(t * PI / 2);
    },
    circ: function (t) {
      return 1 - Math.sqrt(1 - t * t);
    },
    cubic: function (t) {
      return t * t * t;
    },
    expo: function (t) {
      return t == 0 ? 0 : pow(2, 10 * (t - 1));
    },
    quad: function (t) {
      return t * t;
    },
    quart: function (t) {
      return pow(t, 4);
    },
    quint: function (t) {
      return pow(t, 5);
    },
    bounce: function (t) {
      var pow2,
        bounce = 4;
      while (t < ((pow2 = pow(2, --bounce)) - 1) / 11);
      return 1 / pow(4, 3 - bounce) - 7.5625 * pow((pow2 * 3 - 2) / 22 - t, 2);
    }
  };
  forEach(IN_OUT_FUNCS, function (func, name) {
    var easeIn = func;
    EASE[name + 'In'] = easeIn;
    EASE[name + 'Out'] = function (t) {
      return 1 - easeIn(1 - t);
    };
    EASE[name + 'InOut'] = function (t) {
      return t < 0.5 ? easeIn(t * 2) / 2 : 1 - easeIn(t * -2 + 2) / 2;
    };
  });

  /**
   * Created by brian on 8/28/16.
   */
  /**
   * @memberOf Flip
   * @type {number}
   */
  const CLOCK_STATUS_IDLE = 0;
  const CLOCK_STATUS_PAUSED = 1;
  const CLOCK_STATUS_ACTIVE = 2;
  const CLOCK_STATUS_DELAYING = 3;
  const CLOCK_STATUS_HOLDING = 4;
  const CLOCK_STATUS_ENDED = 5;
  const CLOCK_STATUS_UNKNOWN = 6;
  const CLOCK_STATUS_STARTED = 7;
  const CLOCK_STATUS_CANCELED = 8;

  class Clock extends EventEmitter {

    constructor(arg) {
      super();
      let options = makeOptions(arg, DEFAULT_CLOCK_CONSTRUCTOR);
      clonePropertiesFrom(this, options);
      this.reset();
    }

    get started() {
      return this._startTime !== -1;
    }

    get paused() {
      return this._status == CLOCK_STATUS_PAUSED;
    }

    start() {
      if (this._status == CLOCK_STATUS_IDLE) {
        this._status = CLOCK_STATUS_STARTED;
        return true;
      }
      return false;
    }

    restart() {
      return this.reset().start();
    }

    reset() {
      this._status = CLOCK_STATUS_IDLE;
      this.d = 1;
      this.i = this.iteration;
      this.current = this._endTime = this._initTime = this._activeTime = this._startTime = this._holdTime = this._pausedTime = 0;
      return this;
    }

    pause() {
      if (this._status !== CLOCK_STATUS_PAUSED) {
        this._lastStatus = this._status;
        this._status = CLOCK_STATUS_PAUSED;
        this._pausedTime = 0;
        this.triggerEventWithDelegate(EVENT_PAUSE);
        return true;
      }
      return false;
    }

    resume() {
      if (this._status == CLOCK_STATUS_PAUSED) {
        this._status = this._lastStatus;
        this._lastStatus = CLOCK_STATUS_PAUSED;
        this.triggerEventWithDelegate(EVENT_RESUME);
        return true;
      }
      return false;
    }

    cancel() {
      if (this._status !== CLOCK_STATUS_CANCELED) {
        this._status = CLOCK_STATUS_CANCELED;
        this.triggerEventWithDelegate(EVENT_CANCEL);
        return true;
      }
      return false;
    }

    dispose() {
      this.off();
    }

    update(state) {
      let status = this._status;
      if (status == CLOCK_STATUS_CANCELED || status == CLOCK_STATUS_ENDED) {
        return state.willDisposeClock(this);
      } else {
        return _updateClock(this, state);
      }
    }

    triggerEventWithDelegate(event, e) {
      this.emit(event, [e]);
      if (this.delegate) {
        let func = this.delegate['onClock' + capitalizeString(event)];
        if (isFunc(func)) {
          func.apply(this.delegate, [this, e]);
        }
      }
    }
  }
  function _updateClock(clock, state) {
    let timeLine = state.timeLine,
      now = timeLine.now,
      ot = clock._t;
    switch (clock._status) {
      case CLOCK_STATUS_STARTED:
        clock._initTime = now;
        clock._status = CLOCK_STATUS_DELAYING;
        clock.current = 0;
        clock.triggerEventWithDelegate(EVENT_INIT, state);
        _updateClock(clock, state);
        return true;
      case CLOCK_STATUS_DELAYING:
        if (now >= clock._initTime + clock._pausedTime + clock.delay * timeLine.ticksPerSecond) {
          clock._status = CLOCK_STATUS_ACTIVE;
          clock._activeTime = clock._startTime = now;
          clock.current = 0;
          clock.triggerEventWithDelegate(EVENT_START, state);
          if (!clock.paused) {
            _updateClock(clock, state);
          }
          return true;
        }
        return false;
      case CLOCK_STATUS_PAUSED:
        if (clock._lastStatus == CLOCK_STATUS_HOLDING) {
          clock._holdTime += timeLine.advancedTimeInterval;
        } else {
          clock._pausedTime += timeLine.advancedTimeInterval;
        }
        return false;
      case CLOCK_STATUS_ACTIVE:
        var dur = (now - clock._activeTime - clock._pausedTime) / timeLine.ticksPerSecond;
        let t = clock.d ? dur / clock.duration : 1 - dur / clock.duration;
        if (ot === t) {
          return false;
        }
        clock.current = clock.ease(clock._t = clamp(0, 1, t));
        clock.triggerEventWithDelegate(EVENT_UPDATE, state);
        if (!clock.silent) {
          state.task.invalid();
        }
        if (t > 1 || t < 0) {
          clock._status = CLOCK_STATUS_UNKNOWN;
          _updateClock(clock, state);
        }
        return true;
      case CLOCK_STATUS_UNKNOWN:
        if (ot >= 1) {
          if (clock.reverse) {
            clock.d = 0;
            reactiveClock(clock, now);
            clock.triggerEventWithDelegate(EVENT_REVERSE, state);
          } else {
            return iterateClock(clock, now, state);
          }
        } else if (clock.reverse) {
          clock.d = 1;
          return iterateClock(clock, now, state);
        } else {
          throw Error('impossible state t=0,autoReverse=false');
        }
        return false;
      case CLOCK_STATUS_HOLDING:
        if (now >= clock._holdTime + clock.hold * timeLine.ticksPerSecond) {
          clock._status = CLOCK_STATUS_ENDED;
          clock._endTime = now;
          state.willDisposeClock(clock);
          clock.triggerEventWithDelegate(EVENT_END, state);
          return true;
        }
        return false;
      default:
        return false;
    }
  }

  function iterateClock(clock, now, state) {
    if (clock.i > 1 || clock.infinite) {
      clock.i--;
      clock.current = 0;
      reactiveClock(clock, now);
      clock.triggerEventWithDelegate(EVENT_ITERATE, state);
    } else {
      clock.i = 0;
      clock._holdTime = now;
      clock._status = CLOCK_STATUS_HOLDING;
      clock.triggerEventWithDelegate(EVENT_HOLD, state);
      return _updateClock(clock, state) || true;
    }
  }

  function reactiveClock(clock, now) {
    clock._status = CLOCK_STATUS_ACTIVE;
    clock._activeTime = now;
  }

  const DEFAULT_CLOCK_CONSTRUCTOR = Object.freeze({
    duration: 1,
    ease: EASE.linear,
    infinite: false,
    iteration: 1,
    silent: true,
    reverse: false,
    delay: 0,
    hold: 0,
    delegate: 0
  });

  function clamp(min, max, val) {
    if (val < min) {
      return min;
    } else if (val > max) {
      return max;
    }
    return val;
  }

  /**
   * Created by 柏子 on 2015/1/29.
   */
  let syncEnqueue = false;

  function enqueue(callback) {
    syncEnqueue ? callback() : setTimeout(callback, 0);
  }

  function Thenable(opt) {
    if (!(this instanceof Thenable)) {
      return new Thenable(opt);
    }
    this.then = opt.then;
    this.get = opt.get;
  }

  function waitAnimation(something) {
    if (something instanceof Animation) {
      return something.promise;
    } else if (something instanceof Array) {
      let animations = [];
      if (something.every(obj => {
          if (obj instanceof Animation) {
            animations.push(obj.promise);
            return true;
          }
        })) {
        return promiseAll(animations);
      }
    }
    return something;
  }

  function resolvePromise(future) {
    if (likePromise(future)) {
      return future;
    }
    return new Thenable({
      then: function resolved(callback) {
        try {
          return resolvePromise(waitAnimation(callback(future)));
        } catch (ex) {
          return rejectPromise(ex);
        }
      },
      get: function (proName) {
        return proName ? future[proName] : future;
      }
    });
  }

  function rejectPromise(reason) {
    if (likePromise(reason)) {
      return reason;
    }
    return new Thenable({
      then: function rejected(callback, errorback) {
        try {
          return resolvePromise(errorback(reason));
        } catch (ex) {
          return rejectPromise(ex);
        }
      },
      get: function (pro) {
        return pro ? reason[pro] : reason;
      }
    });
  }

  /**
   * @memberOf Flip
   * @param {function} resolver
   * @returns {Thenable}
   * @constructor
   */
  function Promise(resolver) {
    if (!(this instanceof Promise)) {
      return new Promise(resolver);
    }
    let resolvedPromise,
      pending = [],
      ahead = [],
      resolved;
    if (typeof resolver === "function") {
      resolver(resolve, reject);
    } else {
      throw Error('Promise resolver undefined is not a function');
    }
    function resolve(future) {
      try {
        receive(future);
      } catch (ex) {
        receive(undefined, ex);
      }
    }

    function reject(reason) {
      receive(undefined, reason || new Error(''));
    }

    function receive(future, reason) {
      if (!resolved) {
        resolvedPromise = reason == undefined ? resolvePromise(future) : rejectPromise(reason);
        resolved = true;
        for (let i = 0, len = pending.length; i < len; i++) {
          enqueue(function (args, con) {
            return function () {
              let ret = resolvedPromise.then.apply(resolvedPromise, args);
              if (con) {
                ret.then.apply(ret, con);
              }
            };
          }(pending[i], ahead[i]));
        }
        pending = ahead = undefined;
      }
    }

    function next(resolve, reject) {
      ahead.push([resolve, reject]);
    }

    return new Thenable({
      then: function (thenable, errorBack) {
        let handler = [ensureThenable(thenable, function (v) {
          return v;
        }), ensureThenable(errorBack, function (e) {
          throw e;
        })];
        if (resolvedPromise) {
          return warpPromiseValue(resolvedPromise.then.apply(resolvedPromise, handler));
        } else {
          pending.push(handler);
          return new Promise(function (resolve, reject) {
            next(resolve, reject);
          });
        }
      },
      get: function (proname) {
        return resolvedPromise ? resolvedPromise.get(proname) : undefined;
      }
    });
  }

  function ensureThenable(obj, def) {
    let t;
    if ((t = typeof obj) === "object") {
      return function () {
        return obj;
      };
    } else if (t === "function") {
      return obj;
    }
    return def;
  }

  function likePromise(obj) {
    return obj instanceof Thenable || isObj(obj) && isFunc(obj.then) && !(obj instanceof Animation);
  }

  function promiseRace(promises) {
    return new Promise(function (_res, _rej) {
      let hasResult;
      promises.forEach(function (promise) {
        promise.then(resolve, reject);
      });
      function resolve(e) {
        if (!hasResult) {
          _res(e);
          hasResult = true;
        }
      }

      function reject(e) {
        if (!hasResult) {
          _rej(e);
          hasResult = true;
        }
      }
    });
  }

  function promiseAll(promises) {
    return new Promise(function (resolve, reject) {
      let fail,
        num,
        r = new Array(num = promises.length);
      if (!promises.length) {
        return resolve(r);
      }
      promises.forEach(function (promise, i) {
        promise.then(function (pre) {
          check(pre, i);
        }, function (err) {
          check(err, i, true);
        });
      });
      function check(value, i, error) {
        r[i] = value;
        if (error) {
          fail = true;
        }
        if (num == 1) {
          fail ? reject(r) : resolve(r);
        } else {
          num--;
        }
      }
    });
  }

  /**
   * @memberOf Flip.Promise
   * @param {Array<Flip.Promise>}
   * @returns {Flip.Promise}
   */
  Promise.race = promiseRace;
  /**
   * continue when all promises finished
   * @memberof Flip.Promise
   * @param {Array<Flip.Promise>}
   * @returns Flip.Promise
   */
  Promise.all = promiseAll;
  /**
   * @memberof Flip.Promise
   * @returns {{resolve:function,reject:function,promise:Flip.Promise}}
   */
  Promise.defer = function () {
    let defer = {};
    defer.promise = new Promise(function (resolver, rejector) {
      defer.resolve = resolver;
      defer.reject = rejector;
    });
    return defer;
  };
  Promise.resolve = function (any) {
    return any && isFunc(any.then) ? digestThenable(any) : warpPromiseValue(any);
  };
  Promise.reject = function (reason) {
    return Promise(function (resolve, reject) {
      reject(reason);
    });
  };
  Promise.digest = digestThenable;
  Promise.option = function (opt) {
    if (opt) {
      syncEnqueue = !!opt.sync;
    }
  };

  function digestThenable(thenable) {
    return Promise(function (resolve, reject) {
      thenable.then(resolve, reject);
    });
  }

  function warpPromiseValue(any) {
    return Promise(function (resolve) {
      resolve(any);
    });
  }

  class RenderUtil {
    renderWithAnimation(animation, e) {
    }

    updateWithAnimation(animation, e) {
    }
  }
  /**
   * Created by brian on 17/10/2016.
   */

  const ANIMATION_FILL_MODE_REMOVE = 'remove';
  const ANIMATION_FILL_MODE_KEEP = 'keep';

  class Animation extends Render {

    constructor(options) {
      super();
      let clock = this._clock = new Clock(makeOptions(options, DEFAULT_CLOCK_CONSTRUCTOR));
      clock.delegate = this;
      this.fillMode = options.fillMode || ANIMATION_FILL_MODE_REMOVE;
      this._paramCallbacks = {};
      this.params = {};
      this.renderUtils = [];
      this.init();
      this.configOptions(options);
    }

    configOptions(options) {
      this.setVariables(options.variables);
      this.setDelegateHandlers(options);
      forEach(options.once, (func, evt) => this.once(evt, func, true));
      forEach(options.on, (func, evt) => this.on(evt, func));
    }

    get promise() {
      if (!this._deferred) {
        this._deferred = Promise.defer();
      }
      return this._deferred.promise;
    }

    init() {
      this._ended = this._canceled = false;
      return this.invalid();
    }

    start() {
      return this.clock.start();
    }

    pause() {
      return this.clock.pause();
    }

    resume() {
      return this.clock.resume();
    }

    cancel() {
      if (!this.canceled) {
        this._canceled = true;
        this.removeFromParent();
        this.emit(EVENT_CANCEL);
        if (this._deferred) {
          this._deferred.reject(this);
        }
        return true;
      }
      return false;
    }

    onClockUpdate(clock, e) {
      this.params = this.updateVariable(clock.current);
      this.invalid();
      this.emit(EVENT_UPDATE, [e, this]);
    }

    onClockEnd(clock, e) {
      if (this.fillMode == ANIMATION_FILL_MODE_REMOVE) {
        this.removeFromParent(true);
      }
      if (this._deferred) {
        this._deferred.resolve(this);
      }
      this.emit(EVENT_END, [e, this]);
    }

    updateVariable(percent) {
      let ret = {};
      if (isNaN(percent)) {
        percent = this.percent;
      }
      forEach(this._paramCallbacks, (func, key) => ret[key] = func.call(this, percent));
      return ret;
    }

    setDelegateHandlers(map) {
      forEach(map, (func, key) => ANIMATION_HANDLER_NAMES.indexOf(key) > -1 && this.setDelegateHandler(key, func));
      return this;
    }

    setDelegateHandler(name, handler) {
      let delegateMethodName = /^on/.test(name) ? name : `on${ capitalizeString(name) }`;
      if (ANIMATION_HANDLER_NAMES.indexOf(name) > -1) {
        let evtName = name.replace(/^on/, '').toLowerCase();
        this.on(evtName, handler);
      } else if (IS_DEV_ENV) {
        console.warn(`Animation delegate name:${ delegateMethodName } is not added`);
      }
      return false;
    }

    update(e) {
      super.update(e);
      this.clock.update(e);
      this.renderUtils.forEach(c => c.updateWithAnimation(this, e));
    }

    addRenderUtil(util) {
      if (util instanceof RenderUtil) {
        if (arrAdd(this.renderUtils, util)) {
          this.invalid();
          return true;
        }
      }
    }

    render(e) {
      super.render(e);
      forEach(this.renderUtils, util => util.renderWithAnimation(this, e));
    }

    dispose(e) {
      super.dispose(e);
      this.off();
      this.clock.dispose();
    }

    setVariables(map) {
      forEach(map, (val, key) => this.setVariable(key, val));
      return this;
    }

    setVariable(key, value) {
      let range;
      if (!isNaN(value)) {
        range = () => +value;
      } else if (value instanceof Array) {
        let min = +value[0],
          dis = +value[1] - min;
        range = p => min + dis * p;
      } else if (isFunc(value)) {
        range = value;
      } else {
        throw Error('value should be number or function');
      }
      this._paramCallbacks[key] = range;
      return this;
    }

    then(onSuccess, onFail) {
      return this.promise.then(onSuccess, onFail);
    }
  }
  Object.defineProperties(Animation.prototype, {
    clock: {
      get() {
        return this._clock;
      }
    },
    percent: {
      get() {
        return this._clock.current || 0;
      }
    },
    canceled: {
      get() {
        return this._canceled;
      }
    },
    ended: {
      get() {
        return this.clock._status == CLOCK_STATUS_ENDED;
      }
    }
  });
  const ANIMATION_HANDLER_NAMES = ['onUpdate', 'onEnd'];
  [['Init', EVENT_INIT], ['Iterate', EVENT_ITERATE], ['Reverse', EVENT_REVERSE], ['Start', EVENT_START], ['Hold', EVENT_HOLD], ['Pause', EVENT_PAUSE], ['Resume', EVENT_RESUME]].forEach(([methodName, evtName]) => {
    const delegateMethodName = 'on' + methodName;
    Animation.prototype['onClock' + methodName] = function triggerEvent(clock, e) {
      this.emit(evtName, [e, this]);
    };
    ANIMATION_HANDLER_NAMES.push(delegateMethodName);
  });

  /**
   * Created by brian on 16/10/2016.
   */
  let defaultPrefixes;
  let cssPrivateKeyPrefix = '$$';
  let cssPropertyKeys;
  let cssPrivateKeys = [];
  class CSSProxy {

    constructor(source) {
      this.$merge(source);
      this.$invalid = true;
    }

    $withPrefix(key, value, prefixes) {
      (prefixes || defaultPrefixes).forEach(prefix => this[normalizeCSSKey(prefix + key)] = value);
      return this;
    }

    $merge(obj) {
      if (isObj(obj) && obj !== this) {
        forEach(obj, (value, key) => this[key] = value);
      }
      return this;
    }

    $styleText(selector, separator) {
      return combineStyleText(selector, this.$toCachedCssString(separator));
    }

    $toCachedCssString(reset) {
      if (this.$invalid) {
        this.$cachedCssString = this.$toSafeCssString();
        this.$invalid = !!reset;
      }
      return this.$cachedCssString;
    }

    $toSafeCssString(separator) {
      var rules = [];
      forEach(this, function (val, key) {
        var i = cssPrivateKeys.indexOf(key);
        if (i > -1 && val !== void 0) {
          rules.push(cssPropertyKeys[i] + ':' + formatNum(val));
        }
      });
      return rules.join(';' + (separator || ''));
    }

    toString() {
      return this.$toSafeCssString();
    }

  }

  function combineStyleText(selector, body) {
    return selector + '{' + body + '}';
  }

  function normalizeCSSKey(cssKey) {
    return cssKey.replace(/^-/, '').replace(/-([a-z])/g, function (str, char) {
      return char.toUpperCase();
    });
  }

  (function () {
    if (isFunc(window.CSS2Properties)) {
      cssPropertyKeys = Object.getOwnPropertyNames(CSS2Properties.prototype).filter(key => key.indexOf('-') == -1);
    } else {
      cssPropertyKeys = Object.getOwnPropertyNames(document.documentElement.style);
    }
    cssPropertyKeys = cssPropertyKeys.map(function (key) {
      var privateKey = cssPrivateKeyPrefix + key,
        capitalizedKey = capitalizeString(key),
        camelKey = key[0].toLowerCase() + key.substring(1),
        lowerCaseKey = toLowerCssKey(key);
      cssPrivateKeys.push(privateKey);
      registerProperty(CSSProxy.prototype, [key, lowerCaseKey, capitalizedKey, camelKey], {
        get: getter,
        set: setter
      });
      function getter() {
        return this[privateKey];
      }

      function setter(val) {
        var v = castInvalidValue(val);
        if (v != this[privateKey]) {
          this.$invalid = true;
          this[privateKey] = v;
        }
      }

      return lowerCaseKey;
    });
    defaultPrefixes = ['-moz-', '-ms-', '-webkit-', '-o-', ''].filter(function (prefix) {
      var key = prefix.replace(/^-/, '');
      return cssPropertyKeys.some(function (proKey) {
        return proKey.indexOf(key) == 0 || proKey.indexOf(prefix) == 0;
      });
    });
    CSSProxy.prototype.$template = stringTemplate;
    function castInvalidValue(val) {
      var type = typeof val;
      return type == 'string' || type == 'number' ? val : void 0;
    }

    function registerProperty(target, keys, define) {
      keys.forEach(function (key) {
        Object.defineProperty(target, key, define);
      });
    }

    function toLowerCssKey(key) {
      var prefix = /^(webkit|moz|o|ms)[A-Z]/.test(key) ? '-' : '';
      return prefix + key.replace(/[A-Z]/g, function (str) {
          return '-' + str.toLowerCase();
        });
    }
  })();

  /**
   * Created by brian on 14/10/2016.
   */
  /**
   * @memberOf Flip
   */
  class Mat3 {

    constructor(arrayOrX1, y1, dx, x2, y2, dy) {
      var eles;
      if (arrayOrX1 == undefined) {
        eles = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      } else if (y1 == undefined) {
        eles = arrayOrX1;
      } else {
        eles = [arrayOrX1, y1, 0, x2, y2, dx, dy, 1];
      }
      this.elements = new Float32Array(eles);
    }

    clone() {
      return new Mat3(this.elements);
    }

    translate(x, y, z) {
      return multiplyMat(this, [1, 0, 0, 0, 1, 0, x || 0, y || 0, defaultIfNaN(z, 1)]);
    }

    flip(angle, horizontal, ratio) {
      var sinA = sin(angle),
        cosA = cos(angle);
      ratio = ratio || .6;
      return multiplyMat(this, horizontal ? [1, 0, 0, -sinA * ratio, cosA, 0, 0, 0, 1] : [cosA, sinA * ratio, 0, 0, 1, 0, 0, 0, 1]);
    }

    rotate(angle) {
      return this.rotateZ(angle);
    }

    rotateX(angle) {
      var sina = sin(angle),
        cosa = cos(angle);
      return multiplyMat(this, [1, 0, 0, 0, cosa, sina, 0, -sina, cosa]);
    }

    rotateY(angle) {
      var sina = sin(angle),
        cosa = cos(angle);
      return multiplyMat(this, [cosa, 0, -sina, 0, 1, 0, sina, 0, cosa]);
    }

    rotateZ(angle) {
      var sina = sin(angle),
        cosa = cos(angle);
      return multiplyMat(this, [cosa, sina, 0, -sina, cosa, 0, 0, 0, 1]);
    }

    transform(m11, m12, m21, m22, dx, dy) {
      return multiplyMat(this, [m11, m21, 0, m12, m22, 0, dx || 0, dy || 0, 1]);
    }

    skew(angle) {
      return multiplyMat(this, [1, tan(angle), 0, tan(angle || 0), 1, 0, 0, 0, 1]);
    }

    scale(x, y) {
      return multiplyMat(this, [defaultIfNaN(x, 1), 0, 0, 0, defaultIfNaN(y, 1), 0, 0, 0, 1]);
    }

    print() {
      var e = this.elements,
        ret = [];
      for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) ret.push(e[j + i * 3].toFixed(2));
        ret.push('\n');
      }
      return ret.join(' ');
    }

    setElement(col, row, value) {
      this.elements[col * 3 + row] = value;
      return this;
    }

    obliqueProject(rV, rH) {
      rH = rH || 0;
      rV = rV || 0;
      var s = 1 / tan(rV),
        sSin = sin(rH) * s,
        sCos = cos(rH) * s;
      return multiplyMat(this, [1, 0, 0, 0, 1, 0, sCos, sSin, 0], 1);
    }

    applyContext2D(ctx) {
      let eles = this.elements;
      ctx.transform(eles[0], eles[1], eles[2], eles[3], eles[4], eles[5]);
      return this;
    }

    toString() {
      return 'matrix(' + map2DArray(this.elements).join(',') + ')';
    }

    axonProject(rotationX, rotationY) {
      rotationX = rotationX || 0;
      rotationY = rotationY || 0;
      var cosX = cos(rotationX),
        sinX = sin(rotationX),
        cosY = cos(rotationY),
        sinY = sin(rotationY);
      return multiplyMat(this, [cosY, sinX * sinY, 0, 0, cosX, 0, sinY, -cosY * sinX, 0], 1);
    }

    concat(matOrArray) {
      var other = matOrArray instanceof Mat3 ? matOrArray.elements : matOrArray;
      return multiplyMat(this, other);
    }
  }
  function multiplyMat(mat, other, reverse) {
    var a = other,
      b = mat.elements,
      out = b;
    if (reverse) {
      b = other;
      a = out = mat.elements;
    } else {
      a = other;
      b = out = mat.elements;
    }
    var a00 = a[0],
      a01 = a[1],
      a02 = a[2],
      a10 = a[3],
      a11 = a[4],
      a12 = a[5],
      a20 = a[6],
      a21 = a[7],
      a22 = a[8],
      b00 = b[0],
      b01 = b[1],
      b02 = b[2],
      b10 = b[3],
      b11 = b[4],
      b12 = b[5],
      b20 = b[6],
      b21 = b[7],
      b22 = b[8];

    out[0] = a00 * b00 + a01 * b10 + a02 * b20;
    out[1] = a00 * b01 + a01 * b11 + a02 * b21;
    out[2] = a00 * b02 + a01 * b12 + a02 * b22;

    out[3] = a10 * b00 + a11 * b10 + a12 * b20;
    out[4] = a10 * b01 + a11 * b11 + a12 * b21;
    out[5] = a10 * b02 + a11 * b12 + a12 * b22;

    out[6] = a20 * b00 + a21 * b10 + a22 * b20;
    out[7] = a20 * b01 + a21 * b11 + a22 * b21;
    out[8] = a20 * b02 + a21 * b12 + a22 * b22;
    return mat;
  }

  const sin = Math.sin;
  const cos = Math.cos;
  const tan = Math.tan;
  const MAP_SEQ = [0, 1, 3, 4, 6, 7];

  function map2DArray(eles) {
    return MAP_SEQ.map(function (i) {
      return getFloat(eles[i]);
    });
  }

  function getFloat(d) {
    return (+d).toFixed(5);
  }

  function defaultIfNaN(v, def) {
    var ret = +v;
    return isNaN(ret) ? def : ret;
  }

  /**
   * Created by brian on 14/10/2016.
   */
  class CSSRenderUtil extends RenderUtil {

    constructor({ selector, id }) {
      super();
      this.selector = selector || '*';
      this.id = id || generateID();
      this._cssCallbacks = [];
      this._transformCallbacks = [];
      this._lastCSSResult = Object.create(null);
    }

    renderWithAnimation(animation, renderState) {
      let CSSResult = this._lastCSSResult = this.mapCSSResult(this.renderAnimationWithCSSRenderState(animation, renderState));
      objAssign(renderState.cssResults, CSSResult);
    }

    mapCSSResult(results) {
      let ret = Object.create(null);
      let id = this.id;
      results.forEach((val, index) => ret[`${ id }:${ index }`] = val);
      return ret;
    }

    updateWithAnimation(animation, e) {
      if (this._lastCSSResult) {
        objAssign(e.cssResults, this._lastCSSResult);
      }
    }

    renderAnimationWithCSSRenderState(animation, renderState) {
      if (isObj(renderState.cssResults)) {
        return this.renderCSSResults(animation.params, animation);
      } else {
        throw Error('CSSResults property is missing,forget to set CSSRender application?');
      }
    }

    renderCSSResults(params, thisObj) {
      let baseSel = this.selector;
      let ret = [];
      forEach(this._cssCallbacks, pair => {
        let cssProxy = new CSSProxy();
        let maybeResult = pair.callback.call(thisObj, cssProxy, params);
        if (maybeResult !== cssProxy && isObj(maybeResult)) {
          cssProxy.$merge(maybeResult);
        }
        addCSSText(baseSel, pair.selector, cssProxy, ret);
      });
      forEach(this._transformCallbacks, pair => {
        let mat = new Mat3();
        let maybeMat = pair.callback.call(thisObj, mat, params);
        if (maybeMat instanceof Mat3) {
          mat = maybeMat;
        }
        let cssProxy = new CSSProxy();
        cssProxy.$withPrefix('transform', mat.toString());
        addCSSText(baseSel, pair.selector, cssProxy, ret);
      });
      return ret;
    }

    transform(selector, func) {
      let callback;
      if (arguments.length == 1) {
        func = selector;
        selector = '';
      }
      if (func instanceof Mat3) {
        callback = () => func;
      } else if (isFunc(func)) {
        callback = func;
      } else {
        throw Error('transform callback type error');
      }
      if (callback) {
        this._transformCallbacks.push({ selector, callback });
      }
      return this;
    }

    css(selector, func) {
      let callback;
      if (arguments.length == 1) {
        func = selector;
        selector = '';
      }
      if (isObj(func)) {
        callback = proxy => proxy.$merge(func);
      } else if (isFunc(func)) {
        callback = func;
      } else {
        throw Error('css callback type error');
      }
      if (callback) {
        this._cssCallbacks.push({ selector, callback });
      }
      return this;
    }

  }
  CSSRenderUtil.prototype.type = 'CSSRenderUtil';
  let seed = 1;

  function generateID() {
    return 'CSSUtil' + seed++;
  }

  function addCSSText(baseSel, childSel, cssProxy, target) {
    let selector = childSel ? `${ baseSel } ${ childSel.replace(/&/g, baseSel) }` : baseSel;
    let CSSText = cssProxy.$styleText(selector);
    if (CSSText && selector) {
      if (target.indexOf(CSSText) == -1) {
        target.push(CSSText);
        return true;
      }
    }
  }

  /**
   * Created by brian on 16/11/2016.
   */
  function animate(options) {
    let animation = new Animation(options);
    if (!options.manualStart) {
      animation.start();
    }
    let renderTask = options.renderTask || instance.defaultTask;
    renderTask.addChild(animation);
    if ('selector' in options) {
      let cssRenderUtils = new CSSRenderUtil({ selector: options.selector });
      if (isFunc(options.css)) {
        cssRenderUtils.css(options.css);
      } else {
        forEach(options.css, (cb, selector) => cssRenderUtils.css(selector, cb));
      }
      if (isFunc(options.transform)) {
        cssRenderUtils.transform(options.transform);
      } else {
        forEach(options.transform, (cb, selector) => cssRenderUtils.transform(selector, cb));
      }
      animation.addRenderUtil(cssRenderUtils);
    }
    return animation;
  }

  /**
   * Created by brian on 14/10/2016.
   */

  /**
   * Created by brian on 17/11/2016.
   */

  /**
   * Created by brian on 8/21/16.
   */
  let readyFuncs = [];

  function Flip(arg) {
    if (isFunc(arg)) {
      if (readyFuncs) {
        readyFuncs.push(arg);
      } else {
        arg(Flip);
      }
    } else if (isObj(arg)) {
      animate(arg);
    }
  }

  if (typeof window === "object") {
    if (document.readyState !== 'loading') {
      setTimeout(ready, 0);
    }
    document.addEventListener('DOMContentLoaded', ready);
    if (typeof exports === "object") {
      exports = Flip;
    }
    window.Flip = Flip;
  }

  function ready() {
    let funcs = readyFuncs;
    instance.init();
    readyFuncs = null;
    funcs.forEach(callback => callback(Flip));
  }

  Object.defineProperties(Flip, {
    ready: {
      get() {
        return readyFuncs == null;
      }
    }
  });

  exports.instance = instance;
  exports.animate = animate;
  exports.Flip = Flip;
  exports.EASE = EASE;
  exports.Mat3 = Mat3;
  exports.$ = $;
  exports.$$ = $$;
  exports.forEach = forEach;
  exports.Promise = Promise;
  exports.PureRender = PureRender;
  exports.Render = Render;
  exports.isRender = isRender;
  exports.RenderTask = RenderTask;
  exports.RenderGlobal = RenderGlobal;
  exports.RENDER_STATE_PHRASE_UPDATE = RENDER_STATE_PHRASE_UPDATE;
  exports.RENDER_STATE_PHRASE_IDLE = RENDER_STATE_PHRASE_IDLE;
  exports.RENDER_STATE_PHRASE_RENDER = RENDER_STATE_PHRASE_RENDER;
  exports.RENDER_STATE_PHRASE_APPLY = RENDER_STATE_PHRASE_APPLY;
  exports.RenderState = RenderState;
  exports.TimeLine = TimeLine;
  exports.CLOCK_STATUS_IDLE = CLOCK_STATUS_IDLE;
  exports.CLOCK_STATUS_PAUSED = CLOCK_STATUS_PAUSED;
  exports.CLOCK_STATUS_ACTIVE = CLOCK_STATUS_ACTIVE;
  exports.CLOCK_STATUS_DELAYING = CLOCK_STATUS_DELAYING;
  exports.CLOCK_STATUS_HOLDING = CLOCK_STATUS_HOLDING;
  exports.CLOCK_STATUS_ENDED = CLOCK_STATUS_ENDED;
  exports.CLOCK_STATUS_UNKNOWN = CLOCK_STATUS_UNKNOWN;
  exports.CLOCK_STATUS_STARTED = CLOCK_STATUS_STARTED;
  exports.CLOCK_STATUS_CANCELED = CLOCK_STATUS_CANCELED;
  exports.Clock = Clock;
  exports.DEFAULT_CLOCK_CONSTRUCTOR = DEFAULT_CLOCK_CONSTRUCTOR;
  exports.TreeRender = TreeRender;
  exports.ANIMATION_FILL_MODE_REMOVE = ANIMATION_FILL_MODE_REMOVE;
  exports.ANIMATION_FILL_MODE_KEEP = ANIMATION_FILL_MODE_KEEP;
  exports.Animation = Animation;
  exports.RenderUtil = RenderUtil;
  exports.CSSRenderApplication = CSSRenderApplication;
  exports.CSSProxy = CSSProxy;
  exports.combineStyleText = combineStyleText;
  exports.CSSRenderUtil = CSSRenderUtil;

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

})));
//# sourceMappingURL=bundle.js.map
