
// ========================================================================
// == USER INTERFACE API
// ========================================================================

var activePanel;
function Panel(div, canClose, closeHandler) {
  this.div = div;
  this.canClose = canClose;
  this.closeHandler = function () { if (closeHandler) closeHandler() };
}

// returns false if there was already a panel and that panel couldn't
// be closed (e.g. the user didn't want to close it)
function setActivePanel(panel) {
  if (panel != activePanel) {
    if (!closeActivePanel(true))
      return false;
    if (panel)
      document.body.appendChild(panel.div);
    activePanel = panel;
  }
  return true;
}

// removes the panel
// asks for confirmation if argument is true
// returns false if there is still a panel after the call
function closeActivePanel(confirm) {
  if (activePanel) {
    if (confirm && !activePanel.canClose())
      return false;
    activePanel.div.parentNode.removeChild(activePanel.div);
    activePanel.closeHandler();
    activePanel = null;
  }
  return true;
}

window.addEventListener('keypress', function (event) {
  if (event.keyCode == event.DOM_VK_ESCAPE) {
    if (activePanel)
      closeActivePanel(true);
  }
}, false);

function showListAsPanel(className, title, list, closable) {
  var div = document.createElement('div');
  div.className = "panel " + className;
  var h2 = document.createElement('h2');
  h2.appendChild(document.createTextNode(title));
  div.appendChild(h2);
  var ul = document.createElement('ul');
  for (var i in list) {
    var li = document.createElement('li');
    var a = document.createElement('a');
    a.href = list[i];
    a.appendChild(document.createTextNode(list[i]));
    li.appendChild(a);
    ul.appendChild(li)
  }
  div.appendChild(ul);
  if (closable) {
    var p = document.createElement('p');
    var button = document.createElement('button');
    button.appendChild(document.createTextNode('Close'));
    button.onclick = function () {
      closeActivePanel(false);
    };
    p.appendChild(button);
    div.appendChild(p);
  }
  setActivePanel(new Panel(div, function() { return true; }, null));
}


// ========================================================================
// == USER
// ========================================================================

function User(id) {
  this.name = id;
  this.id = id;
  this.showUser = function (closable) {
    alert('Not yet implemented.'); // XXX
  };
}


// ========================================================================
// == STATUS
// ========================================================================

var specStatuses = {
  UNKNOWN: "Section",
  TBW: "Idea; yet to be specified",
  WIP: "Being edited right now",
  FD: "First draft",
  WD: "Working draft",
  LC: "Last call for comments",
  ATRISK: "Being considered for removal",
  CR: "Awaiting implementation feedback",
  REC: "Implemented and widely deployed"
};

var implementationNames = {
  IE: "IE",
  Firefox: "Firefox",
  Safari: "Safari",
  Opera: "Opera"
};

var implementationStatuses = {
  NONE: "no support whatsoever",
  PARTIAL: "incomplete support",
  BUG: "complete but buggy support",
  PASS: "excellent support",
  CRASH: "has some support but crashes"
};

var implementationStatusTooltips = {
  PASS: "The latest release version of %s passes all the available test cases for this feature.",
  PARTIAL: "The latest release version of %s has partial support for this feature, but might have bugs and certainly doesn't support all aspects of this feature.",
  NONE: "The latest release version of %s does not support this feature.",
  BUG: "The latest release version of %s has nearly complete support for this feature, but does not yet pass all the relevant test cases.",
  CRASH: "The latest release version of %s crashes on at least one of the test cases for this feature."
};

function sectionToElement(section) {
  return document.getElementById(section);
}

function elementToSection(element) {
  return element.id;
}

