// Initialize Globals
ai = "Wilbort"; // To tell common.js to start auto-update, focus caret to message box onload.
last_id = 0; // Initialize latest message ID value.
nickname = ""; // Initialize nickname
show = false; // Show all messages (false) or own messages (true)
fetch_interval = 10000; // Fetch interval, in milliseconds. Default 10000 (10 seconds, 6 updates/minute)
throbber_timeout = 100; // Hide throbber after successful update, in milliseconds. Default 100 (10th of 1 second)
refresh = true; // Auto-refresh
// Dismiss error link. Whuteva, whuteva, I don't care 'bout yo' erras.
dismiss_error = " <a href='/ai' onclick='dismissError();return false;' class='dismiss_error' title='dismiss this error'>[&times;]</a> ";
var RecaptchaOptions = {
   theme : 'clean',
   tabindex : 2
};

// Main AJAX function: Handles updates on interval, message submit, and update latest messages.
function makeRequest(submitMsg,retrieveLatest) {
    // If message submitted...
    if (submitMsg) {
        document.getElementById("message").readOnly = true;
        nickname = getContents("value", "nickname").trim(); // First thing: Get Nickname.
        var msgToSend = getContents("value", "message").trim(); // Second thing: Read form field.
        if (do_captcha) {
            var recaptchaChallenge = getContents("value", "recaptcha_challenge_field");
            var recaptchaResponse = getContents("value", "recaptcha_response_field"); // Third: get CAPTCHA
        }
        focusCaret("message"); // Refocus form field.
        // If no message...
        if (msgToSend.length == 0 && !recaptchaResponse) {
            updateContents("HTML", "error", dismiss_error+"Empty message.");
            document.getElementById("message").readOnly = false;
            return false; // Leave.
        }
        if (msgToSend.substring(0,5).toLowerCase() == "/nick") {
            var newNick = msgToSend.substring(6).trim().substring(0,20);
            clearContents("value", "message");
            if (newNick.length > 0)  {
                updateContents("value", "nickname", newNick, "replace");
                // to do: store nick
            } else {
                updateContents("HTML", "error", dismiss_error+"/nick Syntax: <b>/nick <u>new_nickname</u></b>");
            }
            document.getElementById("message").readOnly = false;
            return false;
        }
    }
    
    show = document.getElementById("show").checked;

    showThrobber(1); // Show green throbber to indicate activity.
    
    var httpRequest; // Initialize httpRequest variable.

    // Initialize httpRequest object instance.
    if (window.XMLHttpRequest) { // Mozilla, Safari, ...
        httpRequest = new XMLHttpRequest();
        if (httpRequest.overrideMimeType) {
            httpRequest.overrideMimeType('text/xml');
        }
    } 
    else if (window.ActiveXObject) { // IE
        try {
            httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
        } 
        catch (e) {
            try {
                httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
            } 
            catch (e) {}
        }
    }

    // If client doesn't support XMLHttp[Request]...
    if (!httpRequest) {
        // Show an error with option to reload page.
        updateContents("HTML", "error", dismiss_error+"Unable to connect to server. <a href='/ai'>Reload the page</a>, update your browser, or disable JavaScript.", "replace");
        showThrobber(3); // Turn throbber red to indicate failed update.
        schedRequest(); // Try again in fetch_interval ms.
        document.getElementById("message").readOnly = false;
        return false; // Welp, there's not much more to do if we can't use AJAX, so leave.
    }
    
    // Handle output using parseContents()
    httpRequest.onreadystatechange = function() { parseContents(httpRequest,submitMsg,retrieveLatest); };
    // If user is submitting message...
    if (submitMsg) {
        // Do our AJAX form submit dealy :D
        var params = "ajax=true&nickname="+urlencode(uni2ent(nickname))+"&message="+urlencode(uni2ent(msgToSend))+(do_captcha ? "&recaptcha_challenge_field="+urlencode(uni2ent(recaptchaChallenge))+"&recaptcha_response_field="+urlencode(uni2ent(recaptchaResponse)) : "");
        httpRequest.open('POST', '/ai', true);
        httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
        httpRequest.setRequestHeader("Content-length", params.length);
        httpRequest.setRequestHeader("Connection", "close");
        httpRequest.send(params);
    } else {
        // Otherwise, get normal update.
        // retrieveLatest determines whether we're updating the entire latest messages list
        httpRequest.open('GET', '/ai?ajax=true'+(retrieveLatest ? '&retrieve_latest=true' : '')+(show ? '&show=own' : ''), true);
        httpRequest.send('');
    }

}

