//
// +-------------------+
// | Generic Utilities |
// +-------------------+
//

/**
 * Safer replacement for document.getElementById.
 */
function get_object(obj)
{
    if (typeof obj == "object")
    {
        return obj;
    }

    if (document.all)
    {
        return document.all(obj);
    }
    else if (document.getElementById)
    {
        return document.getElementById(obj);
    }

    return null;
}

//
// The following code declares three functions and adds them to the
// HTMLElement prototype class: insertAdjacentElement, insertAdjacentHTML, and
// insertAdjacentText. These functions are available in MSIE 4+ and are not
// redefined if they already exist. You can find documentation for these
// functions on the internet.
//

if (typeof HTMLElement != "undefined" &&
    !HTMLElement.prototype.insertAdjacentElement)
{
	HTMLElement.prototype.insertAdjacentElement = function (where, node)
	{
		switch (where){
		case 'beforeBegin':
            this.parentNode.insertBefore(node, this);
			break;
		case 'afterBegin':
			this.insertBefore(node, this.firstChild);
			break;
		case 'beforeEnd':
			this.appendChild(node);
			break;
		case 'afterEnd':
			if (this.nextSibling)
            {
                this.parentNode.insertBefore(node, this.nextSibling);
            }
			else
            {
                this.parentNode.appendChild(node);
            }
			break;
		}
	}

	HTMLElement.prototype.insertAdjacentHTML = function (where, html)
	{
		var r = this.ownerDocument.createRange();
		r.setStartBefore(this);
		this.insertAdjacentElement(where, r.createContextualFragment(html));
	}

	HTMLElement.prototype.insertAdjacentText = function (where, text)
	{
		this.insertAdjacentElement(where, document.createTextNode(text));
	}
}

/**
 * Returns true if an only if the given element (which may either be an id or
 * the actual DOM object) exists in the document and has the given css class.
 */
function has_css_class(el, class_name)
{
    return (el = get_object(el)) && el.className.indexOf(class_name) > -1;
}

/**
 * Adds a CSS class to the specified element (which may either be an id or the
 * actual DOM object). If the element already has the class or if no element
 * with the specified id exists in the document, this function has no effect.
 */
function add_css_class(el, class_name)
{
    if ((el = get_object(el)) && !has_css_class(el, class_name))
    {
        el.className += ' ' + class_name;
    }
}

/**
 * Similar to add_css_class, except this function removes the css class.
 */
function remove_css_class(el, class_name)
{
    if ((el = get_object(el)) && has_css_class(el, class_name))
    {
        var pos = el.className.indexOf(class_name);
        el.className = el.className.substring(0, pos)
            + el.className.substring(pos + class_name.length);
    }
}

/**
 * Applies a function to elements that are descendants of the specified parent
 * element and have the specified css_class. If descend_beyond_matches is
 * true, the function will also be applied to the descendants of nodes that
 * satisfy the match criteria; otherwise, matched elements will be treated as
 * leaf nodes and their descendants will not be considered.
 */
function apply_to_descendants_by_css_class(first_parent,
                                           css_class,
                                           descend_beyond_matches,
                                           fn)
{
    function helper(parent)
    {
        for (var el = parent.firstChild; el; el = el.nextSibling)
        {
            var matched = false;

            if (el.nodeType == 1 && has_css_class(el, css_class))
            {
                fn(el);
                matched = true;
            }

            if (!matched || descend_beyond_matches)
            {
                helper(el, css_class);
            }
        }
    }

    helper(get_object(first_parent), css_class);
}

/**
 * Similar to apply_to_descendants_by_css_class, except this function returns
 * a list of matching elements instead of applying a function to them.
 */
function find_descendants_by_css_class(first_parent,
                                       css_class,
                                       descend_beyond_matches)
{
    var answer = new Array();

    apply_to_descendants_by_css_class(
        first_parent,
        css_class,
        descend_beyond_matches,
        function (el) { answer.push(el) });

    return answer;
}

/**
 * Evaluates the contents of all the script tags that are children of a
 * certain element. This is designed to evaluate javascript which is sent with
 * the HTML in an ajax response.
 */
