(function() {
  // A minilanguage for instantiating linked CodeMirror instances and Docs
  function instantiateSpec(spec, place, opts) {
    var names = {}, pos = 0, l = spec.length, editors = [];
    while (spec) {
      var m = spec.match(/^(\w+)(\*?)(?:='([^\']*)'|<(~?)(\w+)(?:\/(\d+)-(\d+))?)\s*/);
      var name = m[1], isDoc = m[2], cur;
      if (m[3]) {
        cur = isDoc ? CodeMirror.Doc(m[3]) : CodeMirror(place, clone(opts, {value: m[3]}));
      } else {
        var other = m[5];
        if (!names.hasOwnProperty(other)) {
          names[other] = editors.length;
          editors.push(CodeMirror(place, opts));
        }
        var doc = editors[names[other]].linkedDoc({
          sharedHist: !m[4],
          from: m[6] ? Number(m[6]) : null,
          to: m[7] ? Number(m[7]) : null
        });
        cur = isDoc ? doc : CodeMirror(place, clone(opts, {value: doc}));
      }
      names[name] = editors.length;
      editors.push(cur);
      spec = spec.slice(m[0].length);
    }
    return editors;
  }
  function clone(obj, props) {
    if (!obj) return;
    clone.prototype = obj;
    var inst = new clone();
    if (props) for (var n in props) if (props.hasOwnProperty(n))
      inst[n] = props[n];
    return inst;
  }
  function eqAll(val) {
    var end = arguments.length, msg = null;
    if (typeof arguments[end-1] == "string")
      msg = arguments[--end];
    if (i == end) throw new Error("No editors provided to eqAll");
    for (var i = 1; i < end; ++i)
      eq(arguments[i].getValue(), val, msg)
  }
  function testDoc(name, spec, run, opts, expectFail) {
    if (!opts) opts = {};
    return test("doc_" + name, function() {
      var place = document.getElementById("testground");
      var editors = instantiateSpec(spec, place, opts);
      var successful = false;
      try {
        run.apply(null, editors);
        successful = true;
      } finally {
        if ((debug && !successful) || verbose) {
          place.style.visibility = "visible";
        } else {
          for (var i = 0; i < editors.length; ++i)
            if (editors[i] instanceof CodeMirror)
              place.removeChild(editors[i].getWrapperElement());
        }
      }
    }, expectFail);
  }
  var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
  function testBasic(a, b) {
    eqAll("x", a, b);
    a.setValue("hey");
    eqAll("hey", a, b);
    b.setValue("wow");
    eqAll("wow", a, b);
    a.replaceRange("u\nv\nw", Pos(0, 3));
    b.replaceRange("i", Pos(0, 4));
    b.replaceRange("j", Pos(2, 1));
    eqAll("wowui\nv\nwj", a, b);
  }
  testDoc("basic", "A='x' B 0, "not at left");
    is(pos.top > 0, "not at top");
  });
  testDoc("copyDoc", "A='u'", function(a) {
    var copy = a.getDoc().copy(true);
    a.setValue("foo");
    copy.setValue("bar");
    var old = a.swapDoc(copy);
    eq(a.getValue(), "bar");
    a.undo();
    eq(a.getValue(), "u");
    a.swapDoc(old);
    eq(a.getValue(), "foo");
    eq(old.historySize().undo, 1);
    eq(old.copy(false).historySize().undo, 0);
  });
  testDoc("docKeepsMode", "A='1+1'", function(a) {
    var other = CodeMirror.Doc("hi", "text/x-markdown");
    a.setOption("mode", "text/javascript");
    var old = a.swapDoc(other);
    eq(a.getOption("mode"), "text/x-markdown");
    eq(a.getMode().name, "markdown");
    a.swapDoc(old);
    eq(a.getOption("mode"), "text/javascript");
    eq(a.getMode().name, "javascript");
  });
  testDoc("subview", "A='1\n2\n3\n4\n5' B<~A/1-3", function(a, b) {
    eq(b.getValue(), "2\n3");
    eq(b.firstLine(), 1);
    b.setCursor(Pos(4));
    eqPos(b.getCursor(), Pos(2, 1));
    a.replaceRange("-1\n0\n", Pos(0, 0));
    eq(b.firstLine(), 3);
    eqPos(b.getCursor(), Pos(4, 1));
    a.undo();
    eqPos(b.getCursor(), Pos(2, 1));
    b.replaceRange("oyoy\n", Pos(2, 0));
    eq(a.getValue(), "1\n2\noyoy\n3\n4\n5");
    b.undo();
    eq(a.getValue(), "1\n2\n3\n4\n5");
  });
  testDoc("subviewEditOnBoundary", "A='11\n22\n33\n44\n55' B<~A/1-4", function(a, b) {
    a.replaceRange("x\nyy\nz", Pos(0, 1), Pos(2, 1));
    eq(b.firstLine(), 2);
    eq(b.lineCount(), 2);
    eq(b.getValue(), "z3\n44");
    a.replaceRange("q\nrr\ns", Pos(3, 1), Pos(4, 1));
    eq(b.firstLine(), 2);
    eq(b.getValue(), "z3\n4q");
    eq(a.getValue(), "1x\nyy\nz3\n4q\nrr\ns5");
    a.execCommand("selectAll");
    a.replaceSelection("!");
    eqAll("!", a, b);
  });
  testDoc("sharedMarker", "A='ab\ncd\nef\ngh' B