window.statusList = {};
function Status(section, /* spec range string */
                status, /* one of: UNKNOWN, TBW, WIP, FD, WD, LC, CR, REC */
                tests, /* array of URIs */
                demos, /* array of URIs */
                implementations, /* dictionary of browser => state,
                                    where browser is one of Safari, IE, Firefox, Opera
                                    and state is one of PASS, PARTIAL, NONE, BUG, CRASH */
                lastEditTime, /* Date */
                lastEditUser, /* User object */
                network /* SpecStatusNetworkConnection object */
               ) {
  var that = this;

  // data
  this.section = section;
  this.status = status;
  this.tests = tests;
  this.tests.sort();
  this.demos = demos;
  this.demos.sort();
  this.implementations = implementations;
  this.lastEditTime = lastEditTime;
  this.lastEditUser = lastEditUser;
  this.network = network;
  this.pendingUpdates = false;
  this.newSection = null;

  // panel
  var div = document.createElement('div');
  div.className = "status " + status;
  var p0 = document.createElement('p');
  var strong0 = document.createElement('strong');
  strong0.appendChild(document.createTextNode(specStatuses[status]));
  p0.appendChild(strong0);
  div.appendChild(p0);
  var p1 = document.createElement('p');
  p1.appendChild(document.createTextNode('Tests: ' + tests.length + (tests.length > 0 ? ' \u2014 ' : '')));
  if (tests.length > 0) {
    var a1 = document.createElement('a');
    a1.appendChild(document.createTextNode('View...'));
    a1.href = "show-tests.html?section=" + encodeURIComponent(section);
    a1.onclick = function () { that.showTests(true); return false; };
    p1.appendChild(a1);
  }
  div.appendChild(p1);
  var p2 = document.createElement('p');
  p2.appendChild(document.createTextNode('Demos: ' + demos.length + (demos.length > 0 ? ' \u2014 ' : '')));
  if (demos.length > 0) {
    var a2 = document.createElement('a');
    a2.appendChild(document.createTextNode('View...'));
    a2.href = "show-demos.html?section=" + encodeURIComponent(section);
    a2.onclick = function () { that.showDemos(true); return false; };
    p2.appendChild(a2);
  }
  div.appendChild(p2);
  var p3 = document.createElement('div');
  p3.className = "implementations";
  for (var i in implementationNames) {
    var name = implementationNames[i];
    if (!(implementations[name] in implementationStatuses)) {
      implementations[name] = 'NONE';
    }
    var impl = document.createElement('p');
    impl.className = name + " " + implementations[name];
    var fullName = implementationNames[name];
    var fullStatus = implementationStatuses[implementations[name]];
    impl.appendChild(document.createTextNode(fullName + ": " + fullStatus));
    if (implementations[name] in implementationStatusTooltips) {
      var s = implementationStatusTooltips[implementations[name]];
      impl.title = s.replace(/%s/g, name);
    }
    p3.appendChild(impl);
  }
  div.appendChild(p3);
  var p4 = document.createElement('p');
  var a4i = document.createElement('a');
  a4i.appendChild(document.createTextNode(this.lastEditTime.getUTCFullYear() + '-' +
                                          String("0" + this.lastEditTime.getUTCMonth()).slice(-2) + '-' +
                                          String("0" + this.lastEditTime.getUTCDate()).slice(-2)));
  a4i.href = "show-history.html?section=" + encodeURIComponent(section);
  a4i.onclick = function () { that.showHistory(true); return false; };
  p4.appendChild(a4i);
  p4.appendChild(document.createTextNode(' '));
  var a4ii = document.createElement('a');
  a4ii.appendChild(document.createTextNode(this.lastEditUser.name));
  a4ii.href = "show-user.html?user=" + encodeURIComponent(this.lastEditUser.id);
  a4ii.onclick = function () { lastEditUser.showUser(true); return false; };
  p4.appendChild(a4ii);
  div.appendChild(p4);
  var p5 = document.createElement('p');
  p5.className = 'edit-link';
  var a5 = document.createElement('a');
  a5.appendChild(document.createTextNode('Edit'));
  a5.href = "edit-section.html?section=" + encodeURIComponent(section);
  a5.onclick = function (event) { that.edit(true, null); return false; };
  p5.appendChild(a5);
  div.appendChild(p5);
  div.onclick = function (event) {
    if (event.detail == 2 && event.button == 0 && event.altKey)
      that.edit(true, null);
  }
  this.panel = div;

  // public methods
  this.show = function () {
    if (this.section in window.statusList && window.statusList[this.section] != this)
      throw 'Tried to show a Status panel for a section that already had one.';
    var target = sectionToElement(section);
    if (!target)
      return; // not on this page
    if (target.tagName == 'div') {
      target.insertBefore(this.panel, null);
    } else {
      target.parentNode.insertBefore(this.panel, target.nextSibling);
    }
    window.statusList[this.section] = this;
  };
  this.hide = function () {
    this.panel.parentNode.removeChild(this.panel);
    delete window.statusList[this.section];
  };

  // private methods
  this.showTests = function (closable) {
    showListAsPanel('tests', 'Tests', this.tests, closable);
  };
  this.showDemos = function (closable) {
    showListAsPanel('demos', 'Demos', this.demos, closable);
  };
  this.showHistory = function (closable) {
    alert('Not yet implemented.'); // XXX
  };

  this.edit = function (closable, callback) {
    showLoginForm(network, closable, function() {
      that.showEdit(closable, callback);
    });
  };
  this.editing = false;
  this.showEdit = function (closable, callback) {
    if (this.editing)
      return; // non-reentrant
    var div = document.createElement('div');
    div.className = "panel editor";
    var h2 = document.createElement('h2');
    h2.appendChild(document.createTextNode("Edit section annotation"));
    div.appendChild(h2);
    var form = document.createElement('form');
    form.addEventListener('change', function () {
      form.className = 'changed';
    }, true);
    form.addEventListener('input', function () {
      form.className = 'changed';
    }, true);
    // XXX should have a way to change the 'section' bit too
    var pStatus = document.createElement('p');
    var labelStatus = document.createElement('label');
    labelStatus.appendChild(document.createTextNode('Section status: '));
    var selectStatus = document.createElement('select');
    selectStatus.name = "specStatus";
    for (var code in specStatuses) {
      if (code != 'UNKNOWN' || this.status == 'UNKNOWN') {
        var option = document.createElement('option');
        option.value = code;
        option.appendChild(document.createTextNode(specStatuses[code]));
        if (code == this.status)
          option.selected = true;
        selectStatus.appendChild(option);
      }
    }
    labelStatus.appendChild(selectStatus);
    pStatus.appendChild(labelStatus);
    form.appendChild(pStatus);
    var pTests = document.createElement('p');
    var labelTests = document.createElement('label');
    labelTests.appendChild(document.createTextNode('Links to tests for this section: '));
    var textareaTests = document.createElement('textarea');
    textareaTests.name = "tests";
    for (var i in this.tests)
      textareaTests.appendChild(document.createTextNode(this.tests[i] + "\r\n"));
    labelTests.appendChild(textareaTests);
    pTests.appendChild(labelTests);
    form.appendChild(pTests);
    var pDemos = document.createElement('p');
    var labelDemos = document.createElement('label');
    labelDemos.appendChild(document.createTextNode('Links to demos for this section: '));
    var textareaDemos = document.createElement('textarea');
    textareaTests.name = "demos";
    for (var i in this.demos)
      textareaDemos.appendChild(document.createTextNode(this.demos[i] + "\r\n"));
    labelDemos.appendChild(textareaDemos);
    pDemos.appendChild(labelDemos);
    form.appendChild(pDemos);
    for (var implCode in implementationNames) {
      var pImplStatus = document.createElement('p');
      var labelImplStatus = document.createElement('label');
      labelImplStatus.appendChild(document.createTextNode(implementationNames[implCode] + ' status: '));
      var selectImplStatus = document.createElement('select');
      selectImplStatus.name = implCode;
      for (var code in implementationStatuses) {
        var option = document.createElement('option');
        option.value = code;
        option.appendChild(document.createTextNode(implementationStatuses[code]));
        option.title = implementationStatusTooltips[code];
        if (code == this.implementations[implCode])
          option.selected = true;
        if (code in implementationStatusTooltips) {
          var s = implementationStatusTooltips[code];
          option.title = s.replace(/%s/g, implementationNames[implCode]);
        }
        selectImplStatus.appendChild(option);
      }
      labelImplStatus.appendChild(selectImplStatus);
      pImplStatus.appendChild(labelImplStatus);
      form.appendChild(pImplStatus);
    }
    div.appendChild(form);

    function save(callback) {
      var data = {};
      // XXX also update data.section if it changed
      var newSection = that.section;
      if (selectStatus.value != that.status)
        data.status = selectStatus.value;
      function doList (textarea, dataField, original) {
        var values = textarea.value.split(/[\r\n]/);
        values.sort();
        var deleted = [];
        var inserted = [];
        var valuesIndex = 0;
        var originalIndex = 0;
        while (valuesIndex < values.length || originalIndex < original.length) {
          // (code duplication here is to avoid extraneous expressions in the conditions)
          // (i'm sure this could be optimised further...)
          if (values[valuesIndex] == '') {
            // ignore blank lines
            valuesIndex += 1;
          } else if (valuesIndex >= values.length) {
            // original was deleted
            deleted.push(original[originalIndex]);
            originalIndex += 1;
          } else if (originalIndex >= original.length) {
            // value was inserted
            inserted.push(values[valuesIndex]);
            valuesIndex += 1;
          } else if (values[valuesIndex] > original[originalIndex]) {
            // original was deleted
            deleted.push(original[originalIndex]);
            originalIndex += 1;
          } else if (values[valuesIndex] < original[originalIndex]) {
            // value was inserted
            inserted.push(values[valuesIndex]);
            valuesIndex += 1;
          } else {
            // synchronised, advance
            originalIndex += 1;
            valuesIndex += 1;
          }
        }
        if (inserted.length)
          data[dataField + "Inserted"] = inserted;
        if (deleted.length)
          data[dataField + "Deleted"] = deleted;
      };
      doList(textareaTests, 'tests', that.tests);
      doList(textareaDemos, 'demos', that.demos);
      data.implementations = {};
      for (var implCode in implementationNames) {
        var select = form.elements[implCode];
        if (select.value != that.implementations[implCode])
          data.implementations[implCode] = select.value;
      }
      that.network.updateEntry(section, data, function (success, message) {
        if (success) {
          form.className = ''; 
          that.newSection = newSection;
          that.pendingUpdates = true;
        } else {
          alert('Failed to save data: ' + message);
        }
        if (callback)
          callback(success);
      });
    }

    var buttonP = document.createElement('p');
    var buttonSave = document.createElement('button');
    buttonSave.appendChild(document.createTextNode('Save'));
    buttonSave.onclick = function () {
      save(null);
    };
    buttonP.appendChild(buttonSave);

    function confirmClose() {
      if (!closable)
        return false;
      if (form.className == '')
        return true;
      return confirm(that.pendingUpdates ? "Are you sure you want to discard the changes you made since you last saved?"
                                         : "Are you sure you want to discard your changes?");
    }

    if (closable) {
      buttonP.appendChild(document.createTextNode(' '));
      var buttonSaveAndClose = document.createElement('button');
      buttonSaveAndClose.appendChild(document.createTextNode('Save and close'));
      buttonSaveAndClose.onclick = function () {
        save(function (success) {
          if (success)
            closeActivePanel(false);
        });
      };
      buttonP.appendChild(buttonSaveAndClose);
      buttonP.appendChild(document.createTextNode(' '));
      var buttonCancel = document.createElement('button');
      buttonCancel.appendChild(document.createTextNode('Cancel'));
      buttonCancel.onclick = function () { closeActivePanel(true); };
      buttonP.appendChild(buttonCancel);
    }
    div.appendChild(buttonP);

    function closed() {
      that.editing = false;
      if (that.pendingUpdates) {
        that.hide();
        that.network.getAnnotation(that.newSection, function (status) {
          status.show();
        });
        if (callback)
          callback(true);
      } else {
        if (callback)
          callback(false);
      }
    }

    this.editing = true;
    setActivePanel(new Panel(div, confirmClose, closed));
  };

}


