﻿import { wait } from "../services/timedeferred";

type QueueWorker = () => Promise<any>;

export class GameActionsQueue {
    public static logging: boolean = false;
    public static waitDisabled = false;

    /**
     * List of pending tasks
     */
    private tasks: QueueWorker[];

    /**
     * Task counter
     */
    private counter: number;

    /**
     * Executing flag.
     */
    private isExecuting: boolean;

    /**
     * Initializes a new instance of the GameActionsQueue class.
     */
    constructor() {
        this.tasks = [];
        this.counter = 0;
    }
    /**
     * Inject worker to the beginning of tasks stack
     * @param worker QueueWorker The function which generated promise.
     */
    public inject(worker: QueueWorker) {
        this.tasks.unshift(worker);
        this.execute();
    }

    /**
     * Push worker to the end of tasks stack
     * @param worker QueueWorker The function which generated promise.
     */
    public push(worker: QueueWorker) {
        this.tasks.push(worker);
        this.execute();
    }

    /**
     * Push waiting task in the queue
     * @param timeout Number The function which generated promise.
     */
    public wait(timeout: number) {
        if (!GameActionsQueue.waitDisabled && timeout > 0) {
            this.push(() => {
                return wait(timeout);
            });
        }
    }

    /**
     * Push waiting task in the queue
     * @param timeout Number The function which generated promise.
     */
    public waitWithInterruption(timeout: number) {
        if (!GameActionsQueue.waitDisabled && timeout > 0) {
            // Split wait interval by 100ms tasks which allow
            // inject other tasks and don't wait until everything is finished.
            for (let i = 0; i < timeout / 200; i++) {
                this.push(() => {
                    return wait(200);
                });
            }
        }
    }

    /**
     * Push waiting task in the queue
     * @param timeout Number The function which generated promise.
     */
    public injectWait(timeout: number) {
        if (!GameActionsQueue.waitDisabled && timeout > 0) {
            this.inject(() => {
                return wait(timeout);
            });
        }
    }

    /**
     * Push waiting task in the queue
     * @param timeout Number The function which generated promise.
     */
    public injectWaitWithInterruption(timeout: number) {
        if (!GameActionsQueue.waitDisabled && timeout > 0) {
            // Split wait interval by 100ms tasks which allow
            // inject other tasks and don't wait until everything is finished.
            for (let i = 0; i < timeout / 200; i++) {
                this.inject(() => {
                    return wait(200);
                });
            }
        }
    }

    /**
     * Push worker to the stack
     * @param callback Function The function which generated promise.
     */
    public injectCallback(callback: () => void) {
        this.inject(() => {
            return Promise.resolve().then(() => callback());
        });
    }

    /**
     * Push worker to the stack
     * @param callback Function The function which generated promise.
     */
    public pushCallback(callback: () => void) {
        this.push(() => {
            return Promise.resolve().then(() => callback());
        });
    }

    /**
     * Clears execution chain.
     */
    public clear() {
        this.tasks = [];
    }

    /**
     * Starts execution chain.
     */
    public async execute() {
        if (this.isExecuting) {
            this.log("Currently task is executing, task will be executed later.");
            return;
        }

        if (this.tasks.length === 0) {
            this.log("No task to execute.");
            return;
        }

        const self = this;
        const worker = this.tasks.shift();
        if (worker === null) {
            this.error("Worker is null");
        }

        this.counter++;
        this.isExecuting = true;
        const task = worker();
        if (task === null) {
            this.error("Task is null");
        }

        this.log("Executing task with id: " + this.counter.toString());
        await task;
        self.isExecuting = false;
        self.log("Finished task with id: " + self.counter.toString());
        setTimeout(function () {
            self.execute();
        }, 100);
    }

    private log(message?: any, ...optionalParams: any[]) {
        if (GameActionsQueue.logging) {
            console.log(message, optionalParams);
        }
    }

    private error(message?: any, ...optionalParams: any[]) {
        if (GameActionsQueue.logging) {
            console.error(message, optionalParams);
        }
    }
}
