"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.ChannelExecutor = exports.maybeCloseBoth = exports.channelStateUnroll = exports.channelStateEffect = exports.ChannelStateEffect = exports.ChannelStateEffectTypeId = exports.ChannelStateEmit = exports.ChannelStateEmitTypeId = exports.ChannelStateDone = exports.ChannelStateDoneTypeId = exports.Inner = exports.InnerTypeId = exports.FromKAnd = exports.FromKAndTypeId = void 0;
// ets_tracing: off
require("../../../../Operator");
const Cause = __importStar(require("../../../../Cause"));
const L = __importStar(require("../../../../Collections/Immutable/List"));
const T = __importStar(require("../../../../Effect"));
const Either = __importStar(require("../../../../Either"));
const Exit = __importStar(require("../../../../Exit"));
const F = __importStar(require("../../../../Fiber"));
const Function_1 = require("../../../../Function");
const O = __importStar(require("../../../../Option"));
const P = __importStar(require("./primitives"));
exports.FromKAndTypeId = Symbol();
class FromKAnd {
    constructor(fromK, rest) {
        this.fromK = fromK;
        this.rest = rest;
        this._typeId = exports.FromKAndTypeId;
    }
}
exports.FromKAnd = FromKAnd;
exports.InnerTypeId = Symbol();
class Inner {
    constructor(exec, subK, lastDone, combineSubK, combineSubKAndInner) {
        this.exec = exec;
        this.subK = subK;
        this.lastDone = lastDone;
        this.combineSubK = combineSubK;
        this.combineSubKAndInner = combineSubKAndInner;
        this._typeId = exports.InnerTypeId;
    }
    close(ex) {
        const fin = this.exec.close(ex);
        if (fin) {
            return T.result(fin);
        }
    }
}
exports.Inner = Inner;
exports.ChannelStateDoneTypeId = Symbol();
class ChannelStateDone {
    constructor() {
        this._typeId = exports.ChannelStateDoneTypeId;
    }
}
exports.ChannelStateDone = ChannelStateDone;
exports.ChannelStateEmitTypeId = Symbol();
class ChannelStateEmit {
    constructor() {
        this._typeId = exports.ChannelStateEmitTypeId;
    }
}
exports.ChannelStateEmit = ChannelStateEmit;
exports.ChannelStateEffectTypeId = Symbol();
class ChannelStateEffect {
    constructor(effect) {
        this.effect = effect;
        this._typeId = exports.ChannelStateEffectTypeId;
    }
}
exports.ChannelStateEffect = ChannelStateEffect;
const _ChannelStateDone = new ChannelStateDone();
const _ChannelStateEmit = new ChannelStateEmit();
function channelStateEffect(state) {
    if ((state === null || state === void 0 ? void 0 : state._typeId) === exports.ChannelStateEffectTypeId) {
        return state.effect;
    }
    return T.unit;
}
exports.channelStateEffect = channelStateEffect;
function channelStateUnroll(runStep) {
    const step = runStep();
    switch (step._typeId) {
        case exports.ChannelStateEffectTypeId: {
            return T.chain_(step.effect, () => channelStateUnroll(runStep));
        }
        case exports.ChannelStateDoneTypeId: {
            return T.succeed(Either.right(_ChannelStateDone));
        }
        case exports.ChannelStateEmitTypeId: {
            return T.succeed(Either.left(_ChannelStateEmit));
        }
    }
}
exports.channelStateUnroll = channelStateUnroll;
function maybeCloseBoth(l, r) {
    if (l && r) {
        return T.zipWith_(T.result(l), T.result(r), (a, b) => Exit.zipRight_(a, b));
    }
    else if (l) {
        return T.result(l);
    }
    else if (r) {
        return T.result(r);
    }
}
exports.maybeCloseBoth = maybeCloseBoth;
const endUnit = new P.Done(() => void 0);
class ChannelExecutor {
    constructor(initialChannel, providedEnv) {
        this.providedEnv = providedEnv;
        this.doneStack = L.empty();
        this.currentChannel = initialChannel();
    }
    restorePipe(exit, prev) {
        const currInput = this.input;
        this.input = prev;
        return currInput === null || currInput === void 0 ? void 0 : currInput.close(exit);
    }
    unwindAllFinalizers(acc, conts, exit) {
        while (!L.isEmpty(conts)) {
            const head = L.unsafeFirst(conts);
            ;
            if (head._typeId === P.ContinuationKTypeId) {
                conts = L.tail(conts);
            }
            else {
                return T.chain_(T.result(head.finalizer(exit)), (finExit) => this.unwindAllFinalizers(Exit.zipRight_(acc, finExit), L.tail(conts), exit));
            }
        }
        return T.done(acc);
    }
    popAllFinalizers(exit) {
        const effect = T.result(this.unwindAllFinalizers(Exit.unit, this.doneStack, exit));
        this.doneStack = L.empty();
        this.storeInProgressFinalizer(effect);
        return effect;
    }
    popNextFinalizersGo(stack, builder) {
        while (!L.isEmpty(stack)) {
            const head = L.unsafeFirst(stack);
            ;
            if (head._typeId === P.ContinuationKTypeId) {
                return stack;
            }
            L.push_(builder, head);
            stack = L.tail(stack);
        }
        return L.empty();
    }
    popNextFinalizers() {
        const builder = L.emptyPushable();
        this.doneStack = this.popNextFinalizersGo(this.doneStack, builder);
        return builder;
    }
    storeInProgressFinalizer(effect) {
        this.inProgressFinalizer = effect;
    }
    clearInProgressFinalizer() {
        this.inProgressFinalizer = undefined;
    }
    ifNotNull(effect) {
        return effect ? effect : T.succeed(Exit.unit);
    }
    close(ex) {
        const runInProgressFinalizer = this.inProgressFinalizer
            ? T.ensuring_(this.inProgressFinalizer, T.succeedWith(() => this.clearInProgressFinalizer()))
            : undefined;
        let closeSubexecutors;
        if (this.subexecutorStack) {
            if (this.subexecutorStack._typeId === exports.InnerTypeId) {
                closeSubexecutors = this.subexecutorStack.close(ex);
            }
            else {
                const fin1 = this.subexecutorStack.fromK.close(ex);
                const fin2 = this.subexecutorStack.rest.close(ex);
                if (fin1 && fin2) {
                    closeSubexecutors = T.zipWith_(T.result(fin1), T.result(fin2), (a, b) => Exit.zipRight_(a, b));
                }
                else if (fin1) {
                    closeSubexecutors = T.result(fin1);
                }
                else if (fin2) {
                    closeSubexecutors = T.result(fin2);
                }
            }
        }
        let closeSelf;
        const selfFinalizers = this.popAllFinalizers(ex);
        if (selfFinalizers) {
            closeSelf = T.ensuring_(selfFinalizers, T.succeedWith(() => this.clearInProgressFinalizer()));
        }
        if (closeSubexecutors || runInProgressFinalizer || closeSelf) {
            return T.uninterruptible(T.map_(T.tuple(this.ifNotNull(closeSubexecutors), this.ifNotNull(runInProgressFinalizer), this.ifNotNull(closeSelf)), ({ tuple: [a, b, c] }) => Exit.zipRight_(a, Exit.zipRight_(b, c))));
        }
    }
    getDone() {
        return this.done;
    }
    getEmit() {
        return this.emitted;
    }
    cancelWith(exit) {
        this.cancelled = exit;
    }
    run() {
        let result = undefined;
        while (!result) {
            if (this.cancelled) {
                result = this.processCancellation();
            }
            else if (this.subexecutorStack) {
                result = this.drainSubexecutor();
            }
            else {
                if (!this.currentChannel) {
                    result = _ChannelStateDone;
                }
                else {
                    ;
                    const currentChannel = this.currentChannel;
                    switch (currentChannel._typeId) {
                        case P.BridgeTypeId: {
                            this.currentChannel = currentChannel.channel;
                            if (this.input) {
                                const inputExecutor = this.input;
                                this.input = undefined;
                                const drainer = T.suspend(() => {
                                    const state = inputExecutor.run();
                                    switch (state._typeId) {
                                        case exports.ChannelStateEmitTypeId: {
                                            return T.chain_(currentChannel.input.emit(inputExecutor.getEmit()), () => drainer);
                                        }
                                        case exports.ChannelStateEffectTypeId: {
                                            return T.foldCauseM_(state.effect, (cause) => currentChannel.input.error(cause), () => drainer);
                                        }
                                        case exports.ChannelStateDoneTypeId: {
                                            const done = inputExecutor.getDone();
                                            return done._tag === "Success"
                                                ? currentChannel.input.done(done.value)
                                                : currentChannel.input.error(done.cause);
                                        }
                                    }
                                });
                                result = new ChannelStateEffect(T.chain_(T.fork(drainer), (fiber) => T.succeedWith(() => {
                                    this.addFinalizer(new P.ContinuationFinalizer((exit) => T.chain_(F.interrupt(fiber), () => T.suspend(() => this.restorePipe(exit, inputExecutor) || T.unit))));
                                })));
                            }
                            break;
                        }
                        case P.PipeToTypeId: {
                            const previousInput = this.input;
                            const leftExec = new ChannelExecutor(currentChannel.left, this.providedEnv);
                            leftExec.input = previousInput;
                            this.input = leftExec;
                            this.addFinalizer(new P.ContinuationFinalizer((exit) => this.restorePipe(exit, previousInput) || T.unit));
                            this.currentChannel = currentChannel.right();
                            break;
                        }
                        case P.ReadTypeId: {
                            result = this.runRead(currentChannel);
                            break;
                        }
                        case P.DoneTypeId: {
                            result = this.doneSucceed(currentChannel.terminal());
                            break;
                        }
                        case P.HaltTypeId: {
                            result = this.doneHalt(currentChannel.error());
                            break;
                        }
                        case P.EffectTypeId: {
                            const peffect = typeof this.providedEnv !== "undefined"
                                ? T.provideAll_(currentChannel.effect, this.providedEnv)
                                : currentChannel.effect;
                            result = new ChannelStateEffect(T.foldCauseM_(peffect, (cause) => {
                                const res = this.doneHalt(cause);
                                if ((res === null || res === void 0 ? void 0 : res._typeId) === exports.ChannelStateEffectTypeId) {
                                    return res.effect;
                                }
                                else {
                                    return T.unit;
                                }
                            }, (z) => {
                                const res = this.doneSucceed(z);
                                if ((res === null || res === void 0 ? void 0 : res._typeId) === exports.ChannelStateEffectTypeId) {
                                    return res.effect;
                                }
                                else {
                                    return T.unit;
                                }
                            }));
                            break;
                        }
                        case P.EmitTypeId: {
                            this.emitted = currentChannel.out();
                            this.currentChannel = endUnit;
                            result = _ChannelStateEmit;
                            break;
                        }
                        case P.EnsuringTypeId: {
                            this.addFinalizer(new P.ContinuationFinalizer((e) => currentChannel.finalizer(e)));
                            this.currentChannel = currentChannel.channel;
                            break;
                        }
                        case P.ConcatAllTypeId: {
                            const exec = new ChannelExecutor(() => currentChannel.value, this.providedEnv);
                            exec.input = this.input;
                            this.subexecutorStack = new Inner(exec, currentChannel.k, undefined, currentChannel.combineInners, currentChannel.combineAll);
                            this.currentChannel = undefined;
                            break;
                        }
                        case P.FoldTypeId: {
                            this.doneStack = L.prepend_(this.doneStack, currentChannel.k);
                            this.currentChannel = currentChannel.value;
                            break;
                        }
                        case P.BracketOutTypeId: {
                            result = this.runBracketOut(currentChannel);
                            break;
                        }
                        case P.ProvideTypeId: {
                            const previousEnv = this.providedEnv;
                            this.providedEnv = currentChannel.env;
                            this.currentChannel = currentChannel.channel;
                            this.addFinalizer(new P.ContinuationFinalizer(() => T.succeedWith(() => {
                                this.providedEnv = previousEnv;
                            })));
                            break;
                        }
                        case P.EffectTotalTypeId: {
                            result = this.doneSucceed(currentChannel.effect());
                            break;
                        }
                        case P.EffectSuspendTotalTypeId: {
                            this.currentChannel = currentChannel.effect();
                            break;
                        }
                    }
                }
            }
        }
        return result;
    }
    runReadGo(state, read, input) {
        switch (state._typeId) {
            case exports.ChannelStateEmitTypeId: {
                return T.succeedWith(() => {
                    this.currentChannel = read.more(input.getEmit());
                });
            }
            case exports.ChannelStateDoneTypeId: {
                return T.succeedWith(() => {
                    this.currentChannel = read.done.onExit(input.getDone());
                });
            }
            case exports.ChannelStateEffectTypeId: {
                return T.foldCauseM_(state.effect, (cause) => T.succeedWith(() => {
                    this.currentChannel = read.done.onHalt(cause);
                }), () => this.runReadGo(input.run(), read, input));
            }
        }
    }
    runRead(read) {
        if (this.input) {
            const input = this.input;
            const state = input.run();
            switch (state._typeId) {
                case exports.ChannelStateEmitTypeId: {
                    this.currentChannel = read.more(input.getEmit());
                    return;
                }
                case exports.ChannelStateDoneTypeId: {
                    this.currentChannel = read.done.onExit(input.getDone());
                    return;
                }
                case exports.ChannelStateEffectTypeId: {
                    return new ChannelStateEffect(T.foldCauseM_(state.effect, (cause) => T.succeedWith(() => {
                        this.currentChannel = read.done.onHalt(cause);
                    }), () => this.runReadGo(input.run(), read, input)));
                }
            }
        }
        else {
            this.currentChannel = read.more(void 0);
        }
    }
    runBracketOut(bracketOut) {
        return new ChannelStateEffect(T.uninterruptibleMask((mask) => T.foldCauseM_(mask.restore(bracketOut.acquire), (cause) => T.succeedWith(() => {
            this.currentChannel = new P.Halt(() => cause);
        }), (out) => T.succeedWith(() => {
            this.addFinalizer(new P.ContinuationFinalizer((e) => bracketOut.finalizer(out, e)));
            this.currentChannel = new P.Emit(() => out);
        }))));
    }
    addFinalizer(f) {
        this.doneStack = L.prepend_(this.doneStack, f);
    }
    drainSubexecutor() {
        const subexecutorStack = this.subexecutorStack;
        if (subexecutorStack._typeId === exports.InnerTypeId) {
            return this.drainInnerSubExecutor(subexecutorStack);
        }
        else {
            return this.drainFromKAndSubexecutor(subexecutorStack.fromK, subexecutorStack.rest);
        }
    }
    handleSubexecFailure(exec, rest, self, cause) {
        const closeEffect = maybeCloseBoth(exec.close(Exit.halt(cause)), rest.exec.close(Exit.halt(cause)));
        return self.finishSubexecutorWithCloseEffect(Exit.halt(cause), closeEffect ? T.chain_(closeEffect, T.done) : undefined);
    }
    drainFromKAndSubexecutor(exec, rest) {
        const run = exec.run();
        switch (run._typeId) {
            case exports.ChannelStateEffectTypeId: {
                return new ChannelStateEffect(T.catchAllCause_(run.effect, (cause) => channelStateEffect(this.handleSubexecFailure(exec, rest, this, cause))));
            }
            case exports.ChannelStateEmitTypeId: {
                this.emitted = exec.getEmit();
                return _ChannelStateEmit;
            }
            case exports.ChannelStateDoneTypeId: {
                const done = exec.getDone();
                switch (done._tag) {
                    case "Failure": {
                        return this.handleSubexecFailure(exec, rest, this, done.cause);
                    }
                    case "Success": {
                        const closeEffect = exec.close(done);
                        const modifiedRest = new Inner(rest.exec, rest.subK, rest.lastDone ? rest.combineSubK(rest.lastDone, done.value) : done.value, rest.combineSubK, rest.combineSubKAndInner);
                        if (closeEffect) {
                            return new ChannelStateEffect(T.foldCauseM_(closeEffect, (cause) => {
                                const restClose = rest.exec.close(Exit.halt(cause));
                                if (restClose) {
                                    return T.foldCauseM_(restClose, (restCause) => this.finishWithExit(Exit.halt(Cause.then(cause, restCause))), () => this.finishWithExit(Exit.halt(cause)));
                                }
                                else {
                                    return this.finishWithExit(Exit.halt(cause));
                                }
                            }, () => T.succeedWith(() => this.replaceSubexecutor(modifiedRest))));
                        }
                        else {
                            this.replaceSubexecutor(modifiedRest);
                            return undefined;
                        }
                    }
                }
            }
        }
    }
    replaceSubexecutor(nextSubExec) {
        this.currentChannel = undefined;
        this.subexecutorStack = nextSubExec;
    }
    finishSubexecutorWithCloseEffect(subexecDone, closeEffect) {
        if (closeEffect) {
            return new ChannelStateEffect(T.foldCauseM_(closeEffect, (cause) => this.finishWithExit(Exit.halt(Cause.then(Exit.fold_(subexecDone, Function_1.identity, () => Cause.empty), cause))), () => this.finishWithExit(subexecDone)));
        }
        else {
            const state = Exit.fold_(subexecDone, (e) => this.doneHalt(e), (a) => this.doneSucceed(a));
            this.subexecutorStack = undefined;
            return state;
        }
    }
    finishWithExit(exit) {
        const state = Exit.fold_(exit, (e) => this.doneHalt(e), (a) => this.doneSucceed(a));
        this.subexecutorStack = undefined;
        if (state) {
            return channelStateEffect(state);
        }
        else {
            return T.unit;
        }
    }
    doneSucceed(z) {
        if (L.isEmpty(this.doneStack)) {
            this.done = Exit.succeed(z);
            this.currentChannel = undefined;
            return _ChannelStateDone;
        }
        const head = L.unsafeFirst(this.doneStack);
        ;
        if (head._typeId === P.ContinuationKTypeId) {
            this.doneStack = L.tail(this.doneStack);
            this.currentChannel = head.onSuccess(z);
            return;
        }
        else {
            const finalizers = this.popNextFinalizers();
            if (L.isEmpty(this.doneStack)) {
                this.doneStack = finalizers;
                this.done = Exit.succeed(z);
                this.currentChannel = undefined;
                return _ChannelStateDone;
            }
            else {
                const finalizerEffect = this.runFinalizers(L.map_(finalizers, (_) => _.finalizer), Exit.succeed(z));
                this.storeInProgressFinalizer(finalizerEffect);
                return new ChannelStateEffect(T.chain_(T.uninterruptible(T.ensuring_(finalizerEffect, T.succeedWith(() => {
                    this.clearInProgressFinalizer();
                }))), () => T.succeedWith(() => this.doneSucceed(z))));
            }
        }
    }
    runFinalizers(finalizers, ex) {
        if (L.isEmpty(finalizers)) {
            return T.succeed(Exit.unit);
        }
        return T.map_(T.forEach_(finalizers, (cont) => T.result(cont(ex))), (results) => O.getOrElse_(Exit.collectAll(...results), () => Exit.unit));
    }
    doneHalt(cause) {
        if (L.isEmpty(this.doneStack)) {
            this.done = Exit.halt(cause);
            this.currentChannel = undefined;
            return _ChannelStateDone;
        }
        const head = L.unsafeFirst(this.doneStack);
        ;
        if (head._typeId === P.ContinuationKTypeId) {
            this.doneStack = L.tail(this.doneStack);
            this.currentChannel = head.onHalt(cause);
            return;
        }
        else {
            const finalizers = this.popNextFinalizers();
            if (L.isEmpty(this.doneStack)) {
                this.doneStack = finalizers;
                this.done = Exit.halt(cause);
                this.currentChannel = undefined;
                return _ChannelStateDone;
            }
            else {
                const finalizerEffect = this.runFinalizers(L.map_(finalizers, (_) => _.finalizer), Exit.halt(cause));
                this.storeInProgressFinalizer(finalizerEffect);
                return new ChannelStateEffect(T.chain_(T.uninterruptible(T.ensuring_(finalizerEffect, T.succeedWith(() => {
                    this.clearInProgressFinalizer();
                }))), () => T.succeedWith(() => this.doneHalt(cause))));
            }
        }
    }
    drainInnerSubExecutor(inner) {
        const run = inner.exec.run();
        switch (run._typeId) {
            case exports.ChannelStateEmitTypeId: {
                const fromK = new ChannelExecutor(() => inner.subK(inner.exec.getEmit()), this.providedEnv);
                fromK.input = this.input;
                this.subexecutorStack = new FromKAnd(fromK, inner);
                return undefined;
            }
            case exports.ChannelStateDoneTypeId: {
                const done = inner.exec.getDone();
                switch (done._tag) {
                    case "Failure": {
                        return this.finishSubexecutorWithCloseEffect(done, inner.exec.close(done));
                    }
                    case "Success": {
                        const doneValue = Exit.succeed(inner.combineSubKAndInner(inner.lastDone, done.value));
                        return this.finishSubexecutorWithCloseEffect(doneValue, inner.exec.close(doneValue));
                    }
                }
            }
            case exports.ChannelStateEffectTypeId: {
                return new ChannelStateEffect(T.catchAllCause_(run.effect, (cause) => channelStateEffect(this.finishSubexecutorWithCloseEffect(Exit.halt(cause), inner.exec.close(Exit.halt(cause))))));
            }
        }
    }
    processCancellation() {
        this.currentChannel = undefined;
        this.done = this.cancelled;
        this.cancelled = undefined;
        return _ChannelStateDone;
    }
}
exports.ChannelExecutor = ChannelExecutor;
//# sourceMappingURL=executor.js.map