// ========================================================================
// == NETWORK
// ========================================================================

function SpecStatusNetworkConnection() {
  var that = this;

  function getCookie(name) {
    var cookies = document.cookie.split("; ");
    for (var index = 0; index < cookies.length; index++) {
      var data = cookies[index].split("=");
      if (data[0] == name)
        return unescape(data[1]);
    }
    return null;
  }

  function setCookie(name, value) {
    var date = new Date();
    date.setYear(date.getYear()+1902);
    document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + date.toGMTString();
  }

  this.username = getCookie('status-username');
  this.password = getCookie('status-password');

  function doXMLHttpRequest(method, url, data, setup, success, failure) {
    var x = new XMLHttpRequest();
    x.open(method, url, true);
    x.onreadystatechange = function (event) {
      if (x.readyState == 4) {
        if (x.status == 204) {
          success(null);
        } else if ((x.status != 200) && (x.status != 304)) {
          failure("Error: " + x.statusText + " (" + x.status + ")");
        } else if (!x.responseXML) {
          failure('server-side XML error');
        } else {
          success(x.responseXML);
        }
      }
    };
    if (setup) setup(x);
    x.send(data);
  }

  function sendPost(url, datatype, data, success, failure) {
    doXMLHttpRequest("POST", url, data, function (x) {
      x.setRequestHeader("Content-Type", datatype);
    }, success, failure);
  }

  function sendGet(url, success, failure) {
    doXMLHttpRequest("GET", url, null, null, success, failure);
  }

  function fatalNetworkError(message) {
    alert("Fatal network error: " + message);
  }

  function getValuesFromChildNodes(node, name) {
    var result = [];
    for (var index = 0; index < node.childNodes.length; index += 1) {
      var child = node.childNodes[index];
      if (child.nodeType == 1 && child.tagName == name)
        result.push(child.textContent);
    }
    return result;
  }

  this.processEntry = function (node, callback) {
    var tests = getValuesFromChildNodes(node, "test");
    var demos = getValuesFromChildNodes(node, "demo");
    var implementations = {};
    for (var index = 0; index < node.childNodes.length; index += 1) {
      var child = node.childNodes[index];
      if (child.nodeType == 1 && child.tagName == "implementation")
        implementations[child.getAttribute("name")] = child.getAttribute("state");
    }
    var status = new Status(node.getAttribute("section"),
                            node.getAttribute("status"),
                            tests, demos, implementations,
                            new Date(node.getAttribute("lastEditTime")*1000),
                            new User(node.getAttribute("lastEditUser")),
                            this);
    setTimeout(function () { callback(status); }, 0);
  };

  this.getAnnotations = function (callback) {
    sendGet('http://www.whattf.org/specs/web-apps/current-work/status.cgi?action=get-all-annotations',
      function (response) {
        // success
        if (response.documentElement.tagName != "annotations")
          return fatalNetworkError('unexpected response from server');
        for (var entryIndex = 0; entryIndex < response.documentElement.childNodes.length; entryIndex += 1) {
          var node = response.documentElement.childNodes[entryIndex];
          if (node.nodeType == 1 && node.tagName == "entry")
            that.processEntry(node, callback);
        }
      }, fatalNetworkError);
  };
  this.getAnnotation = function (section, callback) {
    sendGet('http://www.whattf.org/specs/web-apps/current-work/status.cgi?action=get-one-annotation&section=' + encodeURIComponent(section),
      function (response) {
        // success
        var node = response.documentElement;
        if (node.tagName == "entry")
          that.processEntry(node, callback);
        else
          fatalNetworkError('unexpected response from server');
      }, fatalNetworkError);
  };
  this.createAccount = function (username, email, callback) {
    sendPost("http://www.whattf.org/specs/web-apps/current-work/status.cgi",
             "application/x-www-form-urlencoded",
             "action=create-account" + "&" +
             "username=" + encodeURIComponent(username) + "&" +
             "email=" + encodeURIComponent(email),
             function () {
               callback(true);
             }, function (message) {
               callback(false, message);
             });
  };
  this.login = function (username, password, callback) {
    sendPost("http://www.whattf.org/specs/web-apps/current-work/status.cgi",
             "application/x-www-form-urlencoded",
             "action=login" + "&" +
             "username=" + encodeURIComponent(username) + "&" +
             "password=" + encodeURIComponent(password),
             function () {
               this.username = username;
               setCookie('status-username', username);
               this.password = password;
               setCookie('status-password', password);
               callback(true);
             }, function (message) {
               callback(false, message);
             });
  };
  this.updateEntry = function (section, data, callback) {
    var patch = '';
    if ("section" in data) {
      patch += "&oldSection=" + encodeURIComponent(section);
      patch += "&section=" + encodeURIComponent(data.section);
    } else {
      patch += "&section=" + encodeURIComponent(section);
    }
    if ("status" in data) {
      patch += "&status=" + encodeURIComponent(data.status);
    }
    function doList(list, field) {
      if (list in data)
        for (var index = 0; index < data[list].length; index += 1)
          patch += "&" + encodeURIComponent(field) + "=" + encodeURIComponent(data[list][index]);
    }
    doList("testsInserted", "testInserted");
    doList("testsDeleted", "testDeleted");
    doList("demosInserted", "demoInserted");
    doList("demosDeleted", "demoDeleted");
    if ("implementations" in data)
      for (var implCode in data.implementations)
        patch += "&implementation=" + encodeURIComponent(implCode) + ":" +
                                      encodeURIComponent(data.implementations[implCode]);
    sendPost("http://www.whattf.org/specs/web-apps/current-work/status.cgi",
             "application/x-www-form-urlencoded",
             "action=update" + "&" +
             "username=" + encodeURIComponent(this.username) + "&" +
             "password=" + encodeURIComponent(this.password) +
             patch,
             function () { callback(true); },
             function (message) { callback(false, message); });
  };
  this.getHistory = function (section, callback) {
    // XXX
    callback(false);
  };
  this.getUser = function (userid, callback) {
    // XXX
    callback(new User(userid));
  };
}


