// Densely commented because I'm destined to be confused as hell later otherwise. - Wilson
// Initialize Globals
marquee = true; // To tell common.js to start auto-update, focus caret to message box onload.
last_id = 0; // Initialize latest message ID value.
last_num_new = 0; // Initialize queued messages value.
last_colour = 0; // Initialize the colour picker's default colour ID
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)
// Dismiss error link. Whuteva, whuteva, I don't care 'bout yo' erras.
dismiss_error = " <a href='/marquee' onclick='dismissError();return false;' class='dismiss_error' title='dismiss this error'>[&times;]</a> ";

// Main AJAX function: Handles updates on interval, message submit, and update latest messages.
function makeRequest(submitMsg,retrieveLatest) {
    // If message submitted...
    if (submitMsg) {
        var msgToSend = getContents("value", "message").trim(); // First thing: Read form field.
        var colourToSend = getContents("value", "colour"); // And get colour selection.
        focusCaret("message"); // Refocus form field.
        // If no message...
        if (msgToSend.length == 0) {
            updateContents("HTML", "error", dismiss_error+"A scrolling marquee of nothing, that's real cute. Try some words :P");
            return false; // Leave.
        }
    }

    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='/marquee'>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.
        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&colour="+urlencode(colourToSend)+"&message="+urlencode(uni2ent(msgToSend));
        httpRequest.open('POST', 'marquee.php', 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', 'marquee.php?ajax=true'+(retrieveLatest ? '&retrieve_latest=true' : ''), 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_num_new = msg[3]; // Number of messages in queue (if this number changes, reget latest messages)
            var msg_message = msg[4]; // Contains latest messages, only if re-retrieving them.
            // If query succeeded...
            if (msg_success == "true") {
                // If an error was reported...
                if (msg_error) {
                    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
                        clearContents("HTML", "error"); // and error line.
                        showThrobber(2); // Blue throbber, we need to...
                        makeRequest(false,true); // Update latest messages.
                    } else {
                        // If we're in the second stage of retrieving latest messages...
                        if (retrieveLatest) {
                            // If messages HTML has content, and has actually changed...
                            if (msg_message.length > 0 && msg_message != getContents("HTML", "messages")) {
                                // Update latest messages box
                                updateContents("HTML", "messages", msg_message, "replace");
                            }
                            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 || msg_num_new != last_num_new) {
                                last_id = msg_id; // Update value.
                                last_num_new = msg_num_new; // 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='/marquee'>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() {
    // Clear previous timeout to prevent cascading requests
    if (typeof nextRequest == "number") {clearTimeout(nextRequest);}
    // Set timeout to fetch_interval ms to ping for updates.
    nextRequest = setTimeout(function(){makeRequest(false,false);}, fetch_interval);
}

// 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.
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#00FF00";
            break;
        case 2:
            // Blue: Second stage of an update, receive additional data.
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#0000FF";
        case 3:
            // Red: Data exchange error :<
            document.getElementById("throbber").style.visibility = "visible";
            document.getElementById("throbber").style.color = "#FF0000";
            break;
        default:
            // Black, hidden after throbber_timeout ms: Data exchange complete :>
            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) {
    document.getElementById(field).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);
    }
}

// Change message field text colour.
function chgColour(changeTo) {
    if (changeTo >= 0) {
        if (changeTo > document.getElementById("colour").options.length-1) {
            changeTo = 0;
        }
        document.getElementById("colour").selectedIndex = changeTo;
    }
    var newColour = document.getElementById("colour").options[document.getElementById("colour").selectedIndex].style.backgroundColor;
    document.getElementById("colour").style.backgroundColor = newColour;
    document.getElementById("colour").style.color = newColour;
    document.getElementById("message").style.color = newColour;
}

/*
// Steven D. Wilson
// 2010/02/25T01:07:00NST-0330
// Butts.
*/