(function() {
  "use strict";
  
  var Pos = CodeMirror.Pos;
  namespace = "sublime_";

  function stTest(name) {
    var actions = Array.prototype.slice.call(arguments, 1);
    testCM(name, function(cm) {
      for (var i = 0; i < actions.length; i++) {
        var action = actions[i];
        if (typeof action == "string" && i == 0)
          cm.setValue(action);
        else if (typeof action == "string")
          cm.execCommand(action);
        else if (action instanceof Pos)
          cm.setCursor(action);
        else
          action(cm);
      }
    });
  }

  function at(line, ch, msg) {
    return function(cm) {
      eq(cm.listSelections().length, 1);
      eqCursorPos(cm.getCursor("head"), Pos(line, ch), msg);
      eqCursorPos(cm.getCursor("anchor"), Pos(line, ch), msg);
    };
  }

  function val(content, msg) {
    return function(cm) { eq(cm.getValue(), content, msg); };
  }

  function argsToRanges(args) {
    if (args.length % 4) throw new Error("Wrong number of arguments for ranges.");
    var ranges = [];
    for (var i = 0; i < args.length; i += 4)
      ranges.push({anchor: Pos(args[i], args[i + 1]),
                   head: Pos(args[i + 2], args[i + 3])});
    return ranges;
  }

  function setSel() {
    var ranges = argsToRanges(arguments);
    return function(cm) { cm.setSelections(ranges, 0); };
  }

  function hasSel() {
    var ranges = argsToRanges(arguments);
    return function(cm) {
      var sels = cm.listSelections();
      if (sels.length != ranges.length)
        throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length);
      for (var i = 0; i < sels.length; i++) {
        eqCharPos(sels[i].anchor, ranges[i].anchor, "anchor " + i);
        eqCharPos(sels[i].head, ranges[i].head, "head " + i);
      }
    };
  }

  stTest("bySubword", "the foo_bar DooDahBah \n a FOOBar",
         "goSubwordLeft", at(0, 0),
         "goSubwordRight", at(0, 3),
         "goSubwordRight", at(0, 7),
         "goSubwordRight", at(0, 11),
         "goSubwordRight", at(0, 15),
         "goSubwordRight", at(0, 18),
         "goSubwordRight", at(0, 21),
         "goSubwordRight", at(0, 22),
         "goSubwordRight", at(1, 0),
         "goSubwordRight", at(1, 2),
         "goSubwordRight", at(1, 6),
         "goSubwordRight", at(1, 9),
         "goSubwordLeft", at(1, 6),
         "goSubwordLeft", at(1, 3),
         "goSubwordLeft", at(1, 1),
         "goSubwordLeft", at(1, 0),
         "goSubwordLeft", at(0, 22),
         "goSubwordLeft", at(0, 18),
         "goSubwordLeft", at(0, 15),
         "goSubwordLeft", at(0, 12),
         "goSubwordLeft", at(0, 8),
         "goSubwordLeft", at(0, 4),
         "goSubwordLeft", at(0, 0));

  stTest("splitSelectionByLine", "abc\ndef\nghi",
         setSel(0, 1, 2, 2),
         "splitSelectionByLine",
         hasSel(0, 1, 0, 3,
                1, 0, 1, 3,
                2, 0, 2, 2));

  stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl",
         setSel(0, 1, 1, 1,
                1, 2, 3, 2,
                3, 3, 3, 3),
         "splitSelectionByLine",
         hasSel(0, 1, 0, 3,
                1, 0, 1, 1,
                1, 2, 1, 3,
                2, 0, 2, 3,
                3, 0, 3, 2,
                3, 3, 3, 3));

  stTest("selectLine", "abc\ndef\nghi",
         setSel(0, 1, 0, 1,
                2, 0, 2, 1),
         "selectLine",
         hasSel(0, 0, 1, 0,
                2, 0, 2, 3),
         setSel(0, 1, 1, 0),
         "selectLine",
         hasSel(0, 0, 2, 0));

  stTest("insertLineAfter", "abcde\nfghijkl\nmn",
         setSel(0, 1, 0, 1,
                0, 3, 0, 3,
                1, 2, 1, 2,
                1, 3, 1, 5), "insertLineAfter",
         hasSel(1, 0, 1, 0,
                3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn"));

  stTest("insertLineBefore", "abcde\nfghijkl\nmn",
         setSel(0, 1, 0, 1,
                0, 3, 0, 3,
                1, 2, 1, 2,
                1, 3, 1, 5), "insertLineBefore",
         hasSel(0, 0, 0, 0,
                2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn"));

  stTest("skipAndSelectNextOccurrence", "a foo bar\nfoobar foo",
         setSel(0, 2, 0, 5), "skipAndSelectNextOccurrence", hasSel(1, 0, 1, 3),
         "skipAndSelectNextOccurrence",  hasSel(1, 7, 1, 10),
         "skipAndSelectNextOccurrence",  hasSel(0, 2, 0, 5),
         Pos(0, 3), "skipAndSelectNextOccurrence", hasSel(0, 2, 0, 5),
         "skipAndSelectNextOccurrence", hasSel(1, 7, 1, 10),
         setSel(0, 6, 0, 9), "skipAndSelectNextOccurrence", hasSel(1, 3, 1, 6));

  stTest("selectNextOccurrence", "a foo bar\nfoobar foo",
         setSel(0, 2, 0, 5),
         "selectNextOccurrence", hasSel(0, 2, 0, 5,
                                        1, 0, 1, 3),
         "selectNextOccurrence", hasSel(0, 2, 0, 5,
                                        1, 0, 1, 3,
                                        1, 7, 1, 10),
         "selectNextOccurrence", hasSel(0, 2, 0, 5,
                                        1, 0, 1, 3,
                                        1, 7, 1, 10),
         Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5),
         "selectNextOccurrence", hasSel(0, 2, 0, 5,
                                        1, 7, 1, 10),
         setSel(0, 6, 0, 9),
         "selectNextOccurrence", hasSel(0, 6, 0, 9,
                                        1, 3, 1, 6));

  stTest("selectScope", "foo(a) {\n  bar[1, 2];\n}",
         "selectScope", hasSel(0, 0, 2, 1),
         Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5),
         Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5),
         Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1),
         Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0),
         Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0),
         Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10),
         Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10),
         "selectScope", hasSel(0, 8, 2, 0),
         "selectScope", hasSel(0, 0, 2, 1));

  stTest("goToBracket", "foo(a) {\n  bar[1, 2];\n}",
         Pos(0, 0), "goToBracket", at(0, 0),
         Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4),
         Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8),
         Pos(1, 2), "goToBracket", at(2, 0),
         Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6));

  stTest("swapLine", "1\n2\n3---\n4\n5",
         "swapLineDown", val("2\n1\n3---\n4\n5"),
         "swapLineUp", val("1\n2\n3---\n4\n5"),
         "swapLineUp", val("1\n2\n3---\n4\n5"),
         Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"),
         setSel(0, 1, 0, 1,
                1, 0, 2, 0,
                2, 2, 2, 2),
         "swapLineDown", val("4\n1\n2\n3---\n5"),
         hasSel(1, 1, 1, 1,
                2, 0, 3, 0,
                3, 2, 3, 2),
         "swapLineUp", val("1\n2\n3---\n4\n5"),
         hasSel(0, 1, 0, 1,
                1, 0, 2, 0,
                2, 2, 2, 2));

  stTest("swapLineEmptyBottomSel", "1\n2\n3",
         setSel(0, 1, 1, 0),
         "swapLineDown", val("2\n1\n3"), hasSel(1, 1, 2, 0),
         "swapLineUp", val("1\n2\n3"), hasSel(0, 1, 1, 0),
         "swapLineUp", val("1\n2\n3"), hasSel(0, 0, 0, 0));

  stTest("swapLineUpFromEnd", "a\nb\nc",
         Pos(2, 1), "swapLineUp",
         hasSel(1, 1, 1, 1), val("a\nc\nb"));

  stTest("joinLines", "abc\ndef\nghi\njkl",
         "joinLines", val("abc def\nghi\njkl"), at(0, 4),
         "undo",
         setSel(0, 2, 1, 1), "joinLines",
         val("abc def ghi\njkl"), hasSel(0, 2, 0, 8),
         "undo",
         setSel(0, 1, 0, 1,
                1, 1, 1, 1,
                3, 1, 3, 1), "joinLines",
         val("abc def ghi\njkl"), hasSel(0, 4, 0, 4,
                                         0, 8, 0, 8,
                                         1, 3, 1, 3));

  stTest("duplicateLine", "abc\ndef\nghi",
         Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0),
         "undo",
         setSel(0, 1, 0, 1,
                1, 1, 1, 1,
                2, 1, 2, 1), "duplicateLine",
         val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1,
                                                     3, 1, 3, 1,
                                                     5, 1, 5, 1));
  stTest("duplicateLineSelection", "abcdef",
         setSel(0, 1, 0, 1,
                0, 2, 0, 4,
                0, 5, 0, 5),
         "duplicateLine",
         val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1,
                                                   2, 4, 2, 6,
                                                   2, 7, 2, 7));

  stTest("sortLines", "c\nb\na\nC\nB\nA",
         "sortLines", val("A\nB\nC\na\nb\nc"),
         "undo",
         setSel(0, 0, 2, 0,
                3, 0, 5, 0),
         "sortLines", val("b\nc\na\nB\nC\nA"),
         hasSel(0, 0, 2, 0,
                3, 0, 5, 0),
         "undo",
         setSel(1, 0, 5, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA"));

  stTest("bookmarks", "abc\ndef\nghi\njkl",
         Pos(0, 1), "toggleBookmark",
         setSel(1, 1, 1, 2), "toggleBookmark",
         setSel(2, 1, 2, 2), "toggleBookmark",
         "nextBookmark", hasSel(0, 1, 0, 1),
         "nextBookmark", hasSel(1, 1, 1, 2),
         "nextBookmark", hasSel(2, 1, 2, 2),
         "prevBookmark", hasSel(1, 1, 1, 2),
         "prevBookmark", hasSel(0, 1, 0, 1),
         "prevBookmark", hasSel(2, 1, 2, 2),
         "prevBookmark", hasSel(1, 1, 1, 2),
         "toggleBookmark",
         "prevBookmark", hasSel(2, 1, 2, 2),
         "prevBookmark", hasSel(0, 1, 0, 1),
         "selectBookmarks", hasSel(0, 1, 0, 1,
                                   2, 1, 2, 2),
         "clearBookmarks",
         Pos(0, 0), "selectBookmarks", at(0, 0));

  stTest("smartBackspace", "  foo\n    bar",
         setSel(0, 2, 0, 2, 1, 4, 1, 4, 1, 6, 1, 6), "smartBackspace",
         val("foo\n  br"))

  stTest("upAndDowncaseAtCursor", "abc\ndef  x\nghI",
         setSel(0, 1, 0, 3,
                1, 1, 1, 1,
                1, 4, 1, 4), "upcaseAtCursor",
         val("aBC\nDEF  x\nghI"), hasSel(0, 1, 0, 3,
                                         1, 3, 1, 3,
                                         1, 4, 1, 4),
         "downcaseAtCursor",
         val("abc\ndef  x\nghI"), hasSel(0, 1, 0, 3,
                                         1, 3, 1, 3,
                                         1, 4, 1, 4));

  stTest("mark", "abc\ndef\nghi",
         Pos(1, 1), "setSublimeMark",
         Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1),
         Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1),
         "deleteToSublimeMark", val("aef\nghi"),
         "sublimeYank", val("abc\ndef\nghi"), at(1, 1));

  stTest("findUnder", "foo foobar  a",
         "findUnder", hasSel(0, 4, 0, 7),
         "findUnder", hasSel(0, 0, 0, 3),
         "findUnderPrevious", hasSel(0, 4, 0, 7),
         "findUnderPrevious", hasSel(0, 0, 0, 3),
         Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10),
         Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11));
})();