// Result data handling function
function parseContents(httpRequest,submitMsg,retrieveLatest) {
    
    // If final state (ready to handle data)
    if (httpRequest.readyState == 4) {
        // If response = HTTP OK
        if (httpRequest.status == 200) {
            var msg = httpRequest.responseText.split("\n\n\n"); // Create an array from response
            var msg_success = msg[0]; // true if query succeeded, false if failed.
            var msg_error = msg[1]; // Error message to put on error line.
            var msg_id = msg[2]; // ID of the most recent message in DB (if this number changes, reget latest messages)
            var msg_captcha = msg[3]; // CAPTCHA response value (0=N/A, 1=success, 2=failure)
            // If we need to redisplay the CAPTCHA (session timeout)
            if ((msg_captcha == 3) && !do_captcha) {
                do_captcha = true;
                Recaptcha.create("6LcshAIAAAAAAC6nV3puFPU2HcbA_YM8zD4ozdXO",
                    "captcha", {
                    theme: "clean",
                    tabindex : 2
                });
                updateContents("HTML", "error", dismiss_error+"Session has expired. Please re-enter the CAPTCHA above to continue.", "replace"); // Update error line.
            }
            // If query succeeded...
            if (msg_success == "true") {
                // If an error was reported...
                if (msg_error) {
                    if (msg_captcha == 2) {
                        Recaptcha.reload();
                    }
                    updateContents("HTML", "error", dismiss_error+msg_error, "replace"); // Update error line.
                    showThrobber(0); // Reset throbber; only turns red if there's a comm. error.
                    schedRequest(); // Resume as usual.
                } else {
                    if (submitMsg) {
                        clearContents("value", "message"); // Reset form field
                        document.getElementById("message").readOnly = false;
                        clearContents("HTML", "error"); // and error line.
                        showThrobber(2); // Blue throbber, we need to...
                        if (msg_captcha == 1) {
                            do_captcha = false;
                            Recaptcha.destroy();
                            clearContents("HTML", "captcha");
                        }
                        makeRequest(false,true); // Update latest messages.
                    } else {
                        // If we're in the second stage of retrieving latest messages...
                        if (retrieveLatest) {
                            if (typeof(msg[4]) != "undefined") {
                                var msg_messages = msg[4];
                                msg_messages = msg_messages.split("\n\n"); // Contains latest messages, only if re-retrieving them.
                                // If messages HTML has content, and has actually changed...
                                if (msg_messages.length > 0) {
                                    // Update latest messages box
                                    var msg_message_html = '';
                                    for (var i in msg_messages) {
                                        var msg_message = msg_messages[i].split("\n"); // id, time, is_own, nickname, message, reply
                                        var this_msg_id = msg_message[0];
                                        var this_msg_time = msg_message[1].split(" - ");
                                        var this_msg_own = msg_message[2];
                                        var this_msg_nick = msg_message[3].trim();
                                        var this_msg_msg = msg_message[4].trim();
                                        var this_msg_reply = msg_message[5].trim();
                                        msg_message_html += '<span class="msg_message" id="msg_message_'+this_msg_id+'"><span class="time" title="'+this_msg_time[0]+'">['+this_msg_time[1]+']</span> &lt;<b class="'+(this_msg_own == 'true' ? 'msg_own' : 'msg_else')+'">'+(this_msg_nick ? this_msg_nick : 'anonymous')+'</b>&gt; '+this_msg_msg+'</span><br /> \n';
                                        msg_message_html += '<span class="msg_reply" id="msg_reply_'+this_msg_id+'"><span class="time" title="'+this_msg_time[0]+'">['+this_msg_time[1]+']</span> &lt;<b class="msg_ai">'+ai+'</b>&gt; '+this_msg_reply+'</span><br /> \n';
                                    }
                                    updateContents("HTML", "messages", msg_message_html, "replace");
                                    scrollToBottom("messages");
                                }
                            }
                            showThrobber(0); // Reset throbber, we're done.
                            schedRequest(); // Resume as usual.
                        // If we're in the initial server contact...
                        } else {
                            // If latest message ID or num queued messages has changed,
                            // we need to update latest messages (see if (retrieveLatest))
                            if (msg_id != last_id) {
                                last_id = msg_id; // Update value.
                                showThrobber(2); // Blue throbber, we need to...
                                makeRequest(false,true); // Update latest messages.
                            // Regular update with nothing new.
                            } else {
                                showThrobber(0); // Reset throbber, we're done.
                                schedRequest(); // Resume as usual.
                            }
                        }
                    }
                }
            // If query failed, or output is unexpected.
            // This can happen sometimes if the server is busy or unresponsive.
            } else {
                // If an error was reported...
                if (msg_error) {
                    updateContents("HTML", "error", dismiss_error+msg_error, "replace"); // Update error line.
                }                    
                showThrobber(3); // Red throbber, indicate this update failed
                schedRequest(); // But resume as usual to attempt again in fetch_interval ms.
            }
        } else {
            // If response code is NOT 200, server responded not HTTP OK.
            // Again, this can happen, but should be rare.
            // Disabling verbose error for now, as this tends to be a one-off thing.
            // updateContents("HTML", "error", "Could not retrieve new messages. <a href='/ai'>Reload the page.</a>", "replace");
            showThrobber(3); // Red throbber, indicate this update failed
            schedRequest(); // But resume as usual to attempt again in fetch_interval ms.
        }
    }

}

