class MotionEngine {
    constructor() {
        const aq = [];
        this.animationQueue = aq;

        function calcValues(target, values, initial, k = 1, mode = 'offset') {
            Object.entries(values).forEach(([key, value]) => {
                if (value && typeof value === 'object') {
                    if (!target[key]) {
                        target[key] = {};
                    }
                    calcValues(target[key], value, initial[key], k, mode);
                    return;
                }
                if (mode == 'offset') {
                    target[key] = (initial[key] || 0) + value * k;
                } else if (typeof (value) == 'function') {
                    let v = value(k, target[key]);
                    if (target[key] !== v){
                        target[key] = v;
                    }
                } else {
                    target[key] = (initial[key] || 0) + (value - (initial[key] || 0)) * k;
                }
            });
            return target;
        }

        // a = {o-object, a-attributes, t-time, f-finish event, d-delay, m-mode, r-repeat, 
        // rd-repeat the delay, rf-reset on finish, u-on update, s-scope, p-promise on finish}
        this.add = function (a) {
            a = Array.isArray(a) ? a : [a];
            let r = [];
            a.forEach(e => {
                if (e.t == 0){
                    e.t = 1;
                    this.animate(e, 1)
                }else{
                    aq.push(e);
                }
                if (e.p && !e.f){
                    r.push(new Promise( resolve=> e.f = resolve ))
                }
            });
            if (r.length) return Promise.all(r);
        };

        this.clear = function (object, reset = false) {
            for (var i = aq.length - 1; i >= 0; i--) {
                if (object && aq[i].o == object || !object && (aq[i].ct == aq[i].t || aq[i].rr)) {
                    if (reset){
                        aq[i].ct = 0;
                        this.animate(aq[i], 0);
                    }
                    aq.splice(i, 1);
                }
            }
        };

        this.clearAll = function(){
            aq.splice(0, aq.length);
            return;
        }

        this.remove = function (a) {
            let idx = aq.indexOf(a);
            if (idx > -1) {
                a.ct = 0;
                this.animate(a, 0);
                aq.splice(idx, 1);
            }
        };

        this.update = (delta) => {
            if (aq.length) {
                aq.forEach(e => this.animate(e, delta));
                this.clear();
            }
        };

        this.animate = function (e, t) {
            if (e.d && (e.dt || 0) < e.d) {
                e.dt = (e.dt || 0) + t;
                return;
            }
            if (e.ct === undefined) {
                e.ct = 0;
                e.iv = calcValues({}, e.a, e.o, 0);
            }
            e.ct = e.ct + t;
            if (e.ct > e.t) {
                e.ct = e.t;
                e.f?.();
                if (e.rf){
                    e.ct = t = 0;
                    e.rr = true;
                }
            }
            calcValues(e.o, e.a, e.iv, e.ct / e.t, e.m || 'value');
            e.u?.();
            if (e.ct == e.t && e.r) {
                e.ct = e.m == 'offset' ? undefined : 0;
                if (e.rd) {
                    e.dt = 0;
                }
            }
        };

        this.isActive = function (object) {
            return aq.find(e => e.o == object);
        };

        this.isIdle = function (scope) {
            if (!scope) return aq.length == 0;
            return aq.filter(e => e.s == scope).length == 0
        };
    }
}

export {MotionEngine}