function eval_children(id)
{
    var e = get_object(id)

    if (e)
    {
        var scripts = e.getElementsByTagName('script');

        for (var i = 0; i < scripts.length; i++)
        {
            if (!scripts[i].innerHTML.match(/document\.write/))
            {
                var scriptHTML = scripts[i].innerHTML;
                var funcs = scriptHTML.match(/function\s+\w+\s*\(/g);
                if (funcs && funcs.length > 0)
                {
                    for (var j = 0; j < funcs.length; j++)
                    {
                        funcs[j] = funcs[j].replace(/^function\s+|\s*\($/g,'');
                        var new_func = '__local_' + funcs[j];

                        // This moves the functions into the global scope
                        scriptHTML = scriptHTML + ' '
                            + new_func + ' = ' + funcs[j] + '; '
                            + 'delete ' + funcs[j] + '; '
                            + funcs[j] + ' = ' + new_func + ';';
                    }
                }
                eval(scriptHTML);
            }
        }
    }
}

/**
 * Similar to the PHP built-in number_format function, but more limited in
 * scope. This function only adds thousands separators.
 */
function number_format(n)
{
    n += '';  // convert n to a string

    var answer = '';
    var i = n.length-1;

    for (; i >= 3; i -= 3)
    {
        answer = ',' + n.substr(i-2, 3) + answer;
    }
    if (i >= 0)
    {
        answer = n.substr(0, i + 1) + answer;
    }
    else
    {
        answer = answer.substr(1);
    }

    return answer;
}

/**
 * Given an associative array, returns a list containing the array's keys.
 */
function array_keys(a)
{
    var keys = new Array();
    for (var k in a)
    {
        keys.push(k);
    }
    return keys;
}

/**
 * Given an associative array, returns a list containing the array's values.
 */
function array_values(a)
{
    var values = new Array();
    for (var v in a)
    {
        values.push(a[v]);
    }
    return values;
}

/**
 * Returns an array of selected options.
 */
function get_selected_options(select_element)
{
    var options = select_element.options; // for convenience

    var selected_options = new Array();

    for (var i = 0; i < options.length; i++)
    {
        if (options[i].selected)
        {
            selected_options.push(options[i]);
        }
    }

    return selected_options;
}

function get_options(e)
{
    var options = new Array();
    for (var i = 0; i < e.options.length; i++)
    {
        options[e.options[i].value] = e.options[i].text;
    }
    return options;
}

function clear_select(e)
{
    for (var i = e.options.length; i >= 0; i--)
    {
        e.options[i] = null;
    }

    e.selectedIndex = -1;
}

function populate_select(e, values, map, msg, selected)
{
    if (values.length == 0)
    {
        e.options[0] = new Option('', '');
    }
    if (e.options.length == 0 && msg != null)
    {
        e.options[0] = new Option(msg, '');
    }

    for (var i = 0; i < values.length; i++)
    {
        e.options[e.options.length] = new Option(map[values[i]], values[i]);
        if (values[i] == selected)
        {
            e.options[e.options.length-1].selected = 1;
        }
    }
}

/**
 * Populates a multiselect. "e" is the select element to be populated;
 * "values" is an array of values with which to populate the select; "map" is
 * a mapping of values to their associated text; selected is an array
 * consisting of selected values mapped to booleans.
 */
function populate_multiselect(e, values, map, selected)
{
    for (var i = 0; i < values.length; i++)
    {
        e.options[e.options.length] = new Option(map[values[i]], values[i]);

        if (selected && selected[values[i]])
        {
            e.options[e.options.length-1].selected = 1;
        }
    }
}

function qualify_url_using_base_elem(url)
{
    var base_elem = document.getElementsByTagName("base").item(0);
    if (!base_elem) { // no tag to respect
        return url;
    }

    var base_url_regexp = new RegExp("^[a-z]+://[^/]+");
    var base_url = base_url_regexp.exec(base_elem.href)[0];

    var rel_base_url_regexp =
        new RegExp("^([a-z]+://.+/)[^.]+\.[^.]+$");
    var rel_base_url_parts = rel_base_url_regexp.exec(document.location);
    var rel_base_url = '';
    if (rel_base_url_parts && rel_base_url_parts.length > 1) {
        rel_base_url = rel_base_url_parts[1];
    }

    var qualified_url;

    // check whether url is qualified
    if (base_url_regexp.exec(url)) { // qualified
        return url;
    }
    if (url.length > 0 && url.charAt(0) == '/') { // absolute
        return base_url + url;
    }

    // relative
    return rel_base_url + url;
}

function redirect(url)
{
    window.location=url;
}

/**
 * Given a radio button form element, returns a pair of values: the index of
 * the selected option and the corresponding option value. If no value was
 * selected, returns undefined.
 */
function get_selected_radio_value(el)
{
    for (var i = 0; i < el.length; i++) {
        if (el[i].checked) {
            return [i, el[i].value];
        }
    }
}

function has_checked(f, name)
{
    for (i = 0; i < f.length; i++)
    {
        if (f.elements[i].name == name &&
            f.elements[i].type == "checkbox" &&
            f.elements[i].checked)
        {
            return true;
        }
    }

    return false;
}

function check_all(f, name)
{
    for (i = 0; i < f.length; i++)
    {
        if (f.elements[i].name == name &&
            f.elements[i].type == "checkbox")
        {
            f.elements[i].checked = true;
        }
    }
}

function uncheck_all(f, name)
{
    for (i = 0; i < f.length; i++)
    {
        if (f.elements[i].name == name &&
            f.elements[i].type == "checkbox" &&
            (typeof(uncheck_all.arguments[2]) == undefined ||
             uncheck_all.arguments[2] != f.elements[i].value))
        {
            f.elements[i].checked = false;
        }
    }
}

function uncheck_by_value(f, name, value)
{
    for (i = 0; i < f.length; i++)
    {
        if (f.elements[i].name == name &&
            f.elements[i].type == "checkbox" &&
            f.elements[i].value == value)
        {
            f.elements[i].checked = false;
        }
    }
}

function click_no_pref_checkbox(elem)
{
    if (elem.checked)
    {
        if (elem.value == '')
        {
            uncheck_all(elem.form, elem.name, '');
        }
        else
        {
            uncheck_by_value(elem.form, elem.name, '');
        }
    }
}

function submit_to_popup(f, url, name, width, height)
{
    var args = '?';

    for (i = 0; i < f.length; i++)
    {
         e = f.elements[i];

         if (e.name == '')
         {
             continue;
         }

         if ((e.type == 'checkbox' || e.type == 'radio') && e.checked == true)
         {
             value = e.value;

             if (value == '')
             {
                 value = 1;
             }

             args = args + e.name + '=' + escape(value) + '&';
         }
         else if (e.type == 'text' || e.type == 'hidden' || e.type == 'textarea')
         {
             args = args + e.name + '=' + escape(e.value) + '&';
         }
         else if (e.type == 'select-one' && e.options.selectedIndex > 0)
         {
             args = args + e.name + '=' + escape(e.options[e.options.selectedIndex].value) + '&';
         }
    }

    child = window.open(url + args, name, 'toolbar=no,resizable=yes,location=no,scrollbars=yes,width=' + width + ',height=' + height);
    child.focus();

    return child;
}

/**
 * Function expects the id (a string) or object reference of both the text
 * element to be counted (text_elem_id) and the span object that will contain
 * the character count string. String the is the string that will be used to
 * display the character count. The function replaces {CHAR} with the
 * character count. e.g. "{CHAR} of 150 chars." or just "{CHAR}".
 */
function count_chars(text_elem_id, char_count_span_id, string)
{
    var text_elem = get_object(text_elem_id);
    var char_count_span = get_object(char_count_span_id);

    var text_value = text_elem.value;

    var crlf_re = new RegExp("\r\n", "g");
    text_value = text_value.replace(crlf_re, '\n');

    string = string.replace("{CHAR}", text_value.length);
    char_count_span.innerHTML = number_format(string);
}

/**
 * Object for requesting an external url (GET), placing the output into
 * the supplied element (which must have the innerHTML attribute) AND/OR
 * calling the specified callback function with the output.
 * e.g. <span id="external_content">
 * NOTES:
 * - elem can be either an object reference or a string containing the
 *   id of the element OR null if you don't want the content placed into
 *   any element.
 * - your callback (if any) should accept one paramter for the output
 */
function External_Content(p_url, p_elem, p_callback)
{
    var elem = p_elem;
    var url  = p_url;
    var num_times_loaded = 0;
    var last_open_exception;

    this.load = function()
    {
        var req = create_xml_http_request();

        req.onreadystatechange =
            function()
            {
                if (req.readyState == 4)
                {
                    num_times_loaded++;
                    if (elem)
                    {
                        elem = get_object(elem);
                        elem.innerHTML = req.responseText;
                    }
                    if (p_callback)
                    {
                        p_callback(req.responseText);
                    }
                }
            }

        try
        {
            req.open("GET", add_cache_burster_to_url(url), true);
            req.send(null);
        }
        catch (e)
        {
            last_open_exception = e;
        }
    }

    this.get_num_times_loaded = function()
    {
        return num_times_loaded;
    }

    this.get_last_open_exception = function()
    {
        return last_open_exception;
    }
}


/**
 * Object for requesting an external xml doc, placing the output into
 * the specified callback function with the output object.
 */
function External_XML_Content(p_url, p_callback)
{
    var url  = p_url;
    var num_times_loaded = 0;
    var last_open_exception;

    this.load = function()
    {
        var req = create_xml_http_request();
        req.onreadystatechange =
            function()
            {

                if (req.readyState == 4)
                {
                    num_times_loaded++;
                    if (p_callback)
                    {
                        p_callback(req.responseXML);
                    }
                }
            }

        try
        {
            req.open("GET", add_cache_burster_to_url(url), true);
            req.send(null);
        }
        catch (e)
        {
            last_open_exception = e;
        }
    }

    this.get_num_times_loaded = function()
    {
        return num_times_loaded;
    }

    this.get_last_open_exception = function()
    {
        return last_open_exception;
    }
}

/**
 * Given a UNIX timestamp, returns a formatted date string in either the user's
 * timezone or UTC, depending on the value of use_local_tz. The format of the
 * string is adjusted relative to the current date and time.
 */
function format_ts_offset(t, use_local_tz, show_time)
{
    // Formats d as time of day only.
    function time_of_day(d)
    {
        var hh = d.getHours();
        var mi = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes();
        var meridian = 'am';

        if (hh >= 12)
        {
            meridian = 'pm';
            if (hh > 12)
            {
                hh -= 12;
            }
        }
        else if (hh == 0)
        {
            hh = 12;
        }

        return hh + ':' + mi + ' ' + meridian;
    }

    // Formats d as month and day only.
    function day_of_year(d)
    {
        var mon;
        switch (d.getMonth()) {
        case  0: mon = 'Jan';  break;
        case  1: mon = 'Feb';  break;
        case  2: mon = 'Mar';  break;
        case  3: mon = 'Apr';  break;
        case  4: mon = 'May';  break;
        case  5: mon = 'June'; break;
        case  6: mon = 'July'; break;
        case  7: mon = 'Aug';  break;
        case  8: mon = 'Sep';  break;
        case  9: mon = 'Oct';  break;
        case 10: mon = 'Nov';  break;
        case 11: mon = 'Dec';  break;
        }
        return mon + ' ' + d.getDate();
    }

    // Formats d as a full date (month, day, and year).
    function full_date(d)
    {
        var yy = d.getFullYear() % 100;
        if (yy < 10)
        {
            yy = '0' + yy;
        }
        return (d.getMonth() + 1) + '/' + d.getDate() + '/' + yy;
    }

    var d = new Date(t * 1000);
    if (!use_local_tz)
    {
        d.setTime(d.getTime() + ((d.getTimezoneOffset() * 60) * 1000));
    }

    var now = new Date();
    var strtime = '';
    if (show_time)
    {
        strtime = ', ' + time_of_day(d);
    }

    if (d.getFullYear() == now.getFullYear())
    {
        if (d.getMonth() == now.getMonth() && d.getDate() == now.getDate())
        {
            if (show_time)
            {
                strtime = 'Today at ';
            }
            return strtime + time_of_day(d);
        }
        return day_of_year(d) + strtime;
    }
    return full_date(d) + strtime;
}

/**
 * Given a UNIX timestamp, returns a formatted date and time string in the user's
 * timezone. The format of the string is adjusted relative to the current date
 * and time.
 */
function format_ts_datetime(t)
{
    return format_ts_offset(t, true, true);
}

/**
 * Given a UNIX timestamp, returns a formatted date string in the user's
 * timezone. The format of the string is adjusted relative to the current date
 * and time.
 */
function format_ts(t)
{
    return format_ts_offset(t, true, false);
}

/**
 * Given a UNIX timestamp, returns a formatted date string in UTC timezone.
 * The format of the string is adjusted relative to the current date and time.
 */
function format_ts_utc(t)
{
    return format_ts_offset(t, false, false);
}

/**
 * Similar to format_ts, this function actually prints the formatted timestamp
 * to the page.
 */
function print_ts(t)
{
    document.write(format_ts(t));
}

/**
 * Given a UNIX timestamp, returns the date range based on the user's cuurent
 * time zone.
 */
function format_ts_date_range(t)
{
    var now = new Date();

    var d = new Date(t * 1000);
    d.setTime(d.getTime() + ((d.getTimezoneOffset() * 60) * 1000));
    var time_diff = now.getTime() - d.getTime();
    var days = time_diff/1000/60/60/24;
    var day_diff = now.getDate() - d.getDate();

    if  (days <= 1 && day_diff == 0)
    {
        return "Today";
    }
    else if  (days <= 2 && day_diff == 1)
    {
        return "Yesterday";
    }
    else if  (days <= 3)
    {
        return "Within the last 3 days";
    }
    else if (days <= 7)
    {
        return "Within the last 7 days";
    }
    else if (days <= 14)
    {
        return "Within the last 2 weeks";
    }
    else if (days <= 30)
    {
        return "Within the last 30 days";
    }
    else if (days <= 90)
    {
        return "Within the last 3 months";
    }
    else if (days > 90)
    {
        return "More than 3 months ago";
    }
    return "Never logged in";
}

/**
 * Similar to format_ts_date_range, this function actually prints the formatted
 * range to the page.
 */
function print_ts_date_range(ts)
{
    document.write(format_ts_date_range(ts));
}

/*
 * Prints the current page of a document.
 */
function print_current_page()
{
    window.print();
}

/**
 * Returns f.g if f is a function, else returns g.
 */
function append_function(g, f)
{
    if (f && (typeof(f)).toUpperCase() == 'FUNCTION')
    {
        return function() { f(); g(); };
    }
    return g;
}

/**
 * Appends the function f to the window.onload function chain.
 */
function call_on_window_load(f)
{
    if (window.onload)
    {
        window.onload = append_function(f, window.onload);
    }
    else
    {
        window.onload = f;
    }
}

/**
 * Creates an XMLHTTPRequest object in a portable way.
 */
function create_xml_http_request()
{
    try
    {
        return new ActiveXObject('Microsoft.XMLHTTP');
    }
    catch (e)
    {
        return new XMLHttpRequest();
    }
}

/**
 * Asynchronously requests the specified url. When the request is complete,
 * calls the function f, passing it the xmlhttprequest object.
 */
function get_url_async(url, f)
{
    var req = create_xml_http_request();
    req.onreadystatechange = function() {
        if (req.readyState == 4)
            f(req);
    };
    req.open('GET', add_cache_burster_to_url(url), true);
    req.send('');
	return req;
}

/**
 * Like <code>get_url_async</code> but updates the &lsquo;onClick&rsquo; event
 * handler to do nothing while the request is processing. Should prevent users
 * from issuing the same request multiple times while the network is slow.
 * When the function returns the original onclick handler is replaced allowing
 * the user to re-issue the request.
 */
function get_url_async_set_state(source_element, url, f)
{
	var inRequestOCHandler=function inRequestOCHandler(){return false;};
	var originalOCHandler=null;

    var req = create_xml_http_request();
    req.onreadystatechange = function() {
        if (req.readyState == 4){
            f(req);

            //Original call has completed - if the triggering element
            //still exists in the document reset the on click handler
            //to whatever was originally there.
		    if (source_element && source_element.onclick)
		    {
		    	source_element.onclick=originalOCHandler;
		    }
        }
    };

    //Store the original handler and replace with a &lsquo;do nothing&rsquo;
    //handler - should prevent subsequent calls from be issued before the
    //initial call completes.
    if (source_element && source_element.onclick)
    {
    	originalOCHandler=source_element.onclick;
    	source_element.onclick=inRequestOCHandler;
    }

    req.open('GET', add_cache_burster_to_url(url), true);
    req.send('');
	return req;
}

/**
 * Eval javascript retrieved from a URL after window.onload.
 *
 * Use this instead of load_external_js_delayed.
 *
 * This function is useful for MSIE which has intermittent problems if the
 * document body has not been loaded before the javascript is evaluated.
 */
function eval_js_from_url_on_window_load(url)
{
    var retrieve_js_and_eval = function() {
        eval_js_from_url(key, url);
    };
    call_on_window_load(retrieve_js_and_eval);
}

function draw_deferred_presence_indicators(links, base_url)
{
    var callback = function(r) {
    var href = window.location;
    var usernames = array_keys(r.data);
    var online_status = array_values(r.data);

    var im_tooltip = 'Instant Message';

    for (var i = 0; i < usernames.length; i++)
    {
        var elem_ids = links[usernames[i]];

        for (var j = 0; j < elem_ids.length; j++)
        {
            var elem_id = elem_ids[j][0];

            // Dislplay userplane presence indicator icon
            // if the user is not stealthed.
            // _0 will be the id of the userplane icon
            // _1 is the id of the imps icon if it exists.
            if (online_status[i][1] == 1)
            {
                elem_id  = elem_id + '_0';
            }
            else
            {
                elem_id  = elem_id + '_1';
            }

            var node;
           //need to set display to a different status for userplane presence
            if ((node = document.getElementById(elem_id)))
            {
                node.style.display = 'inline';
            }
        }
    }

    };

    var url = base_url + escape(array_keys(links).join(' '));
    send_request(url, callback);
}

/**
 * Eval javascript retrieved from a URL.
 *
 * use this intead of load_external_js.
 */
function eval_js_from_url(url)
{
    get_url_async(url, _do_eval_ajax_js);
}

function _do_eval_ajax_js(req)
{
    eval(req.responseText);
}

/**
 * DEPRECATED: Please use eval_js_from_url directly.
 *
 * Loads an external javascript file at the given url. The caller may also
 * specify a refresh delay in seconds via the second argument. If the refresh
 * delay is given, the javascript file will be refreshed every N seconds.
 *
 * WARNING: This function assumes that the document body node has finished
 * loading. It will generate obscure errors, particularly on MSIE, if called
 * prior to the body being loaded. Use load_external_js_delayed instead if you
 * can't know a priori that the body has already loaded when you call this
 * function.
 */
function load_external_js(key, url)
{
    eval_js_from_url(url);
    var args = arguments;
    var refresh_in_sec = (args.length > 2) ? args[2] : 0;
    if (refresh_in_sec > 0)
    {
        setTimeout(function () { load_external_js(key, url, refresh_in_sec) },
                   1000 * refresh_in_sec);
    }
}

/**
 * DEPRECATED: Please use eval_js_from_url_on_window_load.
 *
 * Similar to load_external_js, except this function delays the loading of the
 * url until the current window's onload event is triggered. This insures that
 * the document body will have finished loading before load_external_js is
 * called.
 */
function load_external_js_delayed(key, url)
{
    var args = arguments;  // copying makes args a genuine array data type

    var f = (args.length == 2)
        ? function() { load_external_js(key, url) }
        : function() { load_external_js(key, url, args[2]) };

    call_on_window_load(f);
}

/**
 * Warning: This function should not be changed without also updating the
 * ui_Javascript::send_response function defined in ui/javascript.php!
 */
function send_request(url, f)
{
    var req = create_xml_http_request();
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            if (window.eval(f))
            {
                eval('f(' + req.responseText + ')');
            }
        }
    };
    req.open('GET', add_cache_burster_to_url(url), true);
    req.send('');

    return false;  // for notational convience
}

