"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var errors_1 = require("./errors");
var Semaphore = /** @class */ (function () {
    function Semaphore(_value, _cancelError) {
        if (_cancelError === void 0) { _cancelError = errors_1.E_CANCELED; }
        this._value = _value;
        this._cancelError = _cancelError;
        this._weightedQueues = [];
        this._weightedWaiters = [];
    }
    Semaphore.prototype.acquire = function (weight) {
        var _this = this;
        if (weight === void 0) { weight = 1; }
        if (weight <= 0)
            throw new Error("invalid weight ".concat(weight, ": must be positive"));
        return new Promise(function (resolve, reject) {
            if (!_this._weightedQueues[weight - 1])
                _this._weightedQueues[weight - 1] = [];
            _this._weightedQueues[weight - 1].push({ resolve: resolve, reject: reject });
            _this._dispatch();
        });
    };
    Semaphore.prototype.runExclusive = function (callback, weight) {
        if (weight === void 0) { weight = 1; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _a, value, release;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, this.acquire(weight)];
                    case 1:
                        _a = _b.sent(), value = _a[0], release = _a[1];
                        _b.label = 2;
                    case 2:
                        _b.trys.push([2, , 4, 5]);
                        return [4 /*yield*/, callback(value)];
                    case 3: return [2 /*return*/, _b.sent()];
                    case 4:
                        release();
                        return [7 /*endfinally*/];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    Semaphore.prototype.waitForUnlock = function (weight) {
        var _this = this;
        if (weight === void 0) { weight = 1; }
        if (weight <= 0)
            throw new Error("invalid weight ".concat(weight, ": must be positive"));
        return new Promise(function (resolve) {
            if (!_this._weightedWaiters[weight - 1])
                _this._weightedWaiters[weight - 1] = [];
            _this._weightedWaiters[weight - 1].push(resolve);
            _this._dispatch();
        });
    };
    Semaphore.prototype.isLocked = function () {
        return this._value <= 0;
    };
    Semaphore.prototype.getValue = function () {
        return this._value;
    };
    Semaphore.prototype.setValue = function (value) {
        this._value = value;
        this._dispatch();
    };
    Semaphore.prototype.release = function (weight) {
        if (weight === void 0) { weight = 1; }
        if (weight <= 0)
            throw new Error("invalid weight ".concat(weight, ": must be positive"));
        this._value += weight;
        this._dispatch();
    };
    Semaphore.prototype.cancel = function () {
        var _this = this;
        this._weightedQueues.forEach(function (queue) { return queue.forEach(function (entry) { return entry.reject(_this._cancelError); }); });
        this._weightedQueues = [];
    };
    Semaphore.prototype._dispatch = function () {
        var _a;
        for (var weight = this._value; weight > 0; weight--) {
            var queueEntry = (_a = this._weightedQueues[weight - 1]) === null || _a === void 0 ? void 0 : _a.shift();
            if (!queueEntry)
                continue;
            var previousValue = this._value;
            var previousWeight = weight;
            this._value -= weight;
            weight = this._value + 1;
            queueEntry.resolve([previousValue, this._newReleaser(previousWeight)]);
        }
        this._drainUnlockWaiters();
    };
    Semaphore.prototype._newReleaser = function (weight) {
        var _this = this;
        var called = false;
        return function () {
            if (called)
                return;
            called = true;
            _this.release(weight);
        };
    };
    Semaphore.prototype._drainUnlockWaiters = function () {
        for (var weight = this._value; weight > 0; weight--) {
            if (!this._weightedWaiters[weight - 1])
                continue;
            this._weightedWaiters[weight - 1].forEach(function (waiter) { return waiter(); });
            this._weightedWaiters[weight - 1] = [];
        }
    };
    return Semaphore;
}());
exports.default = Semaphore;
