var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
define(["require", "exports", "./domutil", "./undo"], function (require, exports, domutil_1, undo) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    undo = __importStar(undo);
    function getOuterHTML(node) {
        return (node == null) ? "undefined" : node.outerHTML;
    }
    /**
     * Undo operation for [["wed/tree-updater".InsertNodeAtEvent]].
     *
     * The parameters after ``tree_updater`` are the same as the properties on the
     * event corresponding to this class.
     *
     * @private
     */
    class InsertNodeAtUndo extends undo.Undo {
        /**
         * @param treeUpdater The tree updater to use to perform undo or redo
         * operations.
         *
         * @param parent
         * @param index
         */
        constructor(treeUpdater, parent, index) {
            super("InsertNodeAtUndo");
            this.treeUpdater = treeUpdater;
            this.index = index;
            this.parentPath = treeUpdater.nodeToPath(parent);
            // We do not take a node parameter and save it here because further
            // manipulations could take the node out of the tree. So we cannot rely in a
            // reference to a node. What we do instead is keep a path to the parent and
            // the index. The ``node`` property will be filled as needed when
            // undoing/redoing.
        }
        performUndo() {
            if (this.node !== undefined) {
                throw new Error("undo called twice in a row");
            }
            const parent = this.treeUpdater.pathToNode(this.parentPath);
            this.node = parent.childNodes[this.index].cloneNode(true);
            this.treeUpdater.deleteNode(parent.childNodes[this.index]);
        }
        performRedo() {
            if (this.node === undefined) {
                throw new Error("redo called twice in a row");
            }
            const parent = this.treeUpdater.pathToNode(this.parentPath);
            this.treeUpdater.insertNodeAt(parent, this.index, this.node);
            this.node = undefined;
        }
        toString() {
            return [this.desc, "\n",
                " Parent path: ", this.parentPath, "\n",
                " Index: ", this.index, "\n",
                " Node: ", getOuterHTML(this.node), "\n"].join("");
        }
    }
    /**
     * Undo operation for [["wed/tree-updater".SetTextNodeValueEvent]].
     *
     * @private
     */
    class SetTextNodeValueUndo extends undo.Undo {
        /**
         * @param treeUpdater The tree updater to use to perform undo or redo
         * operations.
         */
        constructor(treeUpdater, node, value, oldValue) {
            super("SetTextNodeValueUndo");
            this.treeUpdater = treeUpdater;
            this.value = value;
            this.oldValue = oldValue;
            this.nodePath = treeUpdater.nodeToPath(node);
        }
        performUndo() {
            // The node is necessarily a text node.
            const node = this.treeUpdater.pathToNode(this.nodePath);
            this.treeUpdater.setTextNodeValue(node, this.oldValue);
        }
        performRedo() {
            // The node is necessarily a text node.
            const node = this.treeUpdater.pathToNode(this.nodePath);
            this.treeUpdater.setTextNodeValue(node, this.value);
        }
        toString() {
            return [this.desc, "\n",
                " Node path: ", this.nodePath, "\n",
                " Value: ", this.value, "\n",
                " Old value: ", this.oldValue, "\n"].join("");
        }
    }
    /**
     * Undo operation for [["wed/tree-updater".BeforeDeleteNodeEvent]].
     *
     * @private
     */
    class DeleteNodeUndo extends undo.Undo {
        /**
         * @param treeUpdater The tree updater to use to perform undo or redo
         * operations.
         */
        constructor(treeUpdater, node) {
            super("DeleteNodeUndo");
            this.treeUpdater = treeUpdater;
            const parent = node.parentNode;
            this.parentPath = treeUpdater.nodeToPath(parent);
            this.index = domutil_1.indexOf(parent.childNodes, node);
            this.node = node.cloneNode(true);
        }
        performUndo() {
            if (this.node === undefined) {
                throw new Error("undo called twice in a row");
            }
            const parent = this.treeUpdater.pathToNode(this.parentPath);
            this.treeUpdater.insertNodeAt(parent, this.index, this.node);
            this.node = undefined;
        }
        performRedo() {
            if (this.node !== undefined) {
                throw new Error("redo called twice in a row");
            }
            const parent = this.treeUpdater.pathToNode(this.parentPath);
            this.node = parent.childNodes[this.index].cloneNode(true);
            this.treeUpdater.deleteNode(parent.childNodes[this.index]);
        }
        toString() {
            return [this.desc, "\n",
                " Parent path: ", this.parentPath, "\n",
                " Index: ", this.index, "\n",
                " Node: ", getOuterHTML(this.node), "\n"].join("");
        }
    }
    /**
     * Undo operation for [["wed/tree-updater".SetAttributeNSEvent]].
     *
     * @private
     */
    class SetAttributeNSUndo extends undo.Undo {
        /**
         * @param treeUpdater The tree updater to use to perform undo or redo
         * operations.
         */
        constructor(treeUpdater, node, ns, attribute, oldValue, newValue) {
            super("SetAttributeNSUndo");
            this.treeUpdater = treeUpdater;
            this.ns = ns;
            this.attribute = attribute;
            this.oldValue = oldValue;
            this.newValue = newValue;
            this.nodePath = treeUpdater.nodeToPath(node);
        }
        performUndo() {
            const node = this.treeUpdater.pathToNode(this.nodePath);
            this.treeUpdater.setAttributeNS(node, this.ns, this.attribute, this.oldValue);
        }
        performRedo() {
            const node = this.treeUpdater.pathToNode(this.nodePath);
            this.treeUpdater.setAttributeNS(node, this.ns, this.attribute, this.newValue);
        }
        toString() {
            return [this.desc, "\n",
                " Node path: ", this.nodePath, "\n",
                " Namespace: ", this.ns, "\n",
                " Attribute Name: ", this.attribute, "\n",
                " New value: ", this.newValue, "\n",
                " Old value: ", this.oldValue, "\n"].join("");
        }
    }
    /**
     * Records undo operations.
     */
    class UndoRecorder {
        /**
         * @param editor The editor for which this recorder is created.
         *
         * @param treeUpdater The tree updater on which to listen for modifications.
         */
        constructor(editor, treeUpdater) {
            this.editor = editor;
            this.treeUpdater = treeUpdater;
            this.suppress = false;
            treeUpdater.events.subscribe((ev) => {
                switch (ev.name) {
                    case "InsertNodeAt":
                        this.insertNodeAtHandler(ev);
                        break;
                    case "SetTextNodeValue":
                        this.setTextNodeValueHandler(ev);
                        break;
                    case "BeforeDeleteNode":
                        this.beforeDeleteNodeHandler(ev);
                        break;
                    case "SetAttributeNS":
                        this.setAttributeNSHandler(ev);
                        break;
                    default:
                    // Do nothing...
                }
            });
        }
        /**
         * Sets the suppression state. When suppression is on, the recorder does not
         * record anything. When off, the recorder records. The recorder's suppression
         * state is initially off.
         *
         * @param suppress Whether to suppress or not.
         *
         * @throws {Error} If the call does not change the suppression state.
         */
        suppressRecording(suppress) {
            if (suppress === this.suppress) {
                throw new Error("spurious call to suppressRecording");
            }
            this.suppress = suppress;
        }
        insertNodeAtHandler(ev) {
            if (this.suppress) {
                return;
            }
            this.editor.recordUndo(new InsertNodeAtUndo(this.treeUpdater, ev.parent, ev.index));
        }
        setTextNodeValueHandler(ev) {
            if (this.suppress) {
                return;
            }
            this.editor.recordUndo(new SetTextNodeValueUndo(this.treeUpdater, ev.node, ev.value, ev.oldValue));
        }
        beforeDeleteNodeHandler(ev) {
            if (this.suppress) {
                return;
            }
            this.editor.recordUndo(new DeleteNodeUndo(this.treeUpdater, ev.node));
        }
        setAttributeNSHandler(ev) {
            if (this.suppress) {
                return;
            }
            this.editor.recordUndo(new SetAttributeNSUndo(this.treeUpdater, ev.node, ev.ns, ev.attribute, ev.oldValue, ev.newValue));
        }
    }
    exports.UndoRecorder = UndoRecorder;
});
//  LocalWords:  domutil insertNodeAt setTextNodeValue deleteNode ev param MPL
//  LocalWords:  InsertNodeAtUndo SetTextNodeValueUndo DeleteNodeUndo Dubeau
//  LocalWords:  pathToNode nodeToPath Mangalam SetAttributeNSUndo
//  LocalWords:  BeforeDeleteNode SetAttributeNS suppressRecording
//# sourceMappingURL=undo-recorder.js.map