if (typeof process !== "undefined") {
    require("./test/mockdom");
}

"use strict";

require("./multi_select");
var assert = require("./test/assertions");
var Range = require("./range").Range;
var Editor = require("./editor").Editor;
var EditSession = require("./edit_session").EditSession;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var UndoManager = require("./undomanager").UndoManager;
var lang = require("./lib/lang");

var editor, session, undoManager;

module.exports = {

    name: "ACE undoManager.js",
    setUp: function() {
        editor = editor || new Editor(new MockRenderer());
        session = new EditSession("");
        undoManager = new UndoManager();
        undoManager.$keepRedoStack = true;
        session.setUndoManager(undoManager);
        editor.setSession(session);
    },

    "test: merging": async function(done) {
        editor.session.setValue("-");
        editor.execCommand("insertstring", "a");
        editor.execCommand("insertstring", "b");
        editor.execCommand("insertstring", "c");
        editor.execCommand("gotolinestart");

        // TODO remove setTimeout when timeout from informUndoManager is removed
        await lang.sleep(0);
        editor.execCommand("insertstring", "x");
        editor.execCommand("insertstring", "y");
        editor.execCommand("insertstring", "z");
        editor.execCommand("undo");
        assert.equal(editor.getValue(), "abc-");
        editor.execCommand("redo");
        assert.equal(editor.getValue(), "xyzabc-");
        editor.execCommand("gotolineend");

        editor.execCommand("insertstring", "k");
        editor.execCommand("insertstring", "l");

        await lang.sleep(0);
        editor.sequenceStartTime = Date.now() - 3000;
        editor.execCommand("insertstring", "m");
        editor.execCommand("insertstring", "n");
        assert.equal(editor.getValue(), "xyzabc-klmn");
        editor.execCommand("undo");
        assert.equal(editor.getValue(), "xyzabc-kl");
        done();
    },
    "test: reabsing": function() {
        session.setValue("012345-012345-012345");
        session.insert({row: 0, column: 0}, "xx");
        session.markUndoGroup();
        session.remove(new Range(0, 10, 0, 15));
        session.markUndoGroup();
        session.insert({row: 0, column: 5}, "yy");
        session.markUndoGroup();
        editor.undo();
        editor.undo();
        var rev = undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "z\nz");
        undoManager.markIgnored(rev);
        // editor.undo()
        editor.redo();
        editor.redo();
        var val1 = editor.getValue();
        editor.undo();
        editor.undo();
        editor.undo();
        
        editor.redo();
        editor.redo();
        editor.redo();
        var val2 = editor.getValue();
        assert.equal(val1, val2);
    },
    "test: conflicting deletes": function() {
        session.setValue("012345\nabcdefg\nxyz");
        session.remove(new Range(0, 2, 0, 4));
        assert.equal(session.getLine(0), "0145");
        session.markUndoGroup();
        editor.undo();
        session.remove(new Range(0, 1, 0, 5));
        assert.equal(session.getLine(0), "05");
        session.markUndoGroup();
        editor.redo();
        assert.equal(session.getLine(0), "05");
        editor.undo();
        assert.equal(session.getLine(0), "012345");
    },
    "test: several deltas ignored": function() {
        session.setValue("012345\nabcdefg\nxyz");
        session.insert({row: 0, column: 5}, "zzzz");
        var rev = undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "yyyy");
        session.insert({row: 0, column: 5}, "aaaa");
        undoManager.markIgnored(rev, undoManager.getRevision() + 1);
        editor.undo();
        assert.equal(editor.getValue(), "01234aaaayyyy5\nabcdefg\nxyz");
    },
    "test: canUndo/canRedo and bookmarks": function() {
        session.setValue("012345\nabcdefg\nxyz");
        assert.ok(undoManager.isAtBookmark());
        editor.execCommand("removewordright");
        assert.ok(!undoManager.isAtBookmark());
        var rev = undoManager.getRevision();
        undoManager.bookmark();
        assert.ok(undoManager.isAtBookmark());
        editor.undo();
        assert.ok(!undoManager.isAtBookmark());
        undoManager.bookmark(rev);
        assert.ok(!undoManager.canUndo());
        assert.ok(undoManager.canRedo());
        editor.redo();
        assert.ok(undoManager.isAtBookmark());

        session.insert({row: 0, column: 5}, "yyyy");
        assert.ok(undoManager.canUndo());
        assert.ok(!undoManager.canRedo());
    },
    "test: getRevision": function () {
        session.setValue("012345\nabcdefg\nxyz");
        session.insert({row: 0, column: 5}, "yyyy");
        var rev = undoManager.getRevision();
        assert.equal(rev, 2);
        editor.undo();
        rev = undoManager.getRevision();
        assert.equal(rev, 0);
    },
    "test: swap deltas delete/insert": function () {
        session.setValue("012345\nabcdefg\nxyz");
        session.insert({row: 0, column: 5}, "zzzz");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "yyyy");
        session.remove(new Range(0, 5, 0, 9));
        var rev = undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "aaaa");
        undoManager.markIgnored(rev);
        editor.undo();
        assert.equal(editor.getValue(), "01234aaaazzzz5\nabcdefg\nxyz");
    },
    "test: swap deltas insert/delete": function () {
        session.setValue("012345");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "yyyy");
        var rev = undoManager.startNewGroup();
        session.remove(new Range(0, 5, 0, 9));
        undoManager.markIgnored(rev);
        editor.undo();
        assert.equal(editor.getValue(), "01234yyyy5");

        editor.redo();
        undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "yyyy");
        var rev1 = undoManager.startNewGroup();
        session.remove(new Range(0, 1, 0, 4));
        undoManager.markIgnored(rev1);
        editor.undo();
        assert.equal(editor.getValue(), "045");

        editor.redo();
        undoManager.startNewGroup();
        session.insert({row: 0, column: 1}, "yyyy");
        var rev2 = undoManager.startNewGroup();
        session.remove(new Range(0, 7, 0, 9));
        undoManager.markIgnored(rev2);
        editor.undo();
        assert.equal(editor.getValue(), "04yy5");
    },
    "test: swap deltas insert/insert": function () {
        session.setValue("012345");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 1}, "yyyy");
        var rev = undoManager.startNewGroup();
        session.insert({row: 0, column: 2}, "xxxx");
        undoManager.markIgnored(rev);
        editor.undo();
        assert.equal(editor.getValue(), "0yyyy12345");
    },
    "test: swap deltas delete/delete": function () {
        session.setValue("012345");
        session.insert({row: 0, column: 5}, "zzzz");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 5}, "yyyy");
        session.remove(new Range(0, 5, 0, 9));
        session.insert({row: 0, column: 5}, "aaaa");
        var rev = undoManager.startNewGroup();
        session.remove(new Range(0, 5, 0, 9));
        undoManager.markIgnored(rev);
        editor.undo();
        assert.equal(editor.getValue(), "01234aaaazzzz5");

        editor.undo();
        assert.equal(editor.getValue(), "01234zzzz5");

        editor.redo();
        var rev1=undoManager.startNewGroup();
        session.insert({row: 0, column: 1}, "yyyy");
        session.remove(new Range(0, 0, 0, 1));
        undoManager.markIgnored(rev1);
        editor.undo();
        assert.equal(editor.getValue(), "yyyy1234zzzz5");

        undoManager.startNewGroup();
        session.remove(new Range(0, 0, 0, 1));
        var rev2=undoManager.startNewGroup();
        session.remove(new Range(0, 3, 0, 5));
        undoManager.markIgnored(rev2);
        editor.undo();
        assert.equal(editor.getValue(), "yyyy34zzzz5");

        editor.redo();
        undoManager.startNewGroup();
        session.remove(new Range(0, 3, 0, 5));
        var rev3=undoManager.startNewGroup();
        session.remove(new Range(0, 1, 0, 5));
        undoManager.markIgnored(rev3);
        editor.undo();
        assert.equal(editor.getValue(), "yyyzzzz5");

    },
    "test: xform deltas insert/insert": function () {
        session.setValue("012345");
        session.insert({row: 0, column: 5}, "zzzz");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 0}, "yyyy");
        editor.undo();
        session.insert({row: 0, column: 5}, "aaaa");
        editor.redo();
        assert.equal(editor.getValue(), "yyyy01234aaaazzzz5");
    },
    "test: xform deltas insert/delete": function () {
        session.setValue("012345");
        session.insert({row: 0, column: 5}, "zzzz");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 0}, "yyyy");
        editor.undo();
        session.remove(new Range(0, 0, 0, 1));
        editor.redo();
        assert.equal(editor.getValue(), "yyyy1234zzzz5");

        session.setValue("012345");
        session.insert({row: 0, column: 5}, "zzzz");
        undoManager.startNewGroup();
        var rev=undoManager.startNewGroup();
        session.insert({row: 0, column: 0}, "yyyy");
        undoManager.markIgnored(rev);
        editor.undo();
        session.remove(new Range(0, 0, 0, 1));
        editor.redo();
        assert.equal(editor.getValue(), "yyy01234zzzz5");
    },
    "test: xform deltas delete/insert": function () {
        session.setValue("012345");
        session.insert({row: 0, column: 0}, "yyyy");
        undoManager.startNewGroup();
        session.remove(new Range(0, 0, 0, 1));
        editor.undo();
        session.insert({row: 0, column: 5}, "zzzz");
        editor.redo();
        assert.equal(editor.getValue(), "yyy0zzzz12345");
    },
    "test: xform deltas delete/delete": function () {
        session.setValue("012345");
        session.insert({row: 0, column: 0}, "yyyy");
        undoManager.startNewGroup();
        session.remove(new Range(0, 3, 0, 4));
        editor.undo();
        session.remove(new Range(0, 4, 0, 5));
        editor.redo();
        assert.equal(editor.getValue(), "yyy12345");

        session.setValue("012345");
        session.insert({row: 0, column: 0}, "yyyy");
        session.remove(new Range(0, 2, 0, 5));
        assert.equal(editor.getValue(), "yy12345");
        editor.undo();
        assert.equal(editor.getValue(), "012345");
        session.remove(new Range(0, 1, 0, 2));
        editor.redo();
        assert.equal(editor.getValue(), "yy2345");

        session.setValue("1234abcd ---");
        session.insert({row: 0, column: 0}, "ijkl");
        session.remove(new Range(0, 2, 0, 8));
        editor.undo();
        session.remove(new Range(0, 4, 0, 7));
        editor.redo();
        assert.equal(editor.getValue(), "ijd ---");
        assert.equal(undoManager.$prettyPrint(), '-[abc]0:4=>0:7\t(12)\n+[ijkl]0:0=>0:4\t(13)\n-[kl1234]0:2=>0:8\n---\n');
    },
    "test: clear redo stack after insert": function () {
        undoManager.$keepRedoStack = false;
        session.insert({row: 0, column: 0}, "y");
        editor.undo();
        assert.equal(session.$undoManager.$redoStack.length, 1);
        session.insert({row: 0, column: 0}, "y");
        assert.equal(session.$undoManager.$redoStack.length, 0);
    },
    "test: ignore deltas with incorrect boundaries": function () {
        session.setValue("012\n345\n678");
        undoManager.add({
            action: "remove",
            start: {row: 1, column: 0},
            end: {row: 3, column: 0},
            lines: ["hello", "world"]
        });
        editor.undo();
        assert.equal(editor.getValue(), "012\nhello\nworld345\n678");

        session.setValue("012\n345\n678");
        undoManager.add({
            action: "remove",
            start: {row: 3, column: 0},
            end: {row: 5, column: 0},
            lines: ["hello", "world"]
        });
        editor.undo();
        assert.equal(editor.getValue(), "012\n345\n678\nhello\nworld");


        session.setValue("012\n345\n678");
        undoManager.add({
            action: "remove",
            start: {row: 5, column: 0},
            end: {row: 7, column: 0},
            lines: ["hello", "world"]
        });
        editor.undo();
        assert.equal(editor.getValue(), "012\n345\n678");

        session.setValue("012\n345\n678");
        undoManager.add({
            action: "insert",
            start: {row: 3, column: 0},
            end: {row: 5, column: 0},
            lines: ["hello", "world"]
        });
        editor.undo();
        assert.equal(editor.getValue(), "012\n345\n678");

        session.setValue("012\n345\n678");
        undoManager.$redoStack.push([{
            action: "remove",
            start: {row: 2, column: 0},
            end: {row: 4, column: 0},
            lines: ["hello", "world"]
        }]);
        editor.redo();
        assert.equal(editor.getValue(), "012\n345\n678");
    },
    "test: do not ignore valid deltas": function () {
        editor.setValue("");
        editor.insert("\n");
        editor.insert("\n");
        editor.insert("\n");
        editor.insert("\n");
        editor.undo();
        assert.equal(editor.getValue(), "");
        editor.redo();
        assert.equal(editor.getValue(), "\n\n\n\n");
    },
    "test: limit possible undos amount": function() {
        editor.setValue("");
        undoManager.startNewGroup();
        editor.insert("a");
        undoManager.startNewGroup();
        editor.insert("b");
        undoManager.startNewGroup();
        editor.insert("c");
        assert.equal(undoManager.$undoStack.length, 3);

        undoManager.$undoDepth = 1;
        editor.setValue("");
        undoManager.startNewGroup();
        editor.insert("a");
        undoManager.startNewGroup();
        editor.insert("b");
        undoManager.startNewGroup();
        editor.insert("c");
        assert.equal(undoManager.$undoStack[0][0].lines[0], "c");
        assert.equal(undoManager.$undoStack.length, undoManager.$undoDepth);
    },
    "test: export instance using toJSON": function() {
        let state = undoManager.toJSON();
        assert.equal(typeof state, "object");
        assert.equal(state.$redoStack.length, 0);
        assert.equal(state.$undoStack.length, 0);

        session.insert({row: 0, column: 0}, "j");
        undoManager.startNewGroup();
        session.insert({row: 0, column: 0}, "s");
        editor.undo();

        state = undoManager.toJSON();
        assert.equal(state.$undoStack, undoManager.$undoStack);
        assert.equal(state.$redoStack, undoManager.$redoStack);
        assert.equal(state.$redoStack.length, 1);
        assert.equal(state.$undoStack.length, 1);

    },
    "test: import instance using fromJSON": function() {
        const JSONwithundo = `{"$redoStack":[],"$undoStack":[[{"start":{"row":0,"column":0},"end":{"row":0,"column":1},"action":"insert","lines":["j"],"id":1}]]}`;
        const JSONwithredo = `{"$redoStack":[[{"start":{"row":0,"column":0},"end":{"row":0,"column":1},"action":"insert","lines":["s"],"id":2}]],"$undoStack":[]}`;
        const JSONwithboth = `{"$redoStack":[[{"start":{"row":0,"column":0},"end":{"row":0,"column":1},"action":"insert","lines":["s"],"id":2}]],"$undoStack":[[{"start":{"row":0,"column":0},"end":{"row":0,"column":1},"action":"insert","lines":["j"],"id":1}]]}`;
        const JSONwithnone = `{"$redoStack":[],"$undoStack":[]}`;

        let state = JSON.parse(JSONwithundo);
        undoManager.fromJSON(state);
        assert.equal(undoManager.canUndo(), true);
        assert.equal(undoManager.canRedo(), false);
        state = JSON.parse(JSONwithredo);
        undoManager.fromJSON(state);
        assert.equal(undoManager.canUndo(), false);
        assert.equal(undoManager.canRedo(), true);
        state = JSON.parse(JSONwithboth);
        undoManager.fromJSON(state);
        assert.equal(undoManager.canUndo(), true);
        assert.equal(undoManager.canRedo(), true);
        state = JSON.parse(JSONwithnone);
        undoManager.fromJSON(state);
        assert.equal(undoManager.canUndo(), false);
        assert.equal(undoManager.canRedo(), false);
    }
};


require("./test/run")(module);