/* Generated from Java with JSweet 2.3.7 - http://www.jsweet.org */
import { LoggerCompat } from '../../compat/LoggerCompat';
/**
 * Constructor.
 * @param {Document} document
 * @class
 * @author eric.wittmann@gmail.com
 */
var OtEngine = /** @class */ (function () {
    function OtEngine(document) {
        if (this.pendingCommands === undefined)
            this.pendingCommands = null;
        if (this.commands === undefined)
            this.commands = null;
        if (this.document === undefined)
            this.document = null;
        if (this.pendingUndos === undefined)
            this.pendingUndos = null;
        this.document = document;
        this.pendingCommands = ([]);
        this.pendingUndos = ([]);
        this.commands = ([]);
    }
    /**
     * Gets the current document.
     * @return {Document} {OasDocument}
     */
    OtEngine.prototype.getCurrentDocument = function () {
        return this.document;
    };
    /**
     * Returns true if there is at least one pending command in the engine.  A pending command is one
     * that has not yet been finalized.  This typically means that the command has been applied to
     * the local document but not persisted in some remote store.
     * @return {boolean} {boolean}
     */
    OtEngine.prototype.hasPendingCommands = function () {
        return /* size */ this.pendingCommands.length > 0;
    };
    /**
     * Returns true if the OT engine has already seen the given command.
     * @param {OtCommand} command
     * @return {boolean}
     */
    OtEngine.prototype.hasCommand = function (command) {
        for (var index218 = 0; index218 < this.commands.length; index218++) {
            var cmd = this.commands[index218];
            {
                if (cmd.contentVersion === command.contentVersion) {
                    return true;
                }
            }
        }
        return false;
    };
    /**
     * Executes the given command in the correct sequence.  This command must have a valid
     * finalized contentVersion property.  This property will determine where in the sequence
     * of commands this one falls.  The engine will revert the document to an appropriate state
     * so that the command can be executed in the correct order.  During this process, existing
     * commands may need to be undone and then re-executed after.
     *
     * Here's what happens when executing a command:
     *
     * 1) "undo" all pending commands, since those are always executed last
     * 2) identify the insertion point of the command in the finalized command queue
     * 3) "undo" all finalized commands that fall AFTER this command in the finalized command queue
     * 4) execute this command and insert it into the finalized command queue
     * 5) re-execute all finalized commands that were undone in step #3
     * 6) re-execute all pending commands
     * 7) profit!
     *
     * A future optimization of this algorithm is to only undo/redo the commands that conflict
     * with this command.  This will avoid unnecessary work (why bother undoing/redoing when
     * there is no potential for a conflict in the document).  This optimization can be achieved
     * by ensuring that each command has a list of NodePaths that represent the affected parts of
     * the document tree.  These paths can be used to determine which commands conflict with
     * other commands.
     *
     * Whenever a command is executed, any local commands that have been undone can now be
     * safely removed from the list of commands, because making a change (by definition) orphans
     * any existing reverted commands.  There is no way to recover them.
     *
     * @param {OtCommand} command
     * @param {boolean} pending
     */
    OtEngine.prototype.executeCommand = function (command, pending) {
        if (pending) {
            command.local = true;
            LoggerCompat.info("[OtEngine] Executing PENDING command with contentId: %o", command.contentVersion);
            command.execute(this.document);
            /* add */ (this.pendingCommands.push(command) > 0);
            this.pruneRevertedCommands();
            return;
        }
        if (this.hasCommand(command)) {
            LoggerCompat.info("[OtEngine] Detected duplicate OtCommand with content version: %o", command.contentVersion);
            return;
        }
        LoggerCompat.info("[OtEngine] Executing command with content version: %o", command.contentVersion);
        var pidx;
        pidx = this.pendingUndos.indexOf(command.contentVersion);
        if (pidx !== -1) {
            /* remove */ this.pendingUndos.splice(pidx, 1)[0];
            command.reverted = true;
        }
        for (pidx = /* size */ this.pendingCommands.length - 1; pidx >= 0; pidx--) {
            {
                if (!this.pendingCommands[pidx].reverted) {
                    /* get */ this.pendingCommands[pidx].command.undo(this.document);
                }
            }
            ;
        }
        var insertionIdx = this.commands.length - 1;
        if ( /* size */this.commands.length > 0) {
            while ((insertionIdx >= 0 && /* get */ this.commands[insertionIdx].contentVersion > command.contentVersion)) {
                {
                    if (!this.commands[insertionIdx].reverted) {
                        /* get */ this.commands[insertionIdx].command.undo(this.document);
                    }
                    insertionIdx--;
                }
            }
            ;
        }
        insertionIdx++;
        /* add */ this.commands.splice(insertionIdx, 0, command);
        var idx = insertionIdx;
        while ((idx < /* size */ this.commands.length)) {
            {
                /* get */ this.commands[idx].execute(this.document);
                idx++;
            }
        }
        ;
        for (pidx = 0; pidx < /* size */ this.pendingCommands.length; pidx++) {
            {
                /* get */ this.pendingCommands[pidx].execute(this.document);
            }
            ;
        }
    };
    /**
     * Moves a commands from the "pending" queue to the "finalized" command queue.  This occurs
     * when a local (aka pending) command is acknowledged by the coordinating server and assigned
     * a final content version.  The engine must remove the command from the pending queue, update
     * its contentVersion, and then insert it at the correct location in the finalized queue.
     *
     * Here's what happens when finalizing a command:
     *
     * 1) "undo" all pending commands, shrinking the pending command queue to 0
     * 2) update the given pending command's contentVersion
     * 3) call "executeCommand()" with the newly finalized command
     * 4) re-execute all remaining pending commands
     *
     * @param {number} pendingCommandId
     * @param {number} finalizedContentVersion
     */
    OtEngine.prototype.finalizeCommand = function (pendingCommandId, finalizedContentVersion) {
        LoggerCompat.info("[OtEngine] Finalizing command with contentId: %o  and new contentVersion: %o", pendingCommandId, finalizedContentVersion);
        var isFirstPendingCmd = this.pendingCommands.length > 0 && /* get */ this.pendingCommands[0].contentVersion === pendingCommandId;
        var isLatestCmd = this.commands.length === 0 || ( /* get */this.commands[ /* size */this.commands.length - 1].contentVersion < finalizedContentVersion);
        if (isFirstPendingCmd && isLatestCmd) {
            LoggerCompat.info("[OtEngine] Pending command is \'next up\', performing simple shift from pending to finalized.");
            var command = this.pendingCommands.splice(0, 1)[0];
            command.contentVersion = finalizedContentVersion;
            /* add */ (this.commands.push(command) > 0);
            return;
        }
        var pidx;
        for (pidx = /* size */ this.pendingCommands.length - 1; pidx >= 0; pidx--) {
            {
                if (!this.pendingCommands[pidx].reverted) {
                    /* get */ this.pendingCommands[pidx].command.undo(this.document);
                }
            }
            ;
        }
        var pending = this.pendingCommands;
        this.pendingCommands = ([]);
        var idx;
        var found = false;
        for (idx = 0; idx < /* size */ pending.length; idx++) {
            {
                if ( /* get */pending[idx].contentVersion === pendingCommandId) {
                    found = true;
                    break;
                }
            }
            ;
        }
        if (found) {
            var command = pending[idx];
            /* remove */ pending.splice(idx, 1)[0];
            command.contentVersion = finalizedContentVersion;
            this.executeCommand(command, false);
        }
        else {
            LoggerCompat.info("[OtEngine] Attempted to finalize pending command %d but was not found.", pendingCommandId);
        }
        this.pendingCommands = pending;
        for (pidx = 0; pidx < /* size */ this.pendingCommands.length; pidx++) {
            {
                /* get */ this.pendingCommands[pidx].execute(this.document);
            }
            ;
        }
    };
    /**
     * Called to undo the last local command.  Returns the command that was undone (on success)
     * or null if there was no command to undo.
     * @return {OtCommand}
     */
    OtEngine.prototype.undoLastLocalCommand = function () {
        var idx;
        for (idx = /* size */ this.pendingCommands.length - 1; idx >= 0; idx--) {
            {
                var cmd = this.pendingCommands[idx];
                if (!cmd.reverted) {
                    cmd.undo(this.document);
                    return cmd;
                }
            }
            ;
        }
        var undoneCmd = null;
        if (undoneCmd == null) {
            for (idx = /* size */ this.commands.length - 1; idx >= 0; idx--) {
                {
                    var cmd = this.commands[idx];
                    if (cmd.local && !cmd.reverted) {
                        undoneCmd = cmd;
                        break;
                    }
                }
                ;
            }
        }
        if (undoneCmd != null) {
            this.undo(undoneCmd.contentVersion);
        }
        return undoneCmd;
    };
    /**
     * Called to redo the last "undone" local command.  Returns the command that was redone (on success)
     * or null if there was no command to redo.
     * @return {OtCommand}
     */
    OtEngine.prototype.redoLastLocalCommand = function () {
        var idx;
        if ( /* size */this.pendingCommands.length > 0) {
            var cmd = this.pendingCommands[ /* size */this.pendingCommands.length - 1];
            if (cmd.reverted) {
                cmd.redo(this.document);
                return cmd;
            }
            else {
                return null;
            }
        }
        var redoneCmd = null;
        if (redoneCmd == null) {
            for (idx = /* size */ this.commands.length - 1; idx >= 0; idx--) {
                {
                    var cmd = this.commands[idx];
                    if (cmd.local && cmd.reverted) {
                        redoneCmd = cmd;
                    }
                    if (cmd.local && !cmd.reverted) {
                        break;
                    }
                }
                ;
            }
        }
        if (redoneCmd != null) {
            this.redo(redoneCmd.contentVersion);
        }
        return redoneCmd;
    };
    /**
     * Called to undo a specific command by its contentVersion identifier.  Note: this will never
     * be invoked for a pending command (pending commands don't have content versions yet).
     * @param {number} contentVersion
     * @return {OtCommand}
     */
    OtEngine.prototype.undo = function (contentVersion) {
        var _this = this;
        var idx;
        var commandsToUndo = ([]);
        LoggerCompat.info("[OtEngine] Undo command with content version: %o", contentVersion);
        for (idx = /* size */ this.pendingCommands.length - 1; idx >= 0; idx--) {
            {
                var cmd = this.pendingCommands[idx];
                /* add */ (commandsToUndo.push(cmd) > 0);
            }
            ;
        }
        var found = false;
        var foundCmd = null;
        for (idx = /* size */ this.commands.length - 1; idx >= 0; idx--) {
            {
                var cmd = this.commands[idx];
                /* add */ (commandsToUndo.push(cmd) > 0);
                if (cmd.contentVersion === contentVersion) {
                    found = true;
                    foundCmd = cmd;
                    break;
                }
            }
            ;
        }
        if (!found) {
            /* add */ (this.pendingUndos.push(contentVersion) > 0);
            return null;
        }
        commandsToUndo.forEach(function (cmd) {
            if (!cmd.reverted) {
                cmd.command.undo(_this.document);
            }
        });
        foundCmd.reverted = true;
        for (idx = /* size */ commandsToUndo.length - 1; idx >= 0; idx--) {
            {
                var cmd = commandsToUndo[idx];
                cmd.execute(this.document);
            }
            ;
        }
        return foundCmd;
    };
    /**
     * Called to redo a specific command by its contentVersion identifier.
     * @param {number} contentVersion
     * @return {OtCommand}
     */
    OtEngine.prototype.redo = function (contentVersion) {
        var _this = this;
        var idx;
        var commandsToUndo = ([]);
        LoggerCompat.info("[OtEngine] Redo command with content version: %o", contentVersion);
        for (idx = /* size */ this.pendingCommands.length - 1; idx >= 0; idx--) {
            {
                var cmd = this.pendingCommands[idx];
                /* add */ (commandsToUndo.push(cmd) > 0);
            }
            ;
        }
        var found = false;
        var foundCmd = null;
        for (idx = /* size */ this.commands.length - 1; idx >= 0; idx--) {
            {
                var cmd = this.commands[idx];
                /* add */ (commandsToUndo.push(cmd) > 0);
                if (cmd.contentVersion === contentVersion) {
                    found = true;
                    foundCmd = cmd;
                    break;
                }
            }
            ;
        }
        if (!found) {
            idx = this.pendingUndos.indexOf(contentVersion);
            if (idx !== -1) {
                /* remove */ this.pendingUndos.splice(idx, 1)[0];
            }
            return null;
        }
        commandsToUndo.forEach(function (cmd) {
            if (!cmd.reverted) {
                cmd.command.undo(_this.document);
            }
        });
        foundCmd.reverted = false;
        for (idx = /* size */ commandsToUndo.length - 1; idx >= 0; idx--) {
            {
                var cmd = commandsToUndo[idx];
                cmd.execute(this.document);
            }
            ;
        }
        return foundCmd;
    };
    /**
     * Called to remove any (local only) commands that have been undone/reverted.  This is
     * done whenever a new local command is executed.  It is done because when a new command
     * is executed, any existing reverted commands can no longer be "redone".
     */
    OtEngine.prototype.pruneRevertedCommands = function () {
        var _this = this;
        var cmds = ([]);
        this.commands.forEach((function (cmds) {
            return function (cmd) {
                if (cmd.reverted) /* add */
                    (cmds.push(cmd) > 0);
            };
        })(cmds));
        cmds.forEach(function (cmd) { /* remove */ return (function (a) { var index = a.indexOf(cmd); if (index >= 0) {
            a.splice(index, 1);
            return true;
        }
        else {
            return false;
        } })(_this.commands); });
        if (!(cmds.length == 0)) {
            LoggerCompat.info("[OtEngine] Pruned %d reverted commands", /* size */ cmds.length);
        }
        var pcmds = ([]);
        this.pendingCommands.forEach((function (pcmds) {
            return function (cmd) {
                if (cmd.reverted) /* add */
                    (pcmds.push(cmd) > 0);
            };
        })(pcmds));
        pcmds.forEach(function (cmd) { /* remove */ return (function (a) { var index = a.indexOf(cmd); if (index >= 0) {
            a.splice(index, 1);
            return true;
        }
        else {
            return false;
        } })(_this.pendingCommands); });
        if (!(pcmds.length == 0)) {
            LoggerCompat.info("[OtEngine] Pruned %d reverted pending commands", /* size */ pcmds.length);
        }
    };
    return OtEngine;
}());
export { OtEngine };
OtEngine["__class"] = "io.apicurio.datamodels.cmd.ot.OtEngine";
//# sourceMappingURL=OtEngine.js.map