/**
 * Adjoins a query string to a URL.
 *
 * Note: if $url has a query string containing a variable specified by $table,
 * any references to the variable in $url will be deleted, and replaced using
 * the value in $table.
 */
function url_adjoin_qs(url, table)
{
    function cast_to_string(a)
    {
        if (typeof a == 'boolean') {
            return a ? '1' : '0';
        }

        if (typeof a == 'object' && !a) {
            return '';
        }

        return encodeURIComponent('' + a);
    }

    var parts = url.split('?');
    var vars = [];

    // Add to vars only those request vars that are NOT in the table.
    if (parts.length == 2)
    {
        var name_value_pairs = parts[1].split('&');
        for (var i = 0; i < name_value_pairs.length; i++)
        {
            var name = name_value_pairs[i].split('=')[0];
            if (name && !table.hasOwnProperty(name))
            {
                vars.push(name_value_pairs[i]);
            }
        }
    }

    // Now add to vars all of the name-value pairs in the table.
    for (var key in table)
    {
        vars.push(key + '=' + cast_to_string(table[key]));
    }

    return parts[0] + '?' + vars.join('&');
}

/**
 * Adds a unique request variable to the url to thwart web browsers that cache
 * pages too aggressively (shame on you IE).
 */
function add_cache_burster_to_url(url)
{
    return url_adjoin_qs(url, { _ctk_cache_burst : Math.random() });
}