// Auto-update timer function, called onload, and after every completed request.
function schedRequest() {
    cancelRequest();
    refresh = document.getElementById("refresh").checked;
    if (refresh) {
        // Set timeout to fetch_interval ms to ping for updates.
        nextRequest = setTimeout(function(){makeRequest(false,false);}, fetch_interval);
    }
}

function cancelRequest() {
    // Clear previous timeout to prevent cascading requests
    if (typeof nextRequest == "number") {clearTimeout(nextRequest);}
}

// This crazy-ass function allows for sending unicode characters through XMLHttp[Request].
function uni2ent(srcTxt) {
  var entTxt = '';
  var c, hi, lo;
  var len = 0;
  for (var i=0, code; code=srcTxt.charCodeAt(i); i++) {
    var rawChar = srcTxt.charAt(i);
    // needs to be an HTML entity
    if (code > 255) {
      // normally we encounter the High surrogate first
      if (0xD800 <= code && code <= 0xDBFF) {
        hi  = code;
        lo = srcTxt.charCodeAt(i+1);
        // the next line will bend your mind a bit
        code = ((hi - 0xD800) * 0x400) + (lo - 0xDC00) + 0x10000;
        i++; // we already got low surrogate, so don't grab it again
      }
      // what happens if we get the low surrogate first?
      else if (0xDC00 <= code && code <= 0xDFFF) {
        hi  = srcTxt.charCodeAt(i-1);
        lo = code;
        code = ((hi - 0xD800) * 0x400) + (lo - 0xDC00) + 0x10000;
      }
      // wrap it up as Hex entity
      c = "" + code.toString(16).toUpperCase() + ";";
    }
    else {
      c = rawChar;
    }
    entTxt += c;
    len++;
  }
  return entTxt;
}

// URL-encode hack for JavaScript to fix plus signs being sent through XMLHttp[Request].
function urlencode (str) {
    str = (str+'').toString();
    // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current
    // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following.
    return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').
                                                                    replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
}

// Sloppy function for decoding HTML entities
function html_entity_decode(str) {
    try {
        var tarea=document.createElement('textarea');
        tarea.innerHTML = str; return tarea.value;
        tarea.parentNode.removeChild(tarea);
    } catch(e) {
        //for IE add <div id="htmlconverter" style="display:none;"></div> to the page
        document.getElementById("htmlconverter").innerHTML = '<textarea id="innerConverter">' + str + '</textarea>';
        var content = document.getElementById("innerConverter").value;
        document.getElementById("htmlconverter").innerHTML = "";
        return content;
    }
}

