/**
 * 回呼函數: fnname (arg: TArg): void
 */
interface ActionCallback<TArg> {
    (arg: TArg): void;
}

interface Struct<TArg> {
    callback: ActionCallback<TArg>;
    target: any;
    once?: boolean;
}

export class Action<TArg> {
    private _queue: Struct<TArg>[] = [];

    /**
     * 監聽事件
     * @param callback 回呼函數: fnname (arg: TArg): void
     * @param bindTarget 回呼時this綁定的對象
     */
    AddCallback(callback: ActionCallback<TArg>, bindTarget?: any) {
        let q = <Struct<TArg>> {
            callback: callback,
            target: bindTarget
        };
        this._queue.push(q);
    }

    /**
     * 監聽事件 (一次性)
     * @param callback 回呼函數: fnname (arg: TArg): void
     * @param bindTarget 回呼時this綁定的對象
     */
    AddCallbackOnce(callback: ActionCallback<TArg>, bindTarget?: any) {
        let q = <Struct<TArg>> {
            callback: callback,
            target: bindTarget,
            once: true
        };
        this._queue.push(q);
    }

    /**
     * 移除事件
     * @param callback 
     */
    RemoveByCallback(callback: ActionCallback<TArg>) {
        let index = this._queue.length;
        if (index > 0) {
            while (index--) {
                let q = this._queue[index];
                if (!q.callback || q.callback === callback) {
                    q.callback = undefined;
                    this._queue.splice(index, 1);
                }
            }
        }
    }
    
    /**
     * 移除事件
     * @param bindTarget 回呼時this綁定的對象
     */
    RemoveByBindTarget(bindTarget: any) {
        let index = this._queue.length;
        if (index > 0) {
            while (index--) {
                let q = this._queue[index];
                if (!q.callback || q.target === bindTarget) {
                    q.callback = undefined;
                    this._queue.splice(index, 1);
                }
            }
        }
    }

    /**
     * 移除全部事件
     */
    RemoveAllCallbacks() {
        this._queue.forEach(q => q.callback = undefined);
        this._queue.length = 0;
    }

    /**
     * 發送事件
     * @param arg 參數
     */
    DispatchCallback(arg: TArg) {
        let index = this._queue.length;
        if (index > 0) {
            let cleanRemoved = false;
            this._queue.slice().forEach(q => {
                if (!q.callback) {
                    cleanRemoved = true;
                    return;
                }

                if (q.target) {
                    q.callback.call(q.target, arg);
                } else {
                    q.callback(arg);
                }

                if (q.once) {
                    q.callback = undefined;
                    cleanRemoved = true;
                }
            });

            if (cleanRemoved) {
                index = this._queue.length;
                if (index > 0) {
                    while (index--) {
                        let q = this._queue[index];
                        if (!q.callback) {
                            this._queue.splice(index, 1);
                        }
                    }
                }
            }
        }
    }
}