/**
 * Used by ui_Core::render_ajax_change_value_action_link. See that function
 * for more information.
 */
function ajax_change_value_action(id, url, labels)
{
    function transition(to) {
        get_object(id).className = to;
        var e = get_object(id + '_label');
        e.onclick = "";
        e.removeAttribute('onclick');
        e.innerHTML = labels[to];
    }

    function set_value(v) {
        get_object(id + '_value').innerHTML = v;
    }

    transition('in_progress');

    send_request(
        url,
        function(resp) {
            if (resp['outcome'] != 'success')
                transition(resp['outcome']);
            else {
                transition('completed');
                set_value(resp['value']);
            }
        });

    return false;
}

/**
 * Used by ui_Core::render_ajax_score_links. See that function for more
 * information.
 */
function ajax_score_action(id, url, score, scale)
{
    function decide_class_name(scale_value) {
        var class_name = 'scale_' + scale_value + ' ';
        if (scale_value < score)
            return class_name + 'below';
        if (scale_value > score)
            return class_name + 'above';
        return class_name + 'selected';
    }

    get_object(id).className = 'scored';
    for (var i in scale) {
        var scale_value = scale[i];
        var e = get_object(id + '_' + scale_value);
        e.className = decide_class_name(scale_value);
    }

    send_request(url + '&score=' + score, function() {});
    return false;
}

