"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.isInvalid = exports.isValid = exports.tryCommitAsync = exports.tryCommit = exports.untrackedTodoTargets = exports.addTodo = exports.completeTodos = exports.execTodos = exports.collectTodos = exports.emptyTodoMap = exports.analyzeJournal = exports.commitJournal = exports.prepareResetJournal = void 0;
// ets_tracing: off
require("../../../Operator");
const HM = __importStar(require("../../../Collections/Immutable/HashMap"));
const T = __importStar(require("../../../Effect"));
const driver_1 = require("../_internal/driver");
const TExit_1 = require("../TExit");
const TryCommit_1 = require("../TryCommit");
/**
 * Creates a function that can reset the journal.
 */
function prepareResetJournal(journal) {
    const saved = new Map();
    for (const entry of journal) {
        saved.set(entry[0], entry[1].use((_) => _.copy()));
    }
    return () => {
        journal.clear();
        for (const entry of saved) {
            journal.set(entry[0], entry[1]);
        }
    };
}
exports.prepareResetJournal = prepareResetJournal;
/**
 * Commits the journal.
 */
function commitJournal(journal) {
    for (const entry of journal) {
        entry[1].use((_) => _.commit());
    }
}
exports.commitJournal = commitJournal;
/**
 * Analyzes the journal, determining whether it is valid and whether it is
 * read only in a single pass. Note that information on whether the
 * journal is read only will only be accurate if the journal is valid, due
 * to short-circuiting that occurs on an invalid journal.
 */
function analyzeJournal(journal) {
    let val = "RO";
    for (const entry of journal) {
        val = entry[1].use((_) => (_.isInvalid() ? "I" : _.isChanged() ? "RW" : val));
        if (val === "I") {
            return val;
        }
    }
    return val;
}
exports.analyzeJournal = analyzeJournal;
exports.emptyTodoMap = HM.make();
/**
 * Atomically collects and clears all the todos from any `TRef` that
 * participated in the transaction.
 */
function collectTodos(journal) {
    const allTodos = new Map();
    for (const entry of journal) {
        const tref = entry[1].use((_) => _.tref);
        const todos = tref.todo.get;
        for (const todo of todos) {
            allTodos.set(todo[0], todo[1]);
        }
        tref.todo.set(exports.emptyTodoMap);
    }
    return allTodos;
}
exports.collectTodos = collectTodos;
/**
 * Executes the todos in the current thread, sequentially.
 */
function execTodos(todos) {
    for (const todo of todos) {
        todo[1]();
    }
}
exports.execTodos = execTodos;
/**
 * Runs all the todos.
 */
function completeTodos(io, journal) {
    const todos = collectTodos(journal);
    if (todos.size > 0) {
        Promise.resolve(todos).then(execTodos);
    }
    return new TryCommit_1.Done(io);
}
exports.completeTodos = completeTodos;
/**
 * For the given transaction id, adds the specified todo effect to all
 * `TRef` values.
 */
function addTodo(txnId, journal, todoEffect) {
    let added = false;
    for (const entry of journal) {
        const tref = entry[1].use((_) => _.tref);
        const oldTodo = tref.todo.get;
        if (!HM.has_(oldTodo, txnId)) {
            const newTodo = HM.set_(oldTodo, txnId, todoEffect);
            tref.todo.set(newTodo);
            added = true;
        }
    }
    return added;
}
exports.addTodo = addTodo;
/**
 * Finds all the new todo targets that are not already tracked in the `oldJournal`.
 */
function untrackedTodoTargets(oldJournal, newJournal) {
    const untracked = new Map();
    for (const entry of newJournal) {
        const key = entry[0];
        const value = entry[1];
        if (
        // We already tracked this one
        !oldJournal.has(key) &&
            // This `TRef` was created in the current transaction, so no need to
            // add any todos to it, because it cannot be modified from the outside
            // until the transaction succeeds; so any todo added to it would never
            // succeed.
            !value.use((_) => _.isNew)) {
            untracked.set(key, value);
        }
    }
    return untracked;
}
exports.untrackedTodoTargets = untrackedTodoTargets;
function tryCommit(fiberId, stm, r) {
    const journal = new Map();
    const value = new driver_1.STMDriver(stm, journal, fiberId, r).run();
    const analysis = analyzeJournal(journal);
    if (analysis === "RW") {
        commitJournal(journal);
    }
    else if (analysis === "I") {
        throw new Error("Bug: invalid journal");
    }
    switch (value._typeId) {
        case TExit_1.RetryTypeId: {
            return new TryCommit_1.Suspend(journal);
        }
        case TExit_1.SucceedTypeId: {
            return completeTodos(T.succeed(value.value), journal);
        }
        case TExit_1.FailTypeId: {
            return completeTodos(T.fail(value.value), journal);
        }
        case TExit_1.DieTypeId: {
            return completeTodos(T.die(value.value), journal);
        }
    }
}
exports.tryCommit = tryCommit;
function completeTryCommit(io, k, done) {
    done.set(true);
    k(io);
}
function suspendTryCommit(fiberId, stm, txnId, done, r, k, accum, journal) {
    // eslint-disable-next-line no-constant-condition
    while (1) {
        addTodo(txnId, journal, () => tryCommitAsync(undefined, fiberId, stm, txnId, done, r)(k));
        if (isInvalid(journal)) {
            const v = tryCommit(fiberId, stm, r);
            switch (v._typeId) {
                case TryCommit_1.DoneTypeId: {
                    completeTryCommit(v.io, k, done);
                    return;
                }
                case TryCommit_1.SuspendTypeId: {
                    const untracked = untrackedTodoTargets(accum, v.journal);
                    if (untracked.size > 0) {
                        for (const entry of untracked) {
                            accum.set(entry[0], entry[1]);
                        }
                        journal = untracked;
                    }
                    break;
                }
            }
        }
        else {
            return;
        }
    }
}
function tryCommitAsync(journal, fiberId, stm, txnId, done, r) {
    return (k) => {
        if (!done.get) {
            if (journal == null) {
                const v = tryCommit(fiberId, stm, r);
                switch (v._typeId) {
                    case TryCommit_1.DoneTypeId: {
                        completeTryCommit(v.io, k, done);
                        break;
                    }
                    case TryCommit_1.SuspendTypeId: {
                        suspendTryCommit(fiberId, stm, txnId, done, r, k, v.journal, v.journal);
                        break;
                    }
                }
            }
            else {
                suspendTryCommit(fiberId, stm, txnId, done, r, k, journal, journal);
            }
        }
    };
}
exports.tryCommitAsync = tryCommitAsync;
/**
 * Determines if the journal is valid.
 */
function isValid(journal) {
    let valid = true;
    for (const entry of journal) {
        valid = entry[1].use((_) => _.isValid());
        if (!valid) {
            return valid;
        }
    }
    return valid;
}
exports.isValid = isValid;
/**
 * Determines if the journal is invalid.
 */
function isInvalid(journal) {
    return !isValid(journal);
}
exports.isInvalid = isInvalid;
//# sourceMappingURL=index.js.map