import { CSS, nativeRaf, transitionEnd, nativeTimeout } from '../util/dom';
import { isDefined } from '../util/util';
export class Animation {
    constructor(ele, opts, raf) {
        this._dur = null;
        this._es = null;
        this.hasChildren = false;
        this.isPlaying = false;
        this.hasCompleted = false;
        this.element(ele).opts = opts;
        this._raf = raf || nativeRaf;
    }
    element(ele) {
        if (ele) {
            if (typeof ele === 'string') {
                ele = document.querySelectorAll(ele);
                for (var i = 0; i < ele.length; i++) {
                    this._addEle(ele[i]);
                }
            }
            else if (ele.length) {
                for (var i = 0; i < ele.length; i++) {
                    this._addEle(ele[i]);
                }
            }
            else {
                this._addEle(ele);
            }
        }
        return this;
    }
    _addEle(ele) {
        if (ele.nativeElement) {
            ele = ele.nativeElement;
        }
        if (ele.nodeType === 1) {
            this._eL = (this._e = this._e || []).push(ele);
        }
    }
    add(childAnimation) {
        childAnimation.parent = this;
        this.hasChildren = true;
        this._cL = (this._c = this._c || []).push(childAnimation);
        return this;
    }
    getDuration(opts) {
        if (opts && isDefined(opts.duration)) {
            return opts.duration;
        }
        else if (this._dur !== null) {
            return this._dur;
        }
        else if (this.parent) {
            return this.parent.getDuration();
        }
        return 0;
    }
    duration(milliseconds) {
        this._dur = milliseconds;
        return this;
    }
    getEasing() {
        return this._es !== null ? this._es : (this.parent && this.parent.getEasing()) || null;
    }
    easing(name) {
        this._es = name;
        return this;
    }
    from(prop, val) {
        this._addProp('from', prop, val);
        return this;
    }
    to(prop, val, clearProperyAfterTransition) {
        const fx = this._addProp('to', prop, val);
        if (clearProperyAfterTransition) {
            this.afterClearStyles([fx.trans ? CSS.transform : prop]);
        }
        return this;
    }
    fromTo(prop, fromVal, toVal, clearProperyAfterTransition) {
        return this.from(prop, fromVal).to(prop, toVal, clearProperyAfterTransition);
    }
    _getProp(name) {
        if (this._fx) {
            return this._fx.find((prop) => prop.name === name);
        }
        else {
            this._fx = [];
        }
        return null;
    }
    _addProp(state, prop, val) {
        let fxProp = this._getProp(prop);
        if (!fxProp) {
            var shouldTrans = (TRANSFORMS[prop] === 1);
            fxProp = {
                name: prop,
                trans: shouldTrans,
                wc: (shouldTrans ? CSS.transform : prop)
            };
            this._fx.push(fxProp);
        }
        let fxState = {
            val: val,
            num: null,
            unit: '',
        };
        fxProp[state] = fxState;
        if (typeof val === 'string' && val.indexOf(' ') < 0) {
            let r = val.match(CSS_VALUE_REGEX);
            let num = parseFloat(r[1]);
            if (!isNaN(num)) {
                fxState.num = num;
            }
            fxState.unit = (r[0] !== r[2] ? r[2] : '');
        }
        else if (typeof val === 'number') {
            fxState.num = val;
        }
        return fxProp;
    }
    beforeAddClass(className) {
        (this._bfAdd = this._bfAdd || []).push(className);
        return this;
    }
    beforeRemoveClass(className) {
        (this._bfRm = this._bfRm || []).push(className);
        return this;
    }
    beforeStyles(styles) {
        this._bfSty = styles;
        return this;
    }
    beforeClearStyles(propertyNames) {
        this._bfSty = this._bfSty || {};
        for (var i = 0; i < propertyNames.length; i++) {
            this._bfSty[propertyNames[i]] = '';
        }
        return this;
    }
    beforeAddRead(domReadFn) {
        (this._rdFn = this._rdFn || []).push(domReadFn);
        return this;
    }
    beforeAddWrite(domWriteFn) {
        (this._wrFn = this._wrFn || []).push(domWriteFn);
        return this;
    }
    afterAddClass(className) {
        (this._afAdd = this._afAdd || []).push(className);
        return this;
    }
    afterRemoveClass(className) {
        (this._afRm = this._afRm || []).push(className);
        return this;
    }
    afterStyles(styles) {
        this._afSty = styles;
        return this;
    }
    afterClearStyles(propertyNames) {
        this._afSty = this._afSty || {};
        for (var i = 0; i < propertyNames.length; i++) {
            this._afSty[propertyNames[i]] = '';
        }
        return this;
    }
    play(opts) {
        if (!this._raf) {
            return;
        }
        this._isAsync = this._hasDuration(opts);
        this._clearAsync();
        this._playInit(opts);
        this._raf(() => {
            (void 0);
            this._raf(this._playDomInspect.bind(this, opts));
        });
    }
    _playInit(opts) {
        this._twn = false;
        this.isPlaying = true;
        this.hasCompleted = false;
        this._hasDur = (this.getDuration(opts) > ANIMATION_DURATION_MIN);
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._playInit(opts);
        }
        if (this._hasDur) {
            this._progress(0);
            this._willChg(true);
        }
    }
    _playDomInspect(opts) {
        (void 0);
        this._beforeAnimation();
        const dur = this.getDuration(opts);
        if (this._isAsync) {
            this._asyncEnd(dur, true);
        }
        this._playProgress(opts);
        if (this._isAsync && this._raf) {
            this._raf(this._playToStep.bind(this, 1));
        }
    }
    _playProgress(opts) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._playProgress(opts);
        }
        if (this._hasDur) {
            this._setTrans(this.getDuration(opts), false);
        }
        else {
            this._progress(1);
            this._setAfterStyles();
            this._didFinish(true);
        }
    }
    _playToStep(stepValue) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._playToStep(stepValue);
        }
        if (this._hasDur) {
            this._progress(stepValue);
        }
    }
    _asyncEnd(dur, shouldComplete) {
        var self = this;
        function onTransitionEnd(ev) {
            self._clearAsync();
            self._playEnd();
            self._didFinishAll(shouldComplete, true, false);
        }
        function onTransitionFallback() {
            (void 0);
            self._tm = 0;
            self._clearAsync();
            self._playEnd(1);
            self._didFinishAll(shouldComplete, true, false);
        }
        self._unrgTrns = transitionEnd(self._transEl(), onTransitionEnd);
        self._tm = nativeTimeout(onTransitionFallback, (dur + TRANSITION_END_FALLBACK_PADDING_MS));
    }
    _playEnd(stepValue) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._playEnd(stepValue);
        }
        if (this._hasDur) {
            if (isDefined(stepValue)) {
                this._setTrans(0, true);
                this._progress(stepValue);
            }
            this._setAfterStyles();
            this._willChg(false);
        }
    }
    _hasDuration(opts) {
        if (this.getDuration(opts) > ANIMATION_DURATION_MIN) {
            return true;
        }
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            if (children[i]._hasDuration(opts)) {
                return true;
            }
        }
        return false;
    }
    _hasDomReads() {
        if (this._rdFn && this._rdFn.length) {
            return true;
        }
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            if (children[i]._hasDomReads()) {
                return true;
            }
        }
        return false;
    }
    stop(stepValue = 1) {
        this._clearAsync();
        this._hasDur = true;
        this._playEnd(stepValue);
    }
    _clearAsync() {
        this._unrgTrns && this._unrgTrns();
        this._tm && clearTimeout(this._tm);
        this._tm = this._unrgTrns = undefined;
    }
    _progress(stepValue) {
        let val;
        let effects = this._fx;
        let nuElements = this._eL;
        if (!effects || !nuElements) {
            return;
        }
        if (this._rv) {
            stepValue = ((stepValue * -1) + 1);
        }
        var i, j;
        var finalTransform = '';
        var elements = this._e;
        for (i = 0; i < effects.length; i++) {
            var fx = effects[i];
            if (fx.from && fx.to) {
                var fromNum = fx.from.num;
                var toNum = fx.to.num;
                var tweenEffect = (fromNum !== toNum);
                if (tweenEffect) {
                    this._twn = true;
                }
                if (stepValue === 0) {
                    val = fx.from.val;
                }
                else if (stepValue === 1) {
                    val = fx.to.val;
                }
                else if (tweenEffect) {
                    val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit;
                }
                if (val !== null) {
                    var prop = fx.name;
                    if (fx.trans) {
                        finalTransform += prop + '(' + val + ') ';
                    }
                    else {
                        for (j = 0; j < nuElements; j++) {
                            elements[j].style[prop] = val;
                        }
                    }
                }
            }
        }
        if (finalTransform.length) {
            if (!this._rv && stepValue !== 1 || this._rv && stepValue !== 0) {
                finalTransform += 'translateZ(0px)';
            }
            var cssTransform = CSS.transform;
            for (i = 0; i < elements.length; i++) {
                elements[i].style[cssTransform] = finalTransform;
            }
        }
    }
    _setTrans(dur, forcedLinearEasing) {
        if (!this._fx) {
            return;
        }
        const elements = this._e;
        const easing = (forcedLinearEasing ? 'linear' : this.getEasing());
        const durString = dur + 'ms';
        const cssTransform = CSS.transition;
        const cssTransitionDuration = CSS.transitionDuration;
        const cssTransitionTimingFn = CSS.transitionTimingFn;
        let eleStyle;
        for (var i = 0; i < this._eL; i++) {
            eleStyle = elements[i].style;
            if (dur > 0) {
                eleStyle[cssTransform] = '';
                eleStyle[cssTransitionDuration] = durString;
                if (easing) {
                    eleStyle[cssTransitionTimingFn] = easing;
                }
            }
            else {
                eleStyle[cssTransform] = 'none';
            }
        }
    }
    _beforeAnimation() {
        this._fireBeforeReadFunc();
        this._fireBeforeWriteFunc();
        this._setBeforeStyles();
    }
    _setBeforeStyles() {
        let i, j;
        const children = this._c;
        for (i = 0; i < this._cL; i++) {
            children[i]._setBeforeStyles();
        }
        if (this._rv) {
            return;
        }
        const addClasses = this._bfAdd;
        const removeClasses = this._bfRm;
        let ele;
        let eleClassList;
        let prop;
        for (i = 0; i < this._eL; i++) {
            ele = this._e[i];
            eleClassList = ele.classList;
            if (addClasses) {
                for (j = 0; j < addClasses.length; j++) {
                    eleClassList.add(addClasses[j]);
                }
            }
            if (removeClasses) {
                for (j = 0; j < removeClasses.length; j++) {
                    eleClassList.remove(removeClasses[j]);
                }
            }
            if (this._bfSty) {
                for (prop in this._bfSty) {
                    ele.style[prop] = this._bfSty[prop];
                }
            }
        }
    }
    _fireBeforeReadFunc() {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._fireBeforeReadFunc();
        }
        const readFunctions = this._rdFn;
        if (readFunctions) {
            for (var i = 0; i < readFunctions.length; i++) {
                readFunctions[i]();
            }
        }
    }
    _fireBeforeWriteFunc() {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._fireBeforeWriteFunc();
        }
        const writeFunctions = this._wrFn;
        if (this._wrFn) {
            for (var i = 0; i < writeFunctions.length; i++) {
                writeFunctions[i]();
            }
        }
    }
    _setAfterStyles() {
        let i, j;
        let ele;
        let eleClassList;
        let elements = this._e;
        for (i = 0; i < this._eL; i++) {
            ele = elements[i];
            eleClassList = ele.classList;
            ele.style[CSS.transitionDuration] = ele.style[CSS.transitionTimingFn] = '';
            if (this._rv) {
                if (this._bfAdd) {
                    for (j = 0; j < this._bfAdd.length; j++) {
                        eleClassList.remove(this._bfAdd[j]);
                    }
                }
                if (this._bfRm) {
                    for (j = 0; j < this._bfRm.length; j++) {
                        eleClassList.add(this._bfRm[j]);
                    }
                }
                if (this._bfSty) {
                    for (var prop in this._bfSty) {
                        ele.style[prop] = '';
                    }
                }
            }
            else {
                if (this._afAdd) {
                    for (j = 0; j < this._afAdd.length; j++) {
                        eleClassList.add(this._afAdd[j]);
                    }
                }
                if (this._afRm) {
                    for (j = 0; j < this._afRm.length; j++) {
                        eleClassList.remove(this._afRm[j]);
                    }
                }
                if (this._afSty) {
                    for (var prop in this._afSty) {
                        ele.style[prop] = this._afSty[prop];
                    }
                }
            }
        }
    }
    _willChg(addWillChange) {
        let wc;
        let effects = this._fx;
        if (addWillChange && effects) {
            wc = [];
            for (var i = 0; i < effects.length; i++) {
                var propWC = effects[i].wc;
                if (propWC === 'webkitTransform') {
                    wc.push('transform', '-webkit-transform');
                }
                else {
                    wc.push(propWC);
                }
            }
        }
        let willChange = (wc && wc.length) ? wc.join(',') : '';
        for (var i = 0; i < this._eL; i++) {
            this._e[i].style.willChange = willChange;
        }
    }
    progressStart() {
        this._clearAsync();
        this._beforeAnimation();
        this._progressStart();
    }
    _progressStart() {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._progressStart();
        }
        this._setTrans(0, true);
        this._willChg(true);
    }
    progressStep(stepValue) {
        stepValue = Math.min(1, Math.max(0, stepValue));
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i].progressStep(stepValue);
        }
        if (this._rv) {
            stepValue = ((stepValue * -1) + 1);
        }
        this._progress(stepValue);
    }
    progressEnd(shouldComplete, currentStepValue, maxDelta = 0) {
        (void 0);
        this._isAsync = (currentStepValue > 0.05 && currentStepValue < 0.95);
        const stepValue = shouldComplete ? 1 : 0;
        const factor = Math.max(Math.abs(currentStepValue - stepValue), 0.5) * 2;
        const dur = 64 + factor * maxDelta;
        this._progressEnd(shouldComplete, stepValue, dur, this._isAsync);
        if (this._isAsync) {
            this._asyncEnd(dur, shouldComplete);
            this._raf && this._raf(this._playToStep.bind(this, stepValue));
        }
    }
    _progressEnd(shouldComplete, stepValue, dur, isAsync) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._progressEnd(shouldComplete, stepValue, dur, isAsync);
        }
        if (!isAsync) {
            this._progress(stepValue);
            this._willChg(false);
            this._setAfterStyles();
            this._didFinish(shouldComplete);
        }
        else {
            this.isPlaying = true;
            this.hasCompleted = false;
            this._hasDur = true;
            this._willChg(true);
            this._setTrans(dur, false);
        }
    }
    onFinish(callback, onceTimeCallback = false, clearOnFinishCallacks = false) {
        if (clearOnFinishCallacks) {
            this._fFn = this._fOneFn = undefined;
        }
        if (onceTimeCallback) {
            this._fOneFn = this._fOneFn || [];
            this._fOneFn.push(callback);
        }
        else {
            this._fFn = this._fFn || [];
            this._fFn.push(callback);
        }
        return this;
    }
    _didFinishAll(hasCompleted, finishAsyncAnimations, finishNoDurationAnimations) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i]._didFinishAll(hasCompleted, finishAsyncAnimations, finishNoDurationAnimations);
        }
        if (finishAsyncAnimations && this._isAsync || finishNoDurationAnimations && !this._isAsync) {
            this._didFinish(hasCompleted);
        }
    }
    _didFinish(hasCompleted) {
        this.isPlaying = false;
        this.hasCompleted = hasCompleted;
        if (this._fFn) {
            for (var i = 0; i < this._fFn.length; i++) {
                this._fFn[i](this);
            }
        }
        if (this._fOneFn) {
            for (var i = 0; i < this._fOneFn.length; i++) {
                this._fOneFn[i](this);
            }
            this._fOneFn.length = 0;
        }
    }
    reverse(shouldReverse = true) {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i].reverse(shouldReverse);
        }
        this._rv = shouldReverse;
        return this;
    }
    destroy() {
        const children = this._c;
        for (var i = 0; i < this._cL; i++) {
            children[i].destroy();
        }
        this._clearAsync();
        this.parent = this._e = this._rdFn = this._wrFn = this._raf = null;
        if (this._c) {
            this._c.length = this._cL = 0;
        }
        if (this._fFn) {
            this._fFn.length = 0;
        }
        if (this._fOneFn) {
            this._fOneFn.length = 0;
        }
    }
    _transEl() {
        var targetEl;
        for (var i = 0; i < this._cL; i++) {
            targetEl = this._c[i]._transEl();
            if (targetEl) {
                return targetEl;
            }
        }
        return (this._twn && this._hasDur && this._eL ? this._e[0] : null);
    }
}
const TRANSFORMS = {
    'translateX': 1,
    'translateY': 1,
    'translateZ': 1,
    'scale': 1,
    'scaleX': 1,
    'scaleY': 1,
    'scaleZ': 1,
    'rotate': 1,
    'rotateX': 1,
    'rotateY': 1,
    'rotateZ': 1,
    'skewX': 1,
    'skewY': 1,
    'perspective': 1
};
const CSS_VALUE_REGEX = /(^-?\d*\.?\d*)(.*)/;
const ANIMATION_DURATION_MIN = 32;
const TRANSITION_END_FALLBACK_PADDING_MS = 400;
//# sourceMappingURL=animation.js.map