import { __read, __values } from "tslib";
import { createCancelError, isCancelError } from "./cancellation";
/**
 * Concurrency limiter for async operations.
 *
 * @export
 * @class Limiter
 */
var Limiter = /** @class */ (function () {
    function Limiter(concurrency) {
        var _this = this;
        this.concurrency = concurrency;
        this.functionQueue = [];
        this.active = 0;
        this.emptyCallbacks = [];
        /**
         * Create a promise which resolves when all queued functions have been executed.
         *
         */
        this.empty = function () {
            if (_this.isDone()) {
                return Promise.resolve();
            }
            return new Promise(function (resolve) { return _this.emptyCallbacks.push(resolve); });
        };
        /**
         * Adds a function to the queue
         *
         */
        this.queue = function (func) {
            if (_this.active < _this.concurrency) {
                _this.active++;
                return func()
                    .catch(function (err) {
                    _this.next(undefined);
                    return Promise.reject(err);
                })
                    .then(_this.next);
            }
            return new Promise(function (resolve, reject) {
                _this.functionQueue.push(function (canceled) {
                    return canceled
                        ? reject(createCancelError("Method has been canceled"))
                        : _this.queue(func)
                            .then(resolve)
                            .catch(reject);
                });
            });
        };
        /**
         * Removes and rejects all queued functions.
         *
         * Does not stop any currently executing functions.
         *
         */
        this.clearQueue = function () {
            var e_1, _a;
            try {
                for (var _b = __values(_this.functionQueue), _c = _b.next(); !_c.done; _c = _b.next()) {
                    var entry = _c.value;
                    entry(true);
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
                }
                finally { if (e_1) throw e_1.error; }
            }
            _this.functionQueue = [];
            if (_this.isDone()) {
                _this.triggerEmptyCallbacks();
            }
        };
        this.next = function (res) {
            _this.active--;
            if (_this.isDone() && _this.emptyCallbacks.length) {
                _this.triggerEmptyCallbacks();
            }
            if (_this.active < _this.concurrency && _this.functionQueue.length) {
                var _a = __read(_this.functionQueue.splice(0, 1), 1), nextItem = _a[0];
                nextItem();
            }
            return Promise.resolve(res);
        };
    }
    Limiter.prototype.isCancelError = function (error) {
        return isCancelError(error);
    };
    Limiter.prototype.triggerEmptyCallbacks = function () {
        var e_2, _a;
        try {
            for (var _b = __values(this.emptyCallbacks), _c = _b.next(); !_c.done; _c = _b.next()) {
                var func = _c.value;
                func();
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_2) throw e_2.error; }
        }
        this.emptyCallbacks = [];
    };
    Limiter.prototype.isDone = function () {
        return this.active === 0 && this.functionQueue.length === 0;
    };
    return Limiter;
}());
export { Limiter };
