//=============================================================================
// RPG Maker MZ - SmartAnimation
//
// Copyright 2024 Panzer-IV. All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//=============================================================================
/*:
 * @target MZ
 * @plugindesc スマートアニメーション
 * @author 四号戦車
 * @base PluginCommonBase
 * @base DebugSwitch
 * @base TextResource
 * @base PluginUtils
 * @orderAfter PluginCommonBase
 * @orderAfter DebugSwitch
 * @orderAfter TextResource
 * @orderAfter PluginUtils
 *
 * @help SmartAnimation.js
 *
 * Version: 0.0.33
 *
 * スマートアニメーションを実装するユーティリティプラグインです。
 * フレームに合わせて複数の値を変更できます。
 * 他のプラグインで使用する前提です。
 *
 */
(() => {
	"use strict";

    //========================================================================
    // プラグイン定義
    //========================================================================
    const pluginName = "SmartAnimation";
    const script = document.currentScript;
    const param  = PluginManagerEx.createParameter(script);

    //========================================================================
    // 定数定義 (EasingType)
    //========================================================================
    const EasingType = {
        LINEAR: 'linear',
        EASE_IN: 'ease_in',
        EASE_OUT: 'ease_out',
        EASE_INOUT: 'ease_inout',
    };
    window.EasingType = Object.freeze(EasingType);

    //========================================================================
    // クラス定義 (SmartAnimation)
    //========================================================================
    class SmartAnimation {
        constructor(name, object, property, repeat, start, end, callback, easing) {
            this.name = name;
            this.object = object;
            this.startValue = start;
            this.endValue = end;
            this.targetProperty = property;
            this.state = 'ready';
            this.repeat = repeat;

            if (!!easing) {
                this.easing = easing;
            } else {
                this.easing = EasingType.LINEAR;
            }

            if (!!callback && typeof callback === 'function') {
                this.listener = callback;
            } else {
                this.listener = null;
            }

            this.directionReverse = (end < start);
            this.currentFrame = 0;
            this.currentValue = start;
            this.throttle = 0;
            this.duration = 0;
            this.inSkip = false;
        }

        setDuration(duration) {
            this.duration = duration;
            let amount = this.endValue - this.startValue;
            if (this.directionReverse) {
                amount = this.startValue - this.endValue;
            }
            this.throttle = Math.abs(amount / duration);

            // Logger.d(`setDuration: throttle = ${this.throttle}, duration = ${duration}, amount = ${amount}`);
            this.reset();
            return this;
        }

        setThrottle(throttle) {
            this.throttle = throttle;
            let amount = this.endValue - this.startValue;
            if (this.directionReverse) {
                amount = this.startValue - this.endValue;
            }
            this.duration = amount / Math.abs(throttle);
            // Logger.d(`setThrottle: throttle = ${throttle}, duration = ${this.duration}, amount = ${amount}`);
            this.reset();
            return this;
        }

        getDuration() {
            return this.duration;
        }

        getThrottle() {
            return this.throttle;
        }

        getState() {
            return this.state;
        }

        start() {
            this._setState('running');
        }

        setSkip(skip) {
            this.inSkip = skip;
        }

        stop() {
            if (this.state !== 'complete') {
                this._setState('stopped');
                this._callbackListener(true);
            }
        }

        completeForce() {
            if (this.state !== 'complete') {
                this.currentFrame = this.duration;
                this.update();
            }
        }

        // Game_Picture.prototype.applyEasing = function(current, target) {
        //     const d = this._duration;
        //     const wd = this._wholeDuration;
        //     const lt = this.calcEasing((wd - d) / wd);
        //     const t = this.calcEasing((wd - d + 1) / wd);
        //     const start = (current - target * lt) / (1 - lt);
        //     return start + (target - start) * t;
        // };

        calcTarget() {
            if (this.easing === EasingType.LINEAR) {
                return (this.throttle * this.currentFrame);
            } else {
                const current = this.duration - this.currentFrame;
                const lt = this.calcEasing(this.currentFrame / this.duration);
                const t = this.calcEasing((this.currentFrame + 1) / this.duration);
            }
        }

        calcEasing(t) {
            const exponent = 2;

            switch (this.easing) {
                case EasingType.EASE_IN:
                    return Math.pow(t, exponent);
                case EasingType.EASE_OUT:
                    return 1 - Math.pow(1 - t, exponent);
                case EasingType.EASE_INOUT:
                    if (t < 0.5) {
                        return Math.pow((t * 2), exponent) / 2;
                    } else {
                        return (1 - Math.pow(1 - (t * 2 - 1), exponent)) / 2 + 0.5;
                    }
            } // switch
            return t;
        }

        update() {
            if (this.state !== 'running') {
                return;
            }

            let target = 0;
            if (this.directionReverse) {
                const currentValue = this.startValue - (this.throttle * this.currentFrame);
                target = Math.max(currentValue, this.endValue);
            } else {
                const currentValue = this.startValue + (this.throttle * this.currentFrame);
                target = Math.min(currentValue, this.endValue);
            }

            if (!!this.object && !!this.targetProperty) {
                this.object[this.targetProperty] = target;
            }
            this.currentFrame++;

            if (this.currentFrame >= this.duration || this.inSkip) {
                if (!!this.listener) {
                    this.listener(this.name);
                }

                if (!!this.object && !!this.targetProperty) {
                    this.object[this.targetProperty] = this.endValue;
                }
                this._setState('complete');
                if (this.repeat) {
                    this.reset();
                }
            }
        }

        reset() {
            this.currentFrame = 0;
            this.state = 'ready';
            if (!!this.object && !!this.targetProperty && !!this.object[this.targetProperty]) {
                this.object[this.targetProperty] = this.startValue;
            }
        }

        inRunning() {
            return this.state === 'running';
        }

        isComplete() {
            return this.state === 'complete';
        }

        isRepeat() {
            return this.repeat;
        }

        _callbackListener(byAbort) {
            if (!!this.listener && typeof this.listener === 'function') {
                this.listener(byAbort);
            }
        }

        _setState(newState) {
            this.state = newState;
        }
    }
    window.SmartAnimation = SmartAnimation;

    //========================================================================
    // クラス定義 (SmartAnimationWait)
    //========================================================================
    class SmartAnimationWait extends SmartAnimation {
        constructor(name, duration, callback) {
            super(name, null, null, false, 0, 0, callback);
            this.setDuration(duration);
        }
    }
    window.SmartAnimationWait = SmartAnimationWait;

    //========================================================================
    // クラス定義 (SmartAnimationSequencial)
    //========================================================================
    class SmartAnimationSequencial extends SmartAnimation {
        constructor(name, repeat, callback, ...values) {
            super(name, null, null, repeat, 0, 0, callback, ...values);

            this.values = !!values ? values : [];
            this.targetFrame = values.reduce((accum, value) => {
                return accum + value.getDuration();
            }, 0);
            this.currentIndex = 0;
        }

        update() {
            if (this.state !== 'running') {
                return;
            }

            const current = this.values[this.currentIndex];
            if (!!current) {
                if (current.isComplete()) {
                    this.currentIndex++;
                    if (this.currentIndex >= this.values.length) {
                        this._setState('complete');
                        this._callbackListener(false);

                        if (this.repeat) {
                            this.reset();
                            this.start();
                        }
                    } else {
                        this.currentValueFrame = 0;
                        this.values[this.currentIndex].update();
                    }
                } else {
                    current.update();
                }
            }
        }

        reset() {
            super.reset();
            this.values.forEach(value => value.reset());
            this.currentIndex = 0;
        }

        start() {
            super.start();
            this.values.forEach(value => value.start());
        }

        stop() {
            super.stop();
            this.values.forEach(value => value.stop());
        }
    }
    window.SmartAnimationSequencial = SmartAnimationSequencial;

    //========================================================================
    // クラス定義 (SmartAnimationParallel)
    //========================================================================
    class SmartAnimationParallel extends SmartAnimation {
        constructor(name, repeat, callback, ...values) {
            super(name, null, null, repeat, 0, 0, callback, ...values);
            this.values = !!values ? values : [];
            this.targetFrame = values.reduce((accum, value) => {
                const duration = value.getDuration();
                if (accum < duration) {
                    return duration;
                } else {
                    return accum;
                }
            }, 0);
            this.currentFrame = 0;
        }

        update() {
            if (this.state !== 'running') {
                return;
            }

            this.values
                .filter(value => value.isComplete() === false)
                .forEach(value => value.update());

            this.currentFrame++;
            if (this.currentFrame >= this.targetFrame) {
                this._setState('complete');
                this._callbackListener(false);

                if (this.repeat) {
                    this.reset();
                }
            }
        }

        reset() {
            super.reset();
            this.values.forEach(value => value.reset());
        }

        start() {
            super.start();
            this.values.forEach(value => value.start());
        }

        stop() {
            super.stop();
            this.values.forEach(value => value.stop());
        }
    }
    window.SmartAnimationParallel = SmartAnimationParallel;

})();