/**
 * window.open in IE will not respect the base tag unless
 * it is called like:
 *
 * <a href="inbox/index.html" onclick="window.open(this.href...)">.
 *
 * This calling convention is not always feasible. As such, we replace the
 * window.open function with a more robust version. This also allows ties in
 * nicely with redirect_main_window.
 */
var __original_window_open;
function install_window_open()
{
    if (!__original_window_open) { // ensure we only do this once ever
        __original_window_open = window.open;
    }

    window.open = function(url, name, features, replace) {
        if (!arguments[1])
        {
            name = '';
        }
        if (!arguments[2])
        {
            features = '';
        }
        if (!arguments[3])
        {
            replace = '';
        }

        return __original_window_open(qualify_url_using_base_elem(url),
                                      name,
                                      features,
                                      replace);
    }
}

call_on_window_load(install_window_open);

/**
 * Loads the url in the main window
 */
function redirect_main_window(url)
{
    var w;
    if (window.opener)
    {
        w = window.opener.top;
    }
    else
    {
        w = top;
    }
    w.location = url;
    w.focus();
}

function setCookie(name, value, expires, path, domain, secure) {
    document.cookie= name + "=" + escape(value) +
        ((expires) ? "; expires=" + expires : "") +
        ((path) ? "; path=" + path : "") +
        ((domain) ? "; domain=" + domain : "") +
        ((secure) ? "; secure" : ""); }

