var lts = '<';

var requests = [];

var siteRoot;

var nextSdid = 0;

var nextDlid = 0;

var pseudo = 0;

function respServerError()
{
    htmlAlert("Error", "Uh oh, something went wrong with Argulator. Argulator administrators have been notified, and will fix it soon.");
}

function getXmlHttp()
{
    try {
        // Firefox, Opera 8.0+, Safari
        return new XMLHttpRequest();
    }
    catch (e) {
        // Internet Explorer
        try {
            return new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch (e) {
            try {
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch (e) {
                return null;
            }
        }
    }
}

function handleError(message, url, line)
{
    var xmlHttp = getXmlHttp();
    if (xmlHttp) {
        text = "Message = " + message + ", URL = " + url + ", line = " + line;
        parameters = "op=error&text=" + encodeURIComponent(text);
        xmlHttp.open("POST", siteRoot + "/index.php", true);
        xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlHttp.setRequestHeader("Content-length", parameters.length);
        xmlHttp.setRequestHeader("Connection", "close");
        xmlHttp.send(parameters);
    }
    respServerError();
    return true;
}

window.onerror = handleError;

function HttpRequest(method, parameters)
{
    var xmlHttp = getXmlHttp();
    if (xmlHttp == null) {
        alert("Sorry, Argulator currently requires a web browser that has AJAX support.");
        return;
    }

    var request;
    for (request = 0; request < requests.length; ++request)
       if (requests[request] == null)
          break;
    requests[request] = this;

    function stateChanged()
    {
        if (xmlHttp.readyState != 4)
            return;
        requests[request] = null;
        if (xmlHttp.status != 200) {
            handleError('XMLHTTP status ' + xmlHttp.status,'','');
            return;
        }
        try {
            eval(xmlHttp.responseText);
        }
        catch (e) {
            handleError('XMLHTTP response ' + xmlHttp.responseText,'','');
        }
    }

    xmlHttp.onreadystatechange = stateChanged;

    parameters = "sdid=" + nextSdid + "&dlid=" + nextDlid + "&" + parameters;

    if (method == "GET") {
        xmlHttp.open("GET", siteRoot + "/index.php?" + parameters, true);
        xmlHttp.send(null);
    }
    else {
        parameters = "pseudo=" + pseudo + "&" + parameters;
        xmlHttp.open("POST", siteRoot + "/index.php", true);
        xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlHttp.setRequestHeader("Content-length", parameters.length);
        xmlHttp.setRequestHeader("Connection", "close");
        xmlHttp.send(parameters);
    }
}


function respUserDeleted()
{
    popup(
        "User deleted",
        "<div>We're sorry to see you go. If it's something we did (or didn't do), please take a moment to contact us and let us know. We'll keep your data for a year on file for a year in case " +
        "you change your mind (and for no other reason). If you didn't mean to do this, click the 'Forgot password' link to reset your account.</div>" +
        "<div class='centered'><button onclick='reqGoHome();'>Ok</button></div>");
}

function respCookieError()
{
    htmlAlert("Cookie error", "Cookies must be enabled in your browser to be able to use Argulator.");
}

function respMustBeLoggedIn()
{
    htmlAlert("Not logged in", "You need to be logged in to create or delete statements.");
}

function respInvalidVerificationCode()
{
    htmlAlert("Invalid verification code", "This verification URL is not valid. The most likely explanations are that it has already been used, or that it was pasted incorrectly.");
}

function respNoSuchDefinition()
{
    popup(
        "No such definition",
        "<div>The requested definition was found in the database. It may have been deleted (if all the statements and definitions that used it were deleted.)</div>" +
        "<div class='centered'><button onclick='reqGoHome();'>Ok</button></div>");
}

function respNoSuchStatement()
{
    popup(
        "No such statement",
        "<div>The requested statement was found in the database. It may have been deleted.</div>" +
        "<div class='centered'><button onclick='reqGoHome();'>Ok</button></div>");
}

function respNoSuchUser()
{
    popup(
        "No such user",
        "<div>There is no user of that name in the database.</div>" +
        "<div class='centered'><button onclick='reqGoHome();'>Ok</button></div>");
}

var opinions = new Object();
var watches = new Object();
var rcs = new Object();
var statementColours = new Object();
var pageStatementId = 0;
var pageUserId = '';
var pageDefinition = 0;
var pageTerm = '';
var pageSense = 0;
var pageType = '';

var colours = ["green", "red", "blue", "white"];
var captions = ["Agree", "Disagree", "Neither agree nor disagree", "No opinion"];

var statements = [];

function Statement(sdid, statementId, hasRadios, opinion, watched, rc, colour)
{
    var self = this;

    function object()
    {
        return "statements[" + sdid + "]";
    }

    function RadioSet()
    {
        function Radio(opinion)
        {
            var hover = false;
            var down = false;
            var on = false;

            function updateRadioImage()
            {
                var src = colours[opinion];
                var alt = captions[opinion];
                if (on) {
                    src += "-on";
                    alt += " (checked)";
                }
                if (down)
                    src += "-down";
                else
                    if (hover)
                        src += "-hover";
                src += ".png";

                var img = document.getElementById("r" + sdid + "_" + opinion);
                if (img) {
                    img.src = siteRoot + "/" + src;
                    img.alt = alt;
                }
            }
            this.updateRadioImage = updateRadioImage;

            function mouseOver()
            {
                hover = true;
                updateRadioImage();
            }
            this.mouseOver = mouseOver;

            function mouseOut()
            {
                hover = false;
                down = false;
                updateRadioImage();
            }
            this.mouseOut = mouseOut;

            function mouseDown()
            {
                down = true;
                updateRadioImage();
            }
            this.mouseDown = mouseDown;

            function mouseUp()
            {
                down = false;
                updateRadioImage();
            }
            this.mouseUp = mouseUp;

            function setOn(o)
            {
                on = o;
            }
            this.setOn = setOn;
        }

        var radios = [];
        for (var i = 0; i < 4; ++i)
            radios[i] = new Radio(i);
        this.radios = radios;

        function setOpinion(o)
        {
            for (var i = 0; i < 4; ++i) {
                radios[i].setOn(o == i);
                radios[i].updateRadioImage();
            }
        }
        this.setOpinion = setOpinion;
    }

    var radioSet;
    var expanded;
    var collapsed;

    function updateOpinion()
    {
        var opinion = opinions[self.statementId];

        if (radioSet)
            radioSet.setOpinion(opinion);
        else {
            var led = document.getElementById("l" + sdid);
            if (led) {
                led.src = siteRoot + "/" + colours[opinion] + "-on.png";
                led.alt = captions[opinion];
                led.className = (opinion == 3 ? "hidden" : "led");
            }
        }
    }
    this.updateOpinion = updateOpinion;

    function updateWatch()
    {
        var watch = watches[self.statementId];
        var led = document.getElementById("w" + sdid);
        if (led)
            led.className = (watch ? "led" : "hidden");
    }
    this.updateWatch = updateWatch;

    function updateRC()
    {
        var rc = rcs[self.statementId];
        var rcSpan = document.getElementById("rc" + sdid);
        if (rcSpan === null) {
            // This must be the page itself
            rcSpan = document.getElementById("rc");
            rcSpan.className = rc < 0 ? 'refuted_text' : 'corroborated_text';
            rcSpan.innerHTML = rc < 0 ? 'This statement has been refuted.' : (rc > 0 ? 'This statement has been corroborated.' : '');
        }
        else {
            rcSpan.className = rc < 0 ? 'refuted' : 'corroborated';
            rcSpan.innerHTML = rc < 0 ? 'R' : (rc > 0 ? 'C' : '');
        }
    }
    this.updateRC = updateRC;

    function updateColour()
    {
        var colour = statementColours[self.statementId];
        var link = document.getElementById("a" + sdid);
        if (link)
            link.style.color = colour;
    }
    this.updateColour = updateColour;

    function reqOpine(opinion)
    {
        new HttpRequest("POST", "op=opine&statement=" + self.statementId + "&opinion=" + opinion + "&statement_page=" + pageStatementId);

        if (opinions.hasOwnProperty(-self.statementId))
            opinions[-self.statementId] = (opinion<2 ? 1-opinion : opinion);
        opinions[self.statementId] = opinion;

        for (var statement in statements)
            if (statements.hasOwnProperty(statement))
                if (statements[statement].statementId === self.statementId || statements[statement].statementId === -self.statementId)
                    statements[statement].updateOpinion();
    }
    this.reqOpine = reqOpine;

    function reqExpand()
    {
        if (expanded) {
            document.getElementById("s" + sdid).innerHTML = expanded;

            // The user might have changed their opinion on the statement while it was collapsed.
            // Refresh all the opinions to ensure they are correct.
            for (var statement in statements)
                if (statements.hasOwnProperty(statement))
                    statements[statement].updateOpinion();
        }
        else
            new HttpRequest("GET", "op=expand&statement=" + self.statementId + "&did=" + sdid);
    }
    this.reqExpand = reqExpand;

    function reqCollapse()
    {
        var sdiv = document.getElementById("s" + sdid);
        expanded = sdiv.innerHTML;
        sdiv.innerHTML = "<a href='javascript:statements[" + sdid + "].reqExpand()'><span class='collapsed' title='Expand'></span></a>";
    }
    this.reqCollapse = reqCollapse;

    function respExpand(text)
    {
        document.getElementById("s" + sdid).innerHTML =
            "<a href='javascript:statements[" + sdid + "].reqCollapse()' >" +
                "<span class='expanded' title='Collapse'></span>" +
            "</a>: " + text;
    }
    this.respExpand = respExpand;

    this.statementId = statementId;
    if (hasRadios) {
        radioSet = new RadioSet();
        radioSet.setOpinion(opinion);
        this.radioSet = radioSet;
    }
    statements[sdid] = this;

    opinions[statementId] = opinion;
    watches[statementId] = watched;
    rcs[statementId] = rc;
    statementColours[statementId] = colour;
}

function respUpdateRC(statementId, rc)
{
    if (rcs.hasOwnProperty(statementId))
        rcs[statementId] = rc;
    if (rcs.hasOwnProperty(-statementId))
        rcs[-statementId] = -rc;
    for (var statement in statements)
        if (statements.hasOwnProperty(statement))
            if (statements[statement].statementId === statementId || statements[statement].statementId === -statementId)
                statements[statement].updateRC();
}

function respUpdateColour(statementId, colour)
{
    if (statementColours.hasOwnProperty(statementId))
        statementColours[statementId] = colour;
    if (statementColours.hasOwnProperty(-statementId))
        statementColours[-statementId] = "#" + colour.substring(3, 5) + colour.substring(1, 3) + colour.substring(5, 7);
    for (var statement in statements)
        if (statements.hasOwnProperty(statement))
            if (statements[statement].statementId === statementId || statements[statement].statementId === -statementId)
                statements[statement].updateColour();
}

function reqSearchResults(text, start, home)
{
    new HttpRequest("GET", "op=search_results&text=" + encodeURIComponent(text) + "&start=" + start + "&home=" + (home ? 'true' : 'false'));
}

var darkenDivs = [];
var popupDivs = [];
var popups = 0;

var cursorX;
var cursorY;
var popupX;
var popupY;

var popupDragging = false;

function doResize()
{
    if (popups == 0)
        return;
    var popupDiv = popupDivs[popups-1];
    var darkenDiv = darkenDivs[popups-1];
    popupDiv.style.left = (darkenDiv.offsetWidth - popupDiv.offsetWidth) / 2 + "px";
    popupDiv.style.top = (darkenDiv.offsetHeight - popupDiv.offsetHeight) / 2 + "px";
    popupDragging = false;
}

function popup(title, text)
{
    var popupDiv = document.createElement('div');
    var darkenDiv = document.createElement('div');
    document.body.appendChild(darkenDiv);
    darkenDiv.appendChild(popupDiv);
    darkenDiv.className = 'darken';
    darkenDiv.style.zIndex = (popups+1)*10000;
    popupDiv.className = 'popup';
    popupDiv.style.zIndex = (popups+1)*10000+1;
    darkenDivs[popups] = darkenDiv;
    popupDivs[popups] = popupDiv;
    ++popups;
    popupDiv.innerHTML = "<div class='popup_title' onmousedown='popupDown(event)'>" + title + "</div><div class='popup_text'>" + text + "</div>";
    doResize();
}

function popupDown(event)
{
    var popupDiv = popupDivs[popups-1];
    if (!event)
        event = window.event;
    cursorX = event.clientX;
    cursorY = event.clientY;
    popupX = parseInt(popupDiv.style.left, 10);
    if (isNaN(popupX))
        popupX = 0;
    popupY = parseInt(popupDiv.style.top, 10);
    if (isNaN(popupY))
        popupY = 0;
    popupDragging = true;
}

function popupUp(event)
{
    popupDragging = false;
}

function popupMove(event)
{
    if (!popupDragging)
        return;
    var popupDiv = popupDivs[popups-1];
    if (!event)
        event = window.event;
    popupDiv.style.left = (popupX + event.clientX - cursorX) + "px";
    popupDiv.style.top = (popupY + event.clientY - cursorY) + "px";
}

function hidePopup()
{
    --popups;
    document.body.removeChild(darkenDivs[popups]);
    darkenDivs[popups] = null;
    popupDivs[popups] = null;
    popupDragging = false;
    return false;
}


function reqGoHome()
{
    window.location.href = siteRoot;
}

function respStatementDeleted(gone)
{
    if (!gone) {
        // Something got deleted but it wasn't this page. It could have been another statement mentioned in the page, so reload to make sure the information on the page is not stale.
         window.location.reload();
    }
    else {
        popup("Statement deleted",
            "<div><p>Statement " + pageStatementId + " has been deleted.</p></div>" +
            "<div style='text-align: center;'><button onclick='reqGoHome();'>Go to home page</button></div>");
    }
}

function respDefinitionDeleted()
{
    popup("Definition deleted",
        "<div><p>The definition of " + pageTerm + " (sense " + pageSense + ") has been deleted.</p></div>" +
        "<div style='text-align: center;'><button onclick='reqGoHome();'>Go to home page</button></div>");
}

function pageInformation()
{
    var information = '&page_type=' + pageType;
    if (pageType == 'user')
        information += '&page_user=' + pageUserId;
    else
        if (pageType == 'statement')
            information += "&statement_page=" + pageStatementId;
        else
            if (pageType == 'definition')
                information += "&page_definition=" + pageDefinition;
    information += '&statements=';
    for (var statement in statements)
        if (statements.hasOwnProperty(statement))
            information += statements[statement].statementId + '|';
    return information;
}

function reqDoLogIn()
{
    var user = document.getElementById("username").value;
    var password = document.getElementById("password").value;
    new HttpRequest("POST", "op=log_in&user=" + user + "&password=" + password + pageInformation());
}

function htmlAlert(title, message)
{
    popup(
        title,
        "<div>" + message + "</div>" +
        "<div class='centered'><button onclick='hidePopup();'>Ok</button></div>");
}

function respLogInFailed()
{
    htmlAlert("Login failed", "Incorrect username or password.");
}

function reqDoLogOut()
{
    new HttpRequest("POST", "op=log_out" + pageInformation());
}

function reqShowLostPassword()
{
    popup("Request password reset",
        "<div><p>Enter your email address or username:</p></div>" +
        "<form action='javascript:reqReset();'>" +
            "<div><input type='text' name='resetName' id='resetName' /></div>" +
            "<div><input type='submit' value='Email me a reset password link' /> <button onclick='return hidePopup();'>Cancel</button></div>" +
        "</form>");
}

function reqReset()
{
    new HttpRequest("POST", "op=begin_reset&name=" + document.getElementById("resetName").value);
    hidePopup();
}

function respResetMailSent()
{
    htmlAlert("Password reset mail sent", "An email has been sent to you. Click on the link in this email to change your password.");
}

function respUserNotFound()
{
    popup("Username or email address not found.",
        "<div>Enter your email address or username:</div>" +
        "<div><input type='text' name='resetName' id='resetName' /></div>" +
        "<div class='centered'><button onclick='reqReset();'>Email me a reset password link</button> <button onclick='return hidePopup();'>Cancel</button></div>");
}

function reqCreateAccount()
{
    new HttpRequest("POST", "op=begin_create");
}

function respCreateAccount(code)
{
    popup("Create account",
        "<form action='javascript:reqCreateUser();'>" +
            "<div><label for='email'>Enter your email address here: </label><input type='text' name='email' id='email' maxlength='320' onkeyup='reqUpdateEmail();' /></div>" +
            "<div class='hidden' id='email_in_use'>That email address is already in use. <a href='javascript:reqShowLostPassword();'>Lost your username or password?</a></div>" +
            "<div>(You need to be able to read email sent to this address in order to be able to complete the account creation process. Argulator will never reveal your address to anyone, or send you unsolicited messages.)</div>" +
            "<div><label for='creation_username'>Enter the username you would like to be known by: </label><input type='text' name='creation_username' id='creation_username' maxlength='40' onkeyup='reqUpdateName();' /></div>" +
            "<div class='hidden' id='username_invalid'>User names must not start with a number and can only contain numbers, letters and the symbols $-_.+!*\'();:@ and =.</div>" +
            "<div class='hidden' id='username_in_use'>That user name is already in use. Try another one.</div>" +
            "<div><label for='creation_password'>Enter the password you'd like to use here: </label><input type='password' name='creation_password' id='creation_password' autocomplete='off' size='10'/></div>" +
            "<div><label for='captcha'>Type in the characters from the image below: </label><input type='text' name='captcha' id='captcha' maxlength='8' /></div>" +
            "<div><img src='" + siteRoot + "/captcha/" + code + "' alt='CAPTCHA' width='175' height='45' id='captcha' /></div>" +
            "<div class='hidden' id='captcha_try_again'>The characters you typed in were incorrect - please try again.</div>" +
            "<div><input type='submit' disabled='disabled' id='submit_creation' value='Submit' /> <button onclick='return hidePopup();'>Cancel</button></div>" +
        "</form>");
}

function reqCreateUser()
{
    var name = document.getElementById("creation_username").value;
    var password = document.getElementById("creation_password").value;
    var email = document.getElementById("email").value;
    var captcha = document.getElementById("captcha").value;
    hidePopup();
    new HttpRequest("POST", "op=create_user&name=" + name + "&password=" + password + "&email=" + email + "&captcha=" + captcha);
}

function respCreationMailSent()
{
    popup("Verification mail sent",
        "<div>An email has been sent to the address you provided. Please find that email and click on the link in it to complete the account creation process.</div>" +
        "<div>" +
            "<button onclick='hidePopup();'>Ok</button>" +
        "</div>");
}

function respCaptchaIncorrect(code)
{
    var captcha = document.getElementById("captcha");
    captcha.src = siteRoot + "/captcha/" + code;
    document.getElementById("captcha_try_again").className = "";
    document.getElementById("submit_creation").disabled = false;
}

var sentEmailSequence = 0;
var sentNameSequence = 0;
var receivedEmailSequence = 0;
var receivedNameSequence = 0;
var userInvalid = false;
var userExists = false;
var emailExists = false;
var userEmpty = false;
var emailEmpty = false;

function updateCreateUserButton()
{
    document.getElementById("submit_creation").disabled = (emailExists || userExists || userInvalid || userEmpty || emailEmpty);
}

function reqUpdateName()
{
    var name = document.getElementById("creation_username").value;
    var reg = /^[a-zA-Z$\-_.+!*'();:@=][a-zA-Z0-9$\-_.+!*'();:@=]*$/;
    userEmpty = (name == "");
    userInvalid = !userEmpty && !reg.test(name);
    document.getElementById("username_invalid").className = userInvalid ? "" : "hidden";
    updateCreateUserButton();
    if (userInvalid || userEmpty) {
        ++sentNameSequence;
        receivedNameSequence = sentNameSequence;
        document.getElementById("username_in_use").className = 'hidden';
    }
    else {
        ++sentNameSequence;
        new HttpRequest("GET", "op=check_user_exists&email=false&query=" + encodeURIComponent(name) + "&sequence=" + sentNameSequence);
    }
}

function reqUpdateEmail()
{
    var email = document.getElementById("email").value;
    emailEmpty = (email == "");
    updateCreateUserButton();
    if (emailEmpty) {
        ++sentEmailSequence;
        receivedEmailSequence = sentEmailSequence;
        document.getElementById("email_in_use").className = 'hidden';
    }
    else {
        ++sentEmailSequence;
        new HttpRequest("GET", "op=check_user_exists&email=true&query=" + encodeURIComponent(email) + "&sequence=" + sentEmailSequence);
    }
}

function respUserExists(sequence, email, exists)
{
    var receivedSequence = email ? receivedEmailSequence : receivedNameSequence;

    if (sequence > receivedSequence || sequence < 0) {
        if (sequence >= 0)
            if (email) {
                emailExists = exists;
                receivedEmailSequence = sequence;
            }
            else {
                userExists = exists;
                receivedNameSequence = sequence;
            }
        if (loggedIn) {
            document.getElementById(email ? "new_email_in_use" : "new_username_in_use").className = exists ? "" : "hidden";
            if (email)
                document.getElementById("submit_new_email").disabled = emailExists;
            else
                document.getElementById("submit_new_username").disabled = (userExists || userInvalid);
        }
        else {
            document.getElementById(email ? "email_in_use" : "username_in_use").className = exists ? "" : "hidden";
            document.getElementById("submit_creation").disabled = (emailExists || userExists || userInvalid);
        }
    }
}

function respStatementDoesntExist(statement)
{
    htmlAlert("Unknown statement", "There is no statement " + statement + ".");
}

var loggedIn = false;

function respSetLoginInformation(information, l)
{
    document.getElementById('login_information').innerHTML = information;
    loggedIn = l;
}

var conflicted = false;

function respConflicts(text)
{
    var conflicts = document.getElementById('conflicts');
    conflicts.innerHTML = text;
    conflicted = text ? true : false;
    conflicts.className = conflicted ? "conflicts" : "hidden";
    if (conflicted)
        if (forArgumentsBlock) {
            forArgumentsBlock.creator.parts[0].modified();
            againstArgumentsBlock.creator.parts[0].modified();
        }
}

function respUpdateSearchResults(text)
{
    document.getElementById('search_results').innerHTML = text;
}

function respSearchResults(text)
{
    popup("Search results",
        "<div id='search_results'>" + text + "</div>" +
        "<div class='centered'>" +
            "<button onclick='hidePopup();'>Cancel</button>" +
        "</div>");
}

function respGoToStatementPage(statement)
{
    window.location.href = siteRoot + "/statement/" + statement;
}

function respChangePassword()
{
    popup("Change password",
        "<form action='javascript:reqChangePassword();'>" +
            "<div><label for='newPassword'>Enter your new password here: </label><input type='password' name='newPassword' id='newPassword' autocomplete='off' size='10'/></div>" +
            "<div><input type='submit' value='Submit' /> <button onclick='return hidePopup();'>Cancel</button></div>" +
        "</form>");
}

function reqChangePassword()
{
    var password = document.getElementById("newPassword").value;
    hidePopup();
    new HttpRequest("POST", "op=change_password&password=" + password);
}

function respPasswordChanged()
{
    htmlAlert("Password changed", "Your password has been changed.");
}

function respNextSdid(sdid)
{
    nextSdid = sdid;
}

function respNextDlid(dlid)
{
    nextDlid = dlid;
}

function respSetPseudo(p)
{
    pseudo = p;
}

function reqDeleteStatement()
{
    if (!loggedIn)
        respMustBeLoggedIn();
    else
        new HttpRequest("POST", "op=delete_statement&statement=" + pageStatementId + "&statement_page=" + pageStatementId);
}

function respInvalidEmail()
{
    htmlAlert("Invalid email address", "The email address you provided is not valid.");
}

var searchPart;
var searchCreator;

function ArgumentsBlock(forArgument)
{
    var argumentsBlock = this;

    function forAgainst() { return forArgument ? "for" : "against"; }
    function divId() { return "arguments_" + forAgainst(); }
    function div() { return document.getElementById(divId()); }
    this.div = div;

    function Creator(forArgument)
    {
        var creator = this;

        var incompleteParts = 0;
        var completeParts = 0;
        var totalParts = 0;
        this.parts = [];

        function fa() { return forArgument ? "f" : "a"; }
        function buttonId() { return argument() + "_b"; }
        this.buttonId = buttonId;
        function button() { return document.getElementById(buttonId()); }
        this.button = button;
        function argument() { return "new_" + fa(); }
        function object() { return forAgainst() + "ArgumentsBlock.creator"; }
        this.object = object;
        function divId() { return "new_argument_" + forAgainst() + "_statements"; }
        function div() { return document.getElementById(divId()); }
        this.div = div;

        function Part(n)
        {
            var part = this;

            var typedText;

            // 0 = empty, 1 = non-empty, 2 = final
            this.stage = 1;

            function buttonId() { return creator.buttonId() + n; }
            function button() { return document.getElementById(buttonId()); }
            function inputId() { return argument() + "_i" + n; }
            function input() { return document.getElementById(inputId()); }
            function divId() { return argument() + "_" + n; }
            function div() { return document.getElementById(divId()); }
            this.div = div;
            function object() { return creator.object() + ".parts[" + n + "]"; }

            function editHtml()
            {
                return "<input " +
                        "type='text' " +
                        "name='" + inputId() + "' " +
                        "id='" + inputId() + "' " +
                        "size='100' " +
                        "onkeyup='" + object() + ".modified();' " +
                    "/> " +
                    "<button " +
                        "onclick='" + object() + ".reqSearch();' " +
                        "id='" + buttonId() + "' " +
                    ">" +
                        "Search" +
                    "</button>";
            }

            function update(stage)
            {
                if (stage != part.stage) {
                    if (stage == 1) {
                        ++incompleteParts;
                        if (part.stage == 2)
                            --completeParts;
                        button().disabled = false;
                        creator.button().disabled = true;
                        // When we start typing the last part, create a new last part
                        if (n == totalParts-1)
                            new Part(n+1);
                    }
                    else {
                        --incompleteParts;
                        if (stage == 2)
                            ++completeParts;
                        else
                            button().disabled = true;
                        if (incompleteParts == 0 && completeParts > 0)
                            creator.button().disabled = conflicted;
                    }
                    part.stage = stage;
                    if (stage == 0)
                        while (totalParts > 1 && creator.parts[totalParts-1].stage == 0 && creator.parts[totalParts-2].stage == 0) {
                            // If we have two empty parts at the end, clear one of them up.
                            var div = creator.parts[totalParts-1].div();
                            creator.div().removeChild(div);
                            creator.parts[totalParts-1] = null;
                            --totalParts;
                        }
                }
            }

            function modified()
            {
                update(input().value == "" ? 0 : 1);
            }
            this.modified = modified;

            function reqSearch()
            {
                searchPart = part;
                typedText = input().value;
                new HttpRequest("GET", "op=search&home=false&text=" + encodeURIComponent(typedText));
            }
            this.reqSearch = reqSearch;

            function reqUseStatement(statementId)
            {
                hidePopup();
                new HttpRequest("GET", "op=use_statement&statement=" + statementId);
            }
            this.reqUseStatement = reqUseStatement;

            function respUseStatement(statementId, statementLine)
            {
                part.submitText = "E" + statementId;
                div().innerHTML = statementLine +
                    "<button onclick='" + object() + ".reqEdit();'>Edit</button>";
                update(2);
            }
            this.respUseStatement = respUseStatement;

            function createUseStatement(negate)
            {
                hidePopup();
                var text = input().value;

                div().innerHTML =
                    "<span class='statement'>" +
                        "<span class='statement_id'>" + (negate ? "-" : "") + "New</span>: " + (negate ? "It is not the case that: " : "") + text +
                    "</span>" +
                    "<button onclick='" + object() + ".reqEdit();'>Edit</button>";
                part.submitText = (negate ? "M" : "P") + text;
                update(2);
            }
            this.createUseStatement = createUseStatement;

            function reqDefineTerms()
            {
                new HttpRequest("GET", "op=define_terms&type=statement&for=" + (forArgument ? 'true' : 'false') + "&part=" + n + "&text=" + encodeURIComponent(input().value));
            }
            this.reqDefineTerms = reqDefineTerms;

            function reqEdit()
            {
                div().innerHTML = editHtml();
                input().value = typedText;
                update(1);
            }
            this.reqEdit = reqEdit;

            var newDiv = document.createElement('div');
            newDiv.id = divId();
            newDiv.innerHTML = editHtml();
            creator.div().appendChild(newDiv);

            ++incompleteParts;
            update(0);
            creator.parts[totalParts] = this;
            ++totalParts;
        }

        function reqNewArgument()
        {
            if (!loggedIn) {
                respMustBeLoggedIn();
                return;
            }

            var submit = "";
            var j=0;
            for (var i = 0; i < totalParts; ++i)
                if (creator.parts[i].stage == 2)
                    submit += "&part" + (j++) + "=" + encodeURIComponent(creator.parts[i].submitText);
            submit += "&part" + j + "=X";
            new HttpRequest("POST", "op=create_argument&for=" + (forArgument ? 'true' : 'false') + "&statement_page=" + pageStatementId + "&statement=" + pageStatementId + submit);
        }
        this.reqNewArgument = reqNewArgument;

        function respNewArgument(newArgument)
        {
            argumentsBlock.div().innerHTML += newArgument;
            creator.parts = [];
            div().innerHTML = "";
            incompleteParts = 0;
            completeParts = 0;
            totalParts = 0;
            new Part(0);
            button().disabled = true;
        }
        this.respNewArgument = respNewArgument;

        new Part(0);
    }

    function reqPageArguments(start)
    {
        new HttpRequest("GET", "op=arguments&for=" + (forArgument ? 'true' : 'false') + "&start=" + start + "&statement=" + pageStatementId);
    }

    function respArguments(text)
    {
        div().innerHTML = text;
    }
    this.respArguments = respArguments;

    this.creator = new Creator(forArgument);
}

var forArgumentsBlock = null;
var againstArgumentsBlock = null;

function reqCreateStatement()
{
    hidePopup();
    if (!loggedIn)
        respMustBeLoggedIn();
    else
        new HttpRequest("POST", "op=create_statement&text=" + encodeURIComponent(document.getElementById("search").value));
}

function reqDefineTerms()
{
    new HttpRequest("GET", "op=define_terms&type=statement&text=" + encodeURIComponent(document.getElementById("search").value));
}

function respInit(s)
{
    window.onresize = doResize;
    siteRoot = s;
}

function respSetPageStatementId(statementId)
{
    pageStatementId = statementId;
    forArgumentsBlock = new ArgumentsBlock(true);
    againstArgumentsBlock = new ArgumentsBlock(false);
}

function respSetPageUserId(userId)
{
    pageUserId = userId;
}

function respSetPageDefinition(definition, term, sense)
{
    pageDefinition = definition;
    pageTerm = term;
    pageSense = sense;
}

function respSetPageType(type)
{
    pageType = type;
}

function reqStatementSearch()
{
    var typedText = document.getElementById("search").value;
    new HttpRequest("GET", "op=search&home=true&text=" + encodeURIComponent(typedText));
}

function searchModified()
{
    var typedText = document.getElementById("search").value;
    var button = document.getElementById("search_button");
    if (typedText == "")
        button.disabled = true;
    else
        button.disabled = false;
}

function reqContact()
{
    var reply;
    if (loggedIn)
        reply =
            "<p><label for='allow_reply'>Allow Argulator staff to contact you in response to this message: </label>" +
            "<input type='checkbox' name='allow_reply' id='allow_reply' checked='checked'/></p>";
    else
        reply =
            "<p><label_for='reply_address'>If you want a reply, please enter your email address: </label>" +
            "<input type='text' name='reply_email' id='reply_email' maxlength='320'/></p>";
    popup("Contact us",
        "<form action='javascript:reqSendContact();'>" +
            "<p>Please type your message here:</p>" +
            "<p><textarea row='4' cols='80' id='contact_text'></textarea></p>" + reply +
            "<p><input type='submit' value='Send'/> <button onclick='return hidePopup();'>Cancel</button></p>" +
        "</form>");
}

function reqSendContact()
{
    var text = document.getElementById("contact_text").value;
    var parameters = "text=" + encodeURIComponent(text);
    if (loggedIn) {
        if (document.getElementById("allow_reply").checked)
            parameters += "&allow_reply=true";
    }
    else
        parameters += "&email=" + encodeURIComponent(document.getElementById("reply_email").value);
    hidePopup();
    new HttpRequest("POST", "op=contact&" + parameters);
}

function reqLocationWhy()
{
    var locationWhy = document.getElementById("location_why");
    if (locationWhy.className == '')
        locationWhy.className = 'hidden';
    else
        locationWhy.className = '';
}

function reqBirthYearWhy()
{
    var birthYearWhy = document.getElementById("birth_year_why");
    if (birthYearWhy.className == '')
        birthYearWhy.className = 'hidden';
    else
        birthYearWhy.className = '';
}

function reqSetAnonymous()
{
    new HttpRequest("POST", "op=set_anonymous&anonymous=" + (document.getElementById("anonymous").checked ? "true" : "false"));
}

function reqNowhere()
{
    document.getElementById("locator").className = 'hidden';
    document.getElementById("nowhere").disabled = true;
    new HttpRequest("POST", "op=set_location&longitude=null&latitude=null");
}

function getElementLocation(obj)
{
    var x = 0;
    var y = 0;
    if (obj.offsetParent) {
        do {
            x += obj.offsetLeft;
            y += obj.offsetTop;
        } while (obj = obj.offsetParent);
    }
    else
        if (obj.x) {
            x += obj.x;
            y += obj.y;
        }
    return [x,y];
}

function reqSetLocation(event)
{
    if (!event)
        event = window.event;
    var cursorX;
    var cursorY;
    if (event.pageX) {
        cursorX = event.pageX;
        cursorY = event.pageY;
    }
    else {
        cursorX = event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
        cursorY = event.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
    }
    var xy = getElementLocation(document.getElementById("locations_map"));
    var x = xy[0] + 4;
    var y = xy[1] + 4;
    cursorX -= x;
    cursorY -= y;
    var locator = document.getElementById("locator");
    locator.style.left = (cursorX - 4) + "px";
    locator.style.top = (cursorY - 4) + "px";
    new HttpRequest("POST", "op=set_location&longitude=" + (cursorX - 180) + "&latitude=" + (90 - cursorY));
}

function respSetLocation(latitude, longitude)
{
    var locator = document.getElementById("locator");
    if (latitude === null)
        locator.className = 'hidden';
    else {
        locator.className = 'locator';
        locator.style.left = Math.floor(longitude + 180 - 4) + "px";
        locator.style.top = Math.floor((90 - latitude) - 4) + "px";
    }
}

var currentBirthYear = '';

function respCurrentBirthYear(birthYear)
{
    currentBirthYear = birthYear;
}

function reqSetBirthYear()
{
    document.getElementById("submit_birth_year").disabled = true;
    currentBirthYear = document.getElementById("birth_year").value;
    new HttpRequest("POST", "op=set_birth_year&birth_year=" + encodeURIComponent(currentBirthYear));
}

function reqGetLocationFromIP()
{
    new HttpRequest("POST", "op=auto_location");
}

function isInteger(string)
{
    return (string.search(/^[0-9]+$/) == 0);
}

function reqModifyBirthYear()
{
    var year = document.getElementById("birth_year").value;
    var date = new Date();
    var submitBirthYear = document.getElementById("submit_birth_year");
    var invalidBirthYear = document.getElementById("birth_year_invalid");
    this_year = date.getYear();
    if (this_year < 2009)
        this_year += 1900;
    if (year == '' || year == currentBirthYear || (isInteger(year) && (year >= 1894 && year <= this_year))) {
        submitBirthYear.disabled = (year == currentBirthYear);
        invalidBirthYear.className = 'hidden';
    }
    else {
        submitBirthYear.disabled = true;
        invalidBirthYear.className = '';
    }
}

function reqDeleteUser()
{
    new HttpRequest("POST", "op=delete_user");
}

function reqWatch(watch)
{
    watches[pageStatementId] = watch;
    watches[-pageStatementId] = watch;
    for (var statement in statements)
        if (statements.hasOwnProperty(statement))
            if (statements[statement].statementId === pageStatementId || statements[statement].statementId === -pageStatementId)
                statements[statement].updateWatch();
    new HttpRequest("POST", "op=watch&watch=" + (watch ? 'true' : 'false') + "&statement=" + pageStatementId);
}

function reqSetStatementAnonymous(anonymous)
{
    new HttpRequest("POST", "op=anonymize&anonymous=" + (anonymous ? 'true' : 'false') + "&statement=" + pageStatementId);
}

function reqSetDefinitionAnonymous(anonymous)
{
    new HttpRequest("POST", "op=anonymize_definition&anonymous=" + (anonymous ? 'true' : 'false') + "&definition=" + pageDefinition);
}

function reqTextModified()
{
    document.getElementById("submit_text").disabled = false;
}

function reqSetText()
{
    document.getElementById("submit_text").disabled = true;
    new HttpRequest("POST", "op=set_text&text=" + encodeURIComponent(document.getElementById("bio_text").value));
}

var currentName;

function respCurrentName(name)
{
    currentName = name;
}

function reqModifyUsername()
{
    var name = document.getElementById("new_username").value;
    var reg = /^[a-zA-Z$\-_.+!*'(),;:@=][a-zA-Z0-9$\-_.+!*'(),;:@=]*$/;
    userEmpty = (name == "");
    userInvalid = !userEmpty && !reg.test(name);
    document.getElementById("new_username_invalid").className = userInvalid ? "" : "hidden";
    document.getElementById("submit_new_username").disabled = (userExists || userInvalid || userEmpty || name == currentName);
    if (userInvalid || userEmpty || name == currentName) {
        ++sentNameSequence;
        receivedNameSequence = sentNameSequence;
        document.getElementById("new_username_in_use").className = 'hidden';
    }
    else {
        ++sentNameSequence;
        new HttpRequest("GET", "op=check_user_exists&email=false&query=" + encodeURIComponent(name) + "&sequence=" + sentNameSequence);
    }
}

function reqSetUsername()
{
    currentName = document.getElementById("new_username").value;
    document.getElementById("submit_new_username").disabled = true;
    new HttpRequest("POST", "op=set_username&name=" + encodeURIComponent(currentName));
}

function respSetUsername(username)
{
    window.location.href = siteRoot + "/user/" + username;
}

var currentEmail;

function respCurrentEmail(email)
{
    currentEmail = email;
}

function reqModifyEmail()
{
    var email = document.getElementById("new_email").value;
    emailEmpty = (email == "");
    document.getElementById("submit_new_email").disabled = (emailExists || emailEmpty || email == currentEmail);
    if (emailEmpty || email == currentEmail) {
        ++sentEmailSequence;
        receivedEmailSequence = sentEmailSequence;
        document.getElementById("new_email_in_use").className = 'hidden';
    }
    else {
        ++sentEmailSequence;
        new HttpRequest("GET", "op=check_user_exists&email=true&query=" + encodeURIComponent(email) + "&sequence=" + sentEmailSequence);
    }
}

function reqSetEmail()
{
    currentEmail = document.getElementById("new_email").value;
    document.getElementById("submit_new_email").disabled = true;
    new HttpRequest("POST", "op=set_email&email=" + encodeURIComponent(currentEmail));
}

function respSetEmail()
{
    popup("Verification mail sent",
        "<div>An email has been sent to the address you provided. Please find that email and click on the link in it to complete the email address change process.</div>" +
        "<div>" +
            "<button onclick='hidePopup();'>Ok</button>" +
        "</div>");
}

function respTooManyFailedLogins()
{
    htmlAlert("Too many failed logins", "Please wait an hour before trying again, or <a href='javascript:reqShowLostPassword();'>Reset your password</a>.");
}

function respUnknownStatement(statement)
{
    htmlAlert("Unknown statement", "The statement " + statement + " was not found");
}

function respUnknownUser(user)
{
    htmlAlert("Unknown user", "The user " + user + " was not found");
}

function respContactSent()
{
    htmlAlert("Message sent", "Thank you for your feedback. If you requested a reply we will try to get back to you in 24 hours.");
}

function respResolveConflictFirst()
{
    htmlAlert("Resolve your conflict first", "Users with conflicting opinions cannot create new statements or arguments. Resolve your conflict using the yellow box at the top of the page and then try again.");
}

function respUpdateStatement(statementId, opinion, watched)
{
    opinions[statementId] = opinion;
    opinions[-statementId] = (opinion<2 ? 1-opinion : opinion);
    watches[statementId] = watched;
    watches[-statementId] = watched;
    for (var statement in statements)
        if (statements.hasOwnProperty(statement))
            if (statements[statement].statementId === statementId || statements[statement].statementId === -statementId) {
                statements[statement].updateWatch();
                statements[statement].updateOpinion();
            }
}

function respSet(id, text)
{
    document.getElementById(id).innerHTML = text;
}

var definitions = [];

function getStyle(e, s)
{
    var computedStyle;
    if (typeof e.currentStyle != 'undefined')
        computedStyle = e.currentStyle;
    else
        computedStyle = document.defaultView.getComputedStyle(e, null);
    return computedStyle[s];
}

var definitionStack = [];

var definitionBlock = null;

var maxDefZ = 10;

function Definition(dlid, definitionId)
{
    var self = this;

    function element()
    {
        return document.getElementById("d" + dlid);
    }

    var hovering = false;
    var text = null;

    function reqHover()
    {
        if (text != null) {
            respHover(text);
            return;
        }
        new HttpRequest("GET", "op=hover&definition=" + definitionId + "&lid=" + dlid);
    }
    this.reqHover = reqHover;

    var linkDiv = null;
    var definitionDiv = null;
    var inLink = false;
    var inDefinition = false;

    function respHover(newText)
    {
        hovering = true;
        text = newText;
        var e = element();
        if (!e)
            return;
        if (linkDiv || definitionDiv)
            return;
        xy = getElementLocation(e);
        xy2 = getElementLocation(e.parentNode);
        var z = getStyle(e, 'zIndex');
        if (z == 'auto')
            z = 10;
        linkDiv = document.createElement('div');
        linkDiv.innerHTML = e.innerHTML;
        linkDiv.className = 'definition_floating_link';
        linkDiv.style.left = xy[0] + "px";
        linkDiv.style.top = xy2[1] + "px";
        linkDiv.style.zIndex = z + 2;
        linkDiv.dlid = dlid;
        linkDiv.onmouseover = function(){definitions[this.dlid].reqLinkMouseOver()};
        linkDiv.onmouseout = function(){definitions[this.dlid].reqLinkMouseOut()};
        if (!definitionBlock)
            definitionBlock = document.getElementById('floating_definitions');
        definitionBlock.appendChild(linkDiv);
        linkDiv.style.fontSize = getStyle(e, 'fontSize');
        linkDiv.style.fontWeight = getStyle(e, 'fontWeight');
        definitionDiv = document.createElement('div');
        definitionDiv.innerHTML = text;
        definitionDiv.className = 'definition_floating_text';
        definitionDiv.style.left = "10px";
        definitionDiv.style.top = (xy2[1] + 10) + "px";
        definitionDiv.style.zIndex = z + 1;
        definitionDiv.dlid = dlid;
        definitionDiv.onmouseover = function(){definitions[this.dlid].reqDefinitionMouseOver()};
        definitionDiv.onmouseout = function(){definitions[this.dlid].reqDefinitionMouseOut()};
        definitionBlock.appendChild(definitionDiv);
        var definitionWidth = definitionDiv.clientWidth;
        var linkWidth = linkDiv.clientWidth;
        var linkHeight = linkDiv.clientHeight;
        definitionDiv.style.top = (xy2[1] + linkHeight) + "px";
        var newLeft = xy[0] + linkWidth - definitionWidth;
        if (newLeft > 10)
            definitionDiv.style.left = newLeft + "px";
        var stackSize = definitionStack.length;
        if (stackSize == 0 || definitionStack[stackSize-1] != dlid)
            definitionStack.push(dlid);
    }
    this.respHover = respHover;

    function reqLinkMouseOut()
    {
        inLink = false;
        setTimeout("definitions[" + dlid + "].reqUnhover()", 100);
    }
    this.reqLinkMouseOut = reqLinkMouseOut;

    function reqLinkMouseOver()
    {
        inLink = true;
    }
    this.reqLinkMouseOver = reqLinkMouseOver;

    function reqDefinitionMouseOut()
    {
        inDefinition = false;
        setTimeout("definitions[" + dlid + "].reqUnhover()", 100);
    }
    this.reqDefinitionMouseOut = reqDefinitionMouseOut;

    function reqDefinitionMouseOver()
    {
        inDefinition = true;
    }
    this.reqDefinitionMouseOver = reqDefinitionMouseOver;

    function remove()
    {
        try {
            if (linkDiv)
                document.body.removeChild(linkDiv);
        }
        catch(e) { }
        linkDiv = null;
        try {
            if (definitionDiv)
                document.body.removeChild(definitionDiv);
        }
        catch(e) { }
        definitionDiv = null;
    }
    this.remove = remove;

    function reqUnhover()
    {
        if (inDefinition || inLink)
            return;
        var stackSize = definitionStack.length;
        if (definitionStack[stackSize - 1] != dlid)
            return;
        definitionStack.pop();
        remove();
        --stackSize;
        if (stackSize > 0)
            definitions[definitionStack[stackSize - 1]].reqUnhover();
        else
            definitionBlock.innerHTML = '';
    }
    this.reqUnhover = reqUnhover;

    definitions[dlid] = this;
}

function respStatementSyntaxError()
{
    htmlAlert("Syntax error parsing statement or definition", "Argulator doesn't understand what you put between the curly braces. Try modifying your reqest.");
}

var defineTermsFor;
var defineTermsPart;

function respDefineTerms(text, forArgument, part)
{
    defineTermsFor = forArgument;
    defineTermsPart = part;
    popup("Define terms",
        "<div contenteditable='true'>" + text + "</div>" +
        "<div>Select the word or phrase you wish to define. Enter a definition to search for or create here:</div>" +
        "<form action='javascript:reqDefinitionSearch();'>" +
            "<div><input type='text' name='definition_search' id='definition_search' size='100' onkeyup='reqDefinitionSearchModified();' /> " +
            "<input type='submit' id='definition_search_button' disabled='disabled' value='Search' /></div>" +
        "</form>" +
        "<div><button onclick='return hidePopup();'>Cancel</button></div>");
}

function reqDefinitionSearchModified()
{
    // TODO
}

function reqDefinitionSearch()
{
    // TODO
}