// Change the state of the activity indicator
function showThrobber(show) {
    if (typeof hideThrobber == "number") {clearTimeout(hideThrobber);}
    switch (show) {
        case 1:
            // Green: Submit data/receive reply.
            updateContents("HTML", "throbber", "&gt; &gt; &gt; ", "replace");
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#00FF00";
            break;
        case 2:
            // Blue: Second stage of an update, receive additional data.
            updateContents("HTML", "throbber", "&lt; &lt; &lt; ", "replace");
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#0000FF";
        case 3:
            // Red: Data exchange error :<
            updateContents("HTML", "throbber", "&times; &times; &times; ", "replace");
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#FF0000";
            break;
        default:
            // Black, hidden after throbber_timeout ms: Data exchange complete :>
            updateContents("HTML", "throbber", "&lt; &lt; &lt; ", "replace");
            document.getElementById("throbber").style.color = "#000000";
            hideThrobber = setTimeout(function(){document.getElementById("throbber").style.visibility = "hidden";}, throbber_timeout)
    }
}

// Focus caret to a specific element (like the "message" text field).
function focusCaret(field) {
    fields = field.split("||");
    var i = 0;
    while (document.getElementById(fields[i]).value != "" && i < fields.length-1) {
        i++;
    }
    document.getElementById(fields[i]).focus();
}

// Update the HTML of an element, or the value of a form field.
function updateContents(type, field, contents, mode) {
    switch (type) {
        // For mutating DIVs, SPANs, other non-interactive elements
        case "HTML":
            if (mode != "replaceIfEmpty" || document.getElementById(field).innerHTML.length == 0) {
                document.getElementById(field).innerHTML = (mode == "append" ? document.getElementById(field).innerHTML : "")+contents+(mode == "prepend" ? document.getElementById(field).innerHTML : "");
            }            
            break;
        // For mutating INPUTs, SELECTs, other form fields
        case "value":
            if (mode != "replaceIfEmpty" || document.getElementById(field).value.length == 0) {
                document.getElementById(field).value = (mode == "append" ? document.getElementById(field).value : "")+contents+(mode == "prepend" ? document.getElementById(field).value : "");
            }
            break;
        default:
            if (mode != "replaceIfEmpty" || field.length == 0) {
                field = (mode == "append" ? field : "")+contents+(mode == "prepend" ? field : "");
            }
    }
}

// Get the HTML of an element, or the value of a form field.
function getContents(type, field) {
    switch (type) {
        // For fetching DIVs, SPANs, other non-interactive elements
        case "HTML":
            return document.getElementById(field).innerHTML;
            break;
        // For fetching INPUTs, SELECTs, other form fields
        case "value":
            return document.getElementById(field).value;
            break;
        default:
            return field;                                                                        
    }
}

// Clear the HTML/value of an element/field.
function clearContents (type, field) {
    updateContents(type, field, "", "replace"); // Update with empty string.
}

// Just a specific shortcut.
function dismissError() {
    clearContents("HTML", "error"); // Clear the error line.
}

// Copy/move/prepend/append HTML/value from one element/field to another.
// mode can be replace, replaceIfEmpty, append, or prepend.
// deleteSource moves instead of copies. entityDecode decodes HTML entities for forms.
function copyContents(sourceType, source, destType, dest, mode, deleteSource, entityDecode) {
    sourceContents = getContents(sourceType, source);
    if (entityDecode) {
        sourceContents = html_entity_decode(sourceContents);
    }
    updateContents(destType, dest, sourceContents, mode);
    if (deleteSource) {
        clearContents(sourceType, source);
    }
}

// Scrolls to the bottom of an element.
function scrollToBottom(field) {
    document.getElementById(field).scrollTop = document.getElementById(field).scrollHeight;
}

/*
// Steven D. Wilson
// 2010/07/16T16:26:00NDT-0230
// Butts.
*/