function deleteCookie(name, path, domain) {
    if (getCookie(name)) {
        document.cookie = name + "=" +
            ((path) ? "; path=" + path : "") +
            ((domain) ? "; domain=" + domain : "") +
            "; expires=Thu, 01-Jan-70 00:00:01 GMT";
    }
}

function getCookie(name) {
    var dc = document.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1) {
        begin = dc.indexOf(prefix);
        if (begin != 0) return null;
    } else {
        begin += 2;
    }
    var end = document.cookie.indexOf(";", begin);
    if (end == -1) {
        end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
}

//
// +-------------------+
// | Instant Messenger |
// +-------------------+
//

/**
 * Launches the IM client. encrypted_username is the encrypted username of the
 * recipient.
 */
function launch_im(encrypted_username, area)
{
    up_launchWM('', encrypted_username, '', area);
}

function add_class_toggler_to_element(elem, class_name)
{
    var interval_id = null;

    function has_class(class_name)
    {
        return elem.className.indexOf(class_name) > -1;
    }

    function add_class(class_name)
    {
        if (has_class(class_name))
        {
            return;
        }

        elem.className += ' ' + class_name;
    }

    function remove_class(class_name)
    {
        var start_pos = elem.className.indexOf(class_name);
        var end_pos = start_pos + class_name.length;
        elem.className =
            elem.className.substring(0, start_pos) +
            elem.className.substring(end_pos);
    }

    if (elem.set_class_toggler_on) {
        elem.set_class_toggler_on(false); // turn off any pre-existing class
                                          // togglers
    }

    elem.set_class_toggler_on = function(turn_on) {
        if (turn_on) {
            if (interval_id) {
                return; // no need to do anything, we are already turned on
            }

            interval_id = setInterval(
                function() {
                    if (has_class(class_name)) {
                        remove_class(class_name);
                    }
                    else {
                        add_class(class_name);
                    }
                }, 1000);
        }
        else {
            remove_class(class_name);
            clearInterval(interval_id);
        }
    }
}