// ========================================================================
// == LOGIN
// ========================================================================

var loginStatusSpan = document.createElement('span'); // must be at the end of an element
function tryLogin(network, callback) {
  loginStatusSpan.textContent = 'Logging in as ' + network.username + '...';
  network.login(network.username, network.password, function(success, message) {
    if (success) {
      loginStatusSpan.textContent = 'Logged in as ' + network.username + '.';
    } else {
      loginStatusSpan.textContent = message + '.';
    }
    if (callback)
      callback(success, message);
  });
}

// callback is called upon successful login
// if closable is false, then callback will be called (or page closed)
function showLoginPanel(network, closable, callback) {
  var oldLoginStatusSpanParent = loginStatusSpan.parentNode;
  var div = document.createElement('div');
  div.className = "panel login";
  var h2 = document.createElement('h2');
  h2.appendChild(document.createTextNode("Login"));
  div.appendChild(h2);
  var form = document.createElement('form');
  var p1 = document.createElement('p');
  var label1 = document.createElement('label');
  label1.appendChild(document.createTextNode('Username: '));
  var usernameField = document.createElement('input');
  usernameField.type = "text";
  usernameField.value = network.username || "";
  label1.appendChild(usernameField);
  p1.appendChild(label1);
  form.appendChild(p1);
  var p2 = document.createElement('p');
  var label2 = document.createElement('label');
  label2.appendChild(document.createTextNode('Password: '));
  var passwordField = document.createElement('input');
  passwordField.type = "password";
  passwordField.value = network.password || "";
  label2.appendChild(passwordField);
  p2.appendChild(label2);
  form.appendChild(p2);
  var p3 = document.createElement('p');
  p3.appendChild(document.createTextNode('Status: '));
  p3.appendChild(loginStatusSpan);
  form.appendChild(p3);
  div.appendChild(form);
  var p4 = document.createElement('p');
  var button1 = document.createElement('button');
  button1.appendChild(document.createTextNode('Login'));
  button1.onclick = function () {
    if (usernameField.value && passwordField.value) {
      network.username = usernameField.value;
      network.password = passwordField.value;
      tryLogin(network, function (success, message) {
        if (success) {
          closeActivePanel(false);
          if (callback)
            callback();
        }
      });
    } else {
      alert("You must enter a username and password.");
    }
  };
  p4.appendChild(button1);
  p4.appendChild(document.createTextNode(" "));
  var button2 = document.createElement('button');
  button2.appendChild(document.createTextNode('Request Account...'));
  button2.onclick = function () {
    closeActivePanel(false);
    showAccountRequestPanel(network, closable, callback);
  };
  p4.appendChild(button2);
  if (closable) {
    p4.appendChild(document.createTextNode(" "));
    var button3 = document.createElement('button');
    button3.appendChild(document.createTextNode('Close'));
    button3.onclick = function () {
      closeActivePanel(false);
    };
    p4.appendChild(button3);
  }
  div.appendChild(p4);
  setActivePanel(new Panel(div, function() { return closable; }, function () {
    oldLoginStatusSpanParent.appendChild(loginStatusSpan);
  }));
  if (usernameField.value.length == 0)
    usernameField.focus();
  else
    passwordField.focus();
}

