/**
 * Task abstraction for wed.
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
define(["require", "exports", "rxjs", "rxjs/operators"], function (require, exports, rxjs_1, operators_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    /**
     * A task is a computation that should produce a definite goal after a finite
     * time. This class is used to allow the task to happen in a way that does not
     * completely block the JavaScript virtual machine. The task will happen in
     * cycles that run for a maximum amount of time before relinquishing control.
     */
    class TaskRunner {
        /**
         * @param task The computation controlled by this runner.
         *
         * @param options The options governing this runner.
         */
        constructor(task, options = {}) {
            this.task = task;
            this._timeout = 0;
            this._maxTimespan = 100;
            this._boundWrapper = this._workWrapper.bind(this);
            const keys = ["timeout", "maxTimespan"];
            for (const key of keys) {
                const value = options[key];
                if (value === undefined) {
                    continue;
                }
                if (value < 0) {
                    throw new Error(`the value for ${key} cannot be negative`);
                }
                // tslint:disable-next-line:no-any
                this[`_${key}`] = options[key];
            }
            this._state = new rxjs_1.BehaviorSubject({
                running: false,
                completed: false,
                terminated: false,
            });
            this.state = this._state.asObservable();
        }
        get running() {
            return this._state.value.running;
        }
        get completed() {
            return this._state.value.completed;
        }
        get terminated() {
            return this._state.value.terminated;
        }
        onCompleted() {
            return this.state.pipe(operators_1.first((state) => state.completed)).toPromise();
        }
        _stateFieldChange(field, value) {
            const latest = this._state.value;
            const newState = Object.assign({}, this._state.value);
            newState[field] = value;
            if (newState[field] !== latest[field]) {
                this._state.next(newState);
            }
        }
        _setTimeoutId(value) {
            this._timeoutId = value;
            this._stateFieldChange("running", this._timeoutId !== undefined);
        }
        /**
         * Marks the task as incomplete and starts processing.
         */
        start() {
            this.reset();
            this.resume();
        }
        /**
         * Resets the task to its initial state. The task will be deemed incomplete.
         */
        reset() {
            this._stateFieldChange("completed", false);
            this.task.reset(this);
        }
        /**
         * Resumes the task. This method does not change the completion status of the
         * task. So it is possible to stop a task temporarily and resume it later from
         * where it stopped.
         */
        resume() {
            if (this.completed) {
                return;
            }
            if (this._timeoutId !== undefined) {
                this.stop();
            }
            // When we call ``this.resume``, we want the task to resume ASAP. So we do
            // not use ``this._timeout`` here. However, we do not call
            // ``this._workWrapper`` directly because we want to be able to call
            // ``this.resume`` from event handlers. If we did call ``this._workWrapper``
            // directly, we'd be calling this._cycle from inside this._cycle
            this._setTimeoutId(setTimeout(this._boundWrapper, 0));
        }
        /**
         * Convenience method. The bound version of this method
         * (``this._boundWrapper``) is what is called by the timeouts.
         */
        _workWrapper() {
            if (this._work()) {
                this._setTimeoutId(setTimeout(this._boundWrapper, this._timeout));
            }
            else {
                this._stateFieldChange("completed", true);
            }
        }
        /**
         * Keeps the task running by launching cycles only until done or until the
         * maximum time span for one run is reached.
         *
         * @returns False if there is no more work to do. True otherwise.
         */
        _work() {
            const startDate = Date.now();
            // tslint:disable-next-line:strict-boolean-expressions no-constant-condition
            while (true) {
                // Give a chance to other operations to work.
                if ((this._maxTimespan > 0) &&
                    (Date.now() - startDate) >= this._maxTimespan) {
                    return true;
                }
                const ret = this.task.cycle(this);
                if (!ret) {
                    return false;
                }
            }
        }
        /**
         * Stops the task.
         */
        stop() {
            if (this._timeoutId !== undefined) {
                clearTimeout(this._timeoutId);
            }
            this._setTimeoutId(undefined);
        }
        /**
         * Terminate the task.
         */
        terminate() {
            this.stop();
            this._stateFieldChange("terminated", true);
            this._state.complete();
        }
    }
    exports.TaskRunner = TaskRunner;
});
//  LocalWords:  MPL maxTimespan workWrapper
//# sourceMappingURL=task-runner.js.map