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

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

    public static get(target: object) {
        return new MTweenObject(target);
    }

    public static removeTweens(target: object) {
        MTweenMgr.remove(target);
    }

    public wait(duration: number) {
        this.addTween(new WaitTween(this.toFrameRate(duration), this));
        return this;
    }

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

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

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

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

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

        this.currentTween.apply();
        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() {
        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 (!value) 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() {
        super.apply();
        Object.keys(this.once).forEach(key => {
            this.tweenObjet.target[key] += this.once[key];
        });
    }
}

class WaitTween extends MTweenBase {
}

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();
    }
}

export class MTweenMgr {
    public static remove(target: object) {
        MTweenMgr.tweenObjectList = MTweenMgr.tweenObjectList.filter(e => e.target !== target);
    }

    public static add(tweenObject: MTweenObject) {
        MTweenMgr.tweenObjectList.push(tweenObject);
    }
    public static tweenObjectList: MTweenObject[] = [];
    public static update() {
        this.tweenObjectList.forEach(e => {
            e.update();
        })
    }
}