"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.unsafeMakeSemaphore = exports.makeSemaphore = exports.available = exports.withPermitManaged = exports.withPermitsManaged = exports.withPermit = exports.withPermit_ = exports.withPermits = exports.withPermits_ = exports.Semaphore = void 0;
// ets_tracing: off
const L = __importStar(require("../Collections/Immutable/List"));
const Tp = __importStar(require("../Collections/Immutable/Tuple"));
const E = __importStar(require("../Either"));
const Function_1 = require("../Function");
const O = __importStar(require("../Option"));
const R = __importStar(require("../Ref"));
const ImmutableQueue_1 = require("../Support/ImmutableQueue");
const T = __importStar(require("./effect"));
const M = __importStar(require("./managed"));
const P = __importStar(require("./promise"));
const state_1 = require("./state");
/**
 * An asynchronous semaphore, which is a generalization of a mutex. Semaphores
 * have a certain number of permits, which can be held and released
 * concurrently by different parties. Attempts to acquire more permits than
 * available result in the acquiring fiber being suspended until the specified
 * number of permits become available.
 **/
class Semaphore {
    constructor(state) {
        this.state = state;
        this.loop = this.loop.bind(this);
        this.restore = this.restore.bind(this);
        this.releaseN = this.releaseN.bind(this);
        this.restore = this.restore.bind(this);
    }
    get available() {
        return T.map_(this.state.get, E.fold(() => 0, Function_1.identity));
    }
    loop(n, state, acc) {
        // eslint-disable-next-line no-constant-condition
        while (1) {
            switch (state._tag) {
                case "Right": {
                    return Tp.tuple(acc, E.right(n + state.right));
                }
                case "Left": {
                    const d = state.left.dequeue();
                    if (O.isNone(d)) {
                        return Tp.tuple(acc, E.right(n));
                    }
                    else {
                        const { tuple: [{ tuple: [p, m] }, q] } = d.value;
                        if (n > m) {
                            n = n - m;
                            state = E.left(q);
                            acc = T.zipLeft_(acc, P.succeed_(p, undefined));
                        }
                        else if (n === m) {
                            return Tp.tuple(T.zipLeft_(acc, P.succeed_(p, undefined)), E.left(q));
                        }
                        else {
                            return Tp.tuple(acc, E.left(q.prepend(Tp.tuple(p, m - n))));
                        }
                    }
                    break;
                }
            }
        }
        throw new Error("Bug: we should never get here");
    }
    releaseN(toRelease) {
        return T.uninterruptible(T.flatten(T.chain_((0, state_1.assertNonNegative)(toRelease), () => R.modify_(this.state, (s) => this.loop(toRelease, s, T.unit)))));
    }
    restore(p, n) {
        return T.flatten(R.modify_(this.state, E.fold((q) => O.fold_(q.find(({ tuple: [a] }) => a === p), () => Tp.tuple(this.releaseN(n), E.left(q)), (x) => Tp.tuple(this.releaseN(n - x[1]), E.left(q.filter(({ tuple: [a] }) => a !== p)))), (m) => Tp.tuple(T.unit, E.right(n + m)))));
    }
    prepare(n) {
        if (n === 0) {
            return T.succeed(new state_1.Acquisition(T.unit, T.unit));
        }
        else {
            return T.chain_(P.make(), (p) => R.modify_(this.state, E.fold((q) => Tp.tuple(new state_1.Acquisition(P.await(p), this.restore(p, n)), E.left(q.push(Tp.tuple(p, n)))), (m) => {
                if (m >= n) {
                    return Tp.tuple(new state_1.Acquisition(T.unit, this.releaseN(n)), E.right(m - n));
                }
                return Tp.tuple(new state_1.Acquisition(P.await(p), this.restore(p, n)), E.left(new ImmutableQueue_1.ImmutableQueue(L.of(Tp.tuple(p, n - m)))));
            })));
        }
    }
}
exports.Semaphore = Semaphore;
/**
 * Acquires `n` permits, executes the action and releases the permits right after.
 */
function withPermits_(e, s, n) {
    return T.bracket_(s.prepare(n), (a) => T.chain_(a.waitAcquire, () => e), (a) => a.release);
}
exports.withPermits_ = withPermits_;
/**
 * Acquires `n` permits, executes the action and releases the permits right after.
 *
 * @ets_data_first withPermits_
 */
function withPermits(s, n) {
    return (e) => T.bracket_(s.prepare(n), (a) => T.chain_(a.waitAcquire, () => e), (a) => a.release);
}
exports.withPermits = withPermits;
/**
 * Acquires a permit, executes the action and releases the permit right after.
 */
function withPermit_(self, s) {
    return withPermits_(self, s, 1);
}
exports.withPermit_ = withPermit_;
/**
 * Acquires a permit, executes the action and releases the permit right after.
 *
 * @ets_data_first withPermit_
 */
function withPermit(s) {
    return (self) => withPermit_(self, s);
}
exports.withPermit = withPermit;
/**
 * Acquires `n` permits in a `Managed` and releases the permits in the finalizer.
 */
function withPermitsManaged(s, n) {
    return M.makeReserve(T.map_(s.prepare(n), (a) => M.makeReservation_(a.waitAcquire, () => a.release)));
}
exports.withPermitsManaged = withPermitsManaged;
/**
 * Acquires a permit in a `Managed` and releases the permit in the finalizer.
 */
function withPermitManaged(s) {
    return withPermitsManaged(s, 1);
}
exports.withPermitManaged = withPermitManaged;
/**
 * The number of permits currently available.
 */
function available(s) {
    return s.available;
}
exports.available = available;
/**
 * Creates a new `Sempahore` with the specified number of permits.
 */
function makeSemaphore(permits) {
    return T.map_(R.makeRef(E.right(permits)), (state) => new Semaphore(state));
}
exports.makeSemaphore = makeSemaphore;
/**
 * Creates a new `Sempahore` with the specified number of permits.
 */
function unsafeMakeSemaphore(permits) {
    const state = R.unsafeMakeRef(E.right(permits));
    return new Semaphore(state);
}
exports.unsafeMakeSemaphore = unsafeMakeSemaphore;
//# sourceMappingURL=semaphore.js.map