// @flow strict
import assert from 'assert';

export type TaskID = number; // can't mark opaque due to https://github.com/flowtype/flow-remove-types/pull/61
type Task = {
    callback: (timeStamp: number) => void;
    id: TaskID;
    cancelled: boolean;
};

class TaskQueue {
    _queue: Array<Task>;
    _id: TaskID;
    _cleared: boolean;
    _currentlyRunning: Array<Task> | false;

    constructor()  {
        this._queue = [];
        this._id = 0;
        this._cleared = false;
        this._currentlyRunning = false;
    }

    add(callback: (timeStamp: number) => void): TaskID {
        const id = ++this._id;
        const queue = this._queue;
        queue.push({callback, id, cancelled: false});
        return id;
    }

    remove(id: TaskID) {
        const running = this._currentlyRunning;
        const queue = running ? this._queue.concat(running) : this._queue;
        for (const task of queue) {
            if (task.id === id) {
                task.cancelled = true;
                return;
            }
        }
    }

    run(timeStamp: number = 0) {
        assert(!this._currentlyRunning);
        const queue = this._currentlyRunning = this._queue;

        // Tasks queued by callbacks in the current queue should be executed
        // on the next run, not the current run.
        this._queue = [];

        for (const task of queue) {
            if (task.cancelled) continue;
            task.callback(timeStamp);
            if (this._cleared) break;
        }

        this._cleared = false;
        this._currentlyRunning = false;
    }

    clear() {
        if (this._currentlyRunning) {
            this._cleared = true;
        }
        this._queue = [];
    }
}

export default TaskQueue;