function showAccountRequestPanel(network, closable, loginCallback) {
  var div = document.createElement('div');
  div.className = "panel accountRequest";
  var h2 = document.createElement('h2');
  h2.appendChild(document.createTextNode("Account Request"));
  div.appendChild(h2);
  var form = document.createElement('form');
  var p1 = document.createElement('p');
  var label1 = document.createElement('label');
  label1.appendChild(document.createTextNode('Username: '));
  var usernameField = document.createElement('input');
  usernameField.type = "text";
  usernameField.value = network.username || "";
  label1.appendChild(usernameField);
  p1.appendChild(label1);
  form.appendChild(p1);
  var p2 = document.createElement('p');
  var label2 = document.createElement('label');
  label2.appendChild(document.createTextNode('E-mail: '));
  var emailField = document.createElement('input');
  emailField.type = "email";
  emailField.value = "";
  label2.appendChild(emailField);
  p2.appendChild(label2);
  form.appendChild(p2);
  div.appendChild(form);
  var p4 = document.createElement('p');
  var button1 = document.createElement('button');
  button1.appendChild(document.createTextNode('Request Account and Retry Login...'));
  button1.onclick = function () {
    if (usernameField.value && emailField.value) {
      network.createAccount(usernameField.value, emailField.value, function(success, message) {
        if (success) {
          alert("A password has been e-mailed to the address you provided.");
          network.username = usernameField.value;
          network.password = '';
          showLoginPanel(network, closable, loginCallback);
        } else {
          alert("Either you typed in an incorrect e-mail address, or the username is already taken. If the account is yours, your password may have been e-mailed to you again.");
        }
      });
    } else {
      alert("You must enter a username and e-mail address.");
    }
  };
  p4.appendChild(button1);
  p4.appendChild(document.createTextNode(" "));
  var button2 = document.createElement('button');
  button2.appendChild(document.createTextNode('Retry Login...'));
  button2.onclick = function () {
    closeActivePanel(false);
    showLoginPanel(network, closable, loginCallback);
  };
  p4.appendChild(button2);
  if (closable) {
    p4.appendChild(document.createTextNode(" "));
    var button3 = document.createElement('button');
    button3.appendChild(document.createTextNode('Close'));
    button3.onclick = function () {
      closeActivePanel(false);
    };
    p4.appendChild(button3);
  }
  div.appendChild(p4);
  setActivePanel(new Panel(div, function() { return closable; }, null));
  usernameField.focus();
}


