#include easings.js

// This probably need a bit of a rewrite at some point to allow starting for arbitrary time points
const MamiAnimate = info => {
    if(typeof info !== 'object')
        throw 'info must be an object';

    let onUpdate;
    if('update' in info && typeof info.update === 'function')
        onUpdate = info.update;
    if(onUpdate === undefined)
        throw 'update is a required parameter for info';

    let duration;
    if('duration' in info)
        duration = parseFloat(info.duration);
    if(duration <= 0)
        throw 'duration is a required parameter for info and must be greater than 0';

    let onStart;
    if('start' in info && typeof info.start === 'function')
        onStart = info.start;

    let onEnd;
    if('end' in info && typeof info.end === 'function')
        onEnd = info.end;

    let onCancel;
    if('cancel' in info && typeof info.cancel === 'function')
        onCancel = info.cancel;

    let easing;
    if('easing' in info)
        easing = info.easing;

    let delayed;
    if('delayed' in info)
        delayed = !!info.delayed;

    let async;
    if('async' in info)
        async = !!info.async;

    const easingType = typeof easing;
    if(easingType !== 'function') {
        if(easingType === 'string' && easing in MamiEasings)
            easing = MamiEasings[easing];
        else
            easing = MamiEasings.linear;
    }

    if(typeof window.mami?.settings?.get === 'function')
        duration *= mami.settings.get('dbgAnimDurationMulti');

    let tStart, tLast,
        cancel = false,
        tRawCompletion = 0,
        tCompletion = 0,
        started = !delayed;

    const update = tCurrent => {
        if(tStart === undefined) {
            tStart = tCurrent;
            if(onStart !== undefined)
                onStart();
        }

        const tElapsed = tCurrent - tStart;

        tRawCompletion = Math.min(1, Math.max(0, tElapsed / duration));
        tCompletion = easing(tRawCompletion);

        onUpdate(tCompletion, tRawCompletion);

        if(tElapsed < duration) {
            if(cancel) {
                if(onCancel !== undefined)
                    onCancel(tCompletion, tRawCompletion);
                return;
            }

            tLast = tCurrent;
            requestAnimationFrame(update);
        } else if(onEnd !== undefined)
            onEnd();
    };

    let promise;
    if(async)
        promise = new Promise((resolve, reject) => {
            if(onCancel === undefined) {
                onCancel = reject;
            } else {
                const realOnCancel = onCancel;
                onCancel = (...args) => {
                    realOnCancel(...args);
                    reject(...args);
                };
            }

            if(onEnd === undefined) {
                onEnd = resolve;
            } else {
                const realOnEnd = onEnd;
                onEnd = (...args) => {
                    realOnEnd(...args);
                    resolve(...args);
                };
            }

            if(!delayed)
                requestAnimationFrame(update);
        });

    if(!delayed) {
        if(promise !== undefined)
            return promise;

        requestAnimationFrame(update);
    }

    return {
        getCompletion: () => tCompletion,
        getRawCompletion: () => tRawCompletion,
        start: () => {
            if(!started) {
                started = true;
                requestAnimationFrame(update);
            }

            if(promise !== undefined)
                return promise;
        },
        cancel: () => {
            cancel = true;
        },
    };
};
