import { MUtils } from "../Global/MUtils";
import MTimer from "../Global/MTimer";
import GameMgr from "../Mgr/GameMgr";

class MTweenObject {
    public static readonly frameRate = 60;
    protected tweenList: MTweenBase[] = [];
    public currentTween: MTweenBase = null;
    public target: object = null

    constructor(target: object) {
        this.target = target;
    }

    public wait(duration: number, isFrameCount = true, callback?: () => void) {
        this.addTween(new WaitTween(isFrameCount ? duration : this.toFrameRate(duration), this, callback));
        return this;
    }

    protected addTween(tween: MTweenBase) {
        if (this.currentTween == null) {
            this.currentTween = tween;
        } else {
            this.tweenList.push(tween);
        }
    }

    public to(props: object, duration: number, isFrameCount = true) {
        this.addTween(new LinearTween(props, isFrameCount ? duration : this.toFrameRate(duration), this));
        return this;
    }

    public set(props: object) {
        for (let key in props) {
            this.target[key] = props[key];
        }
        return this;
    }

    public call(callback: () => void) {
        this.addTween(new CallbackTween(callback, 0, this));
        return this;
    }

    private toFrameRate(duration: number) {
        return duration / 1000 * MTweenObject.frameRate;
    }

    public update() {
        if (!this.currentTween) return;

        this.currentTween.apply(MTimer.dtFactor);
        if (this.currentTween.frameCount <= 0) {
            if (this.tweenList.length > 0) {
                this.currentTween = this.tweenList.shift();
            } else {
                this.currentTween = null;
            }
        }
    }
}



abstract class MTweenBase {
    private started = false;
    public frameCount: number = 0;
    protected tweenObjet: MTweenObject = null;
    constructor(frameCount: number, tweenObject: MTweenObject) {
        this.tweenObjet = tweenObject;
        this.frameCount = frameCount;
    }

    protected start() { }
    public apply(dtFactor?: number) {
        if (this.started == false) {
            this.start();
            this.started = true;
        }

        this.frameCount--;
    }
}

class LinearTween extends MTweenBase {
    private once: object = null;

    constructor(props: object, frameCount: number, tweenObject: MTweenObject) {
        super(frameCount, tweenObject);
        this.once = props;
    }

    public start() {
        Object.keys(this.once).forEach(key => {
            this.getOnceValue(this.once, key, this.once[key]);
        });
    }

    private getOnceValue(props: object, key: string, value: number) {
        if (typeof value == "undefined" || value == null) return props[key] = value;

        if (this.frameCount == 0) {
            return props[key] = value;
        } else {
            if (typeof this.tweenObjet.target[key] == "number") {
                let delta = value - this.tweenObjet.target[key];
                let once = delta / this.frameCount;
                props[key] = once;
            } else {
                console.error("prop is not number");
                delete props[key];
            }
        }
    }

    public apply(dtFactor: number) {
        super.apply(dtFactor);
        Object.keys(this.once).forEach(key => {
            this.tweenObjet.target[key] += this.once[key] * dtFactor;
        });
    }
}

class WaitTween extends MTweenBase {
    private callback: () => void = null;
    constructor(frameCount: number, tweenObject: MTweenObject, callback?: () => void) {
        super(frameCount, tweenObject);
        this.callback = callback;
    }

    apply() {
        super.apply();
        this.callback && this.callback();
    }
}

class CallbackTween extends MTweenBase {
    private callback: () => void = null;
    constructor(callback: () => void, frameCount: number, tweenObject: MTweenObject) {
        super(frameCount, tweenObject);
        this.callback = callback;
    }

    public start() {
        this.callback();
        this.callback = null;
    }
}
export class MTween extends GameMgr {
    constructor() { super("TweenMgr") }

    private tweenObjectList: MTweenObject[] = [];
    private removeQueue: any[] = [];
    private isTraversing = false;
    public removeTweens(target: object) {
        if (this.isTraversing) {
            this.removeQueue.push(target);
        } else {
            this.tweenObjectList = this.tweenObjectList.filter(e => e.target !== target);
        }
    }

    public get(target: object) {
        let tween = new MTweenObject(target);
        this.tweenObjectList.push(tween);
        return tween;
    }

    public onUpdate() {
        if (this.pause) return;

        this.isTraversing = true;
        for (let i = 0; i <= this.tweenObjectList.length - 1; i++) {
            this.tweenObjectList[i].update();
        }
        this.isTraversing = false;

        this.tweenObjectList = this.tweenObjectList.filter((tween) => {
            if (tween.currentTween == null) return false;

            for (let removeTarget of this.removeQueue) {
                if (removeTarget === tween.target) {
                    return false;
                }
            }

            return true;
        });

        this.removeQueue = [];

    }
}