// ========================================================================
// == MAIN
// ========================================================================

function initAnnotations() {
  var network = new SpecStatusNetworkConnection();
  network.getAnnotations(function (status) {
    status.show();
  });
  var loginP = document.createElement('p');
  loginP.className = 'loginUI';
  var loginA = document.createElement('a');
  loginA.href = 'status-documentation.html';
  loginA.appendChild(document.createTextNode('Specification annotation system'));
  loginP.appendChild(loginA);
  loginP.appendChild(document.createTextNode(': '));
  var loginButton = document.createElement('input');
  loginButton.type = 'button';
  loginButton.value = 'Login...';
  loginButton.onclick = function(event) {
    showLoginPanel(network, true, null);
  };
  loginP.appendChild(loginButton);
  loginP.appendChild(document.createTextNode(' '));
  loginP.appendChild(loginStatusSpan);
  if (network.username && network.password)
    tryLogin(network, null);
  document.body.appendChild(loginP);
  document.addEventListener('click', function(event) {
    if (event.detail == 2 && event.button == 0 && event.altKey) {
      var section = event.target;
      while (section && section.parentNode != document.body)
        section = section.parentNode;
      if (!section || section.className.match(/\bpanel\b/))
        return; // not interesting
      while (section && !section.id)
        section = section.previousSibling;
      if (section) {
        if (section.id in window.statusList) {
          window.statusList[section.id].edit(true, null);
        } else {
          var status = new Status(section.id, 'UNKNOWN', [], [], {}, new Date(), new User('(unsaved)'), network);
          status.show();
          status.edit(true, function (saved) {
            if (!saved)
              status.hide();
          });
        }
        event.preventDefault();
      }
    }
  }, false);
}

function showLoginForm(network, closable, callback) {
  if (network.username && network.password) {
    tryLogin(network, function(success, message) {
      if (!success)
        showLoginPanel(network, closable, callback);
      else
        callback();
    });
  } else {
    showLoginPanel(network, closable, callback);
  }
}

function requireLogin(network, callback) {
  showLoginForm(network, false, callback);
}