/**
 * Dynamically inserts pending im messages into the pending im alert section.
 * elem_im is the HTML element of the IM indicator. elem_alert is the HTML
 * element for the pending messages block. elem_messages is the HTML element
 * within elem_alert where messages will be inserted. users is an aray of
 * indexes into messages and ignores. open_im_console_fn is the function
 * that opens the IM client window for the appropriate site area.
 */
function render_pending_im_messages(elem_im,
                                    elem_alert,
                                    elem_messages,
                                    users,
                                    messages,
                                    open_im_console_fn)
{

    if (users.length == 0 || messages.length == 0)
    {
        return;
    }

    var content =
        '<table summary="" cellspacing="0" cellpadding="0" border="0" id="pending_message_table">';
    var has_new = false;

    for (var i = 0; i < users.length; i++)
    {
        if (typeof(messages[users[i]]) != 'undefined' &&
            messages[users[i]] != null)
        {
            has_new = true;
            content += messages[users[i]];
        }
    }

    content += '</table>';
    elem_messages.innerHTML = content;
    var elem_iframe = document.getElementById('alertbox_im_pending_msgs_iframe');

    add_class_toggler_to_element(elem_im, "incoming");

    if (has_new)
    {
        elem_im.set_class_toggler_on(true);

        // display pending message block
        elem_alert.style.display = 'block';
        elem_iframe.style.display = 'block';
        window.focus();
    }
    else
    {
        elem_im.set_class_toggler_on(false);
        elem_alert.style.display = 'none';
        elem_iframe.style.display = 'none';
    }
}

function remove_pending_im_message(username)
{
    var message_elem = document.getElementById('pending_im_' + username);

    message_elem.parentNode.removeChild(message_elem);

    var messages = document.getElementsByName('pending_im_msg');

    if (messages.length == 0)
    {
        document.getElementById('alertbox_im_pending_msgs').style.display = 'none';
        document.getElementById('alertbox_im_pending_msgs_iframe').style.display = 'none';
    }
}

/**
 * Calls the function f, passing it a string indicating the strength of the
 * provided password. The possible values are (from worst to best) "too
 * short", "weak", "fair", "good", and "strong".
 */
