import { ICancellation, IDestroyable } from "./interfaces";
import { argumentNotNull, destroyed } from "./safe";

export class Cancellation implements ICancellation {
    private _reason: any;
    private _cbs: Array<(e: any) => void>;

    constructor(action: (cancel: (e?: any) => void) => void) {
        argumentNotNull(action, "action");

        action(this._cancel.bind(this));
    }

    isSupported(): boolean {
        return true;
    }
    throwIfRequested(): void {
        if (this._reason)
            throw this._reason;
    }

    isRequested(): boolean {
        return !!this._reason;
    }

    register(cb: (e: any) => void): IDestroyable {
        argumentNotNull(cb, "cb");

        if (this._reason) {
            cb(this._reason);
            return destroyed;
        } else {
            if (!this._cbs)
                this._cbs = [cb];
            else
                this._cbs.push(cb);

            const me = this;
            return {
                destroy() {
                    me._unregister(cb);
                }
            };
        }
    }

    private _unregister(cb) {
        if (this._cbs) {
            const i = this._cbs.indexOf(cb);
            if (i >= 0)
                this._cbs.splice(i, 1);
        }
    }

    private _cancel(reason) {
        if (this._reason)
            return;

        this._reason = (reason = reason || new Error("Operation cancelled"));

        if (this._cbs) {
            this._cbs.forEach(cb => cb(reason));
            this._cbs = null;
        }
    }

    static readonly none: ICancellation = {
        isSupported(): boolean {
            return false;
        },

        throwIfRequested(): void {
        },

        isRequested(): boolean {
            return false;
        },

        register(_cb: (e: any) => void): IDestroyable {
            return destroyed;
        }
    };
}