function analyze_password_strength(password, f)
{
    function match_all(patterns)
    {
        for (var i in patterns)
            if (!password.match(patterns[i]))
                return false;
        return true;
    }

    function all_letters()
    {
        return password.match(/^[A-Za-z]*$/)
    }
    function all_digits()
    {
        return password.match(/^[0-9]*$/)
    }
    function alphanumeric()
    {
        return match_all([ /^[A-Za-z0-9]*$/, /[A-Za-z]/, /[0-9]/ ])
    }
    function password_strength()
    {
        if (password.length == 0)
        {
            return '';
        }

        if (password.length < 6)
        {
            return 'too short';
        }

        if (all_digits() || all_letters())
        {
            return 'fair';
        }

        if (alphanumeric())
        {
            return 'good';
        }

        return 'strong';
    }

    f(password_strength());
}

/**
 * Performs validation of an email address.
 */
function validate_email_address(address)
{
    var email_validation_req =
        new External_Content(
                     '/user/validate_email.html?address=' + escape(address),
                     'email_status',
                     null);
    email_validation_req.load();
}

/**
 * Checks if a specific username is available for new account creation.
 */
function check_username_availability(username)
{
    var username_availability_req =
        new External_Content(
                     '/user/check_username_availability.html?username=' + escape(username),
                     'username_availability',
                     null);
    username_availability_req.load();
}

/**
 * Gets a user's age for underage registration check.
 */
function get_user_age(month, day, year)
{
    if (month && day && year)
    {
        var dob = new Date(year, month - 1, day);
        var now = new Date();

        var age = now.getFullYear() - dob.getFullYear();
        if (now.getMonth() < dob.getMonth() ||
            (now.getMonth() == dob.getMonth() && now.getDate() < dob.getDate()))
        {
            age--;
        }

        return age;
    }

    return null;
}

/**
 * Copies to clipboard (only in IE)
 */
function copy_to_clipboard(elem)
{
    elem.focus();
    elem.select();

    if (window.clipboardData)
    {
        window.clipboardData.setData('Text',elem.value);
    }
}

function delete_dating_search(id, name, num_columns)
{
    var confirmed = confirm('Click Ok to delete saved search: ' + name);
    if (confirmed)
    {
        var object = document.getElementById('search_' + id);
        object.style.display = 'none';

        var req = new External_Content(
            '/dating/search/remove.html?search_id=' + escape(id) + '&num_cols=' + escape(num_columns),
            'dating_saved_search_list');
        req.load();
    }
}

function gws_fake_dater_opt_out(page_log_upsell, gateway_section_id)
{
    var elem = document.getElementById(page_log_upsell);

    if (typeof elem != 'undefined') {
        elem.style.display = 'none';
    }
    new External_Content('/gateway/opt_out.html?gateway_section_id=' + gateway_section_id).load();
}

function dt_set_gallery_main_image(anchor_elem)
{
    clicked_image_elem = anchor_elem.getElementsByTagName('img')[0];
    src = new String(clicked_image_elem.src);
    src = src.replace("75x75", "300x300");
    src = src.replace('medium', 'ex-large');
    main_div_elem = document.getElementById('member_photo_viewer_main');
    main_img_elem = main_div_elem.getElementsByTagName('img')[0];
    main_img_elem.src = src;
}

/**
 * Gets the javascript for making an asynchronous request for search
 * result row apply clicks, to determine where to redirect to and whether
 * to open a new window (in the case of external applies).
 */
function jb_processApplyLinkResponse(r)
{
    if (r.error == 0) {
        if (r.new_window == 1) {
            window.open(r.redirect_url);
        } else {
            window.location = r.redirect_url;
        }
    }
}

function jb_getApplyLink(job_id, hbx_campaign_id)
{
    var applyLinkBase = "/jobs/job/apply_redirect.html?job_id=";
    var applyLink = applyLinkBase + job_id;

    if (hbx_campaign_id) {
        applyLink += "&hbx_campaign_id=" + hbx_campaign_id;
    }

    send_request(applyLink, jb_processApplyLinkResponse);
}

var im_Message_Renderer = new function()
{
    this.interval_in_seconds = 60;
    this.max_interval_in_seconds = 120;

    this.init = function(url)
    {
        setTimeout(function() {im_Message_Renderer.draw(add_cache_burster_to_url(url));} , this.interval_in_seconds*1000);
    }

    this.draw = function(url)
    {
        var head = document.getElementsByTagName('head').item(0);
        var script = document.getElementById('im_message_renderer');

        if (script)
        {
            script.parentNode.removeChild(script);
        }

        script = document.createElement('script');
        script.id = 'im_message_renderer';
        script.src = url;
        head.appendChild(script);

        this.interval_in_seconds = Math.min(this.interval_in_seconds,
                                     this.max_interval_in_seconds);

        if (this.interval_in_seconds < this.max_interval_in_seconds)
        {
            this.interval_in_seconds *= 2;
            setTimeout(function() {im_Message_Renderer.draw(add_cache_burster_to_url(url));} , this.interval_in_seconds*1000);
        }
    };
}
