// Define the number of days cookies will stay around for by default. Define
// the location of our right and down arrows for collapsible sections, full
// URL location; a down-facing arrow represents an open section and an
// right-facing arrow a closed section.
//
var cookie_max_days = 185;
var close_arrow = "http://thepeoplescoalition.org/images/calendar/right.gif";
var open_arrow = "http://thepeoplescoalition.org/images/calendar/down.gif";

// Define the URL of the local URL proxy server, to process remote URLs, and
// make a regular expression identifier to extract the hostname from a URL.
//
var url_proxy_server = "/php/url_proxy.php";
var hostname_from_url_id = /^http:\/\/(www.)?([^\/]+)/i;

// Toggle the opening of the passed in node's section. The passed in node is
// assumed to have a next sibling that is used to display the section. Also,
// the first child of the passed in node should be an image tag, for the arrow
// setting. Save preferences as appropriate.
//
function toggle_expand_section(node)
    {

    // Get the next sibling element of the passed in node; if we can't, eject
    // with an error message.
    //
    var next_sibling = element_relative(node, "nextSibling");
    if ( ! next_sibling)
        {die("Code error: Toggle expand section: No next sibling found");}

    // Use the passed in node's next sibling's display as an indicator of
    // whether the section is closed or not. If closed, set the source image
    // of the passed in node's first child to a open arrow and the display of
    // the node's next sibling to inline (displayed). If open, set to a close
    // arrow and to none.
    //
    if (next_sibling.style.display == "none")
        {
        next_sibling.style.display = "inline";
        node.firstChild.src = open_arrow;
        }
    else
        {
        next_sibling.style.display = "none";
        node.firstChild.src = close_arrow;
        }

    // Save the preferences of the two nodes that we changed.
    //
    save_pref(node.firstChild.id);
    save_pref(next_sibling.id);
    }

// Returns the next sibling element of the passed in node, or the previous
// sibling element if the second argument is true. Pass in a node and a
// relative type--such as "nextSibling", "previousSibling", "firstChild", or
// "lastChild"--and this function returns the corresponding element relative
// (not any other node types, such as text), if one exists.
//
function element_relative(node, relative_type)
    {

    // Just eject if the passed in node is not.
    //
    if ( ! node)
        {return;}

    // Make sure a relative type was specified, or eject with an error.
    //
    if ( ! relative_type)
        {die("Code error: Element relative: No relative type specified");}

    // Make a variable for any sibling we find, and find the first potential
    // sibling adjacent to the passed in node.
    //
    var element_found;
    var potential_element = node[relative_type];

    // As long as we only have potential elements, keep looking.
    //
    while (potential_element && ! element_found)
        {

        // If the potential element is an element, eureka! Otherwise, go
        // forward to the next relative type from potential element node that
        // we currently have.
        //
        if (potential_element.nodeType == 1)
            {element_found = potential_element;}
        else
            {potential_element = potential_element[relative_type];}
        }

    // Return the element that we found, if any.
    //
    return element_found;
    }

// Returns just the children elements of the passed in node.
//
function children_elements(node)
    {

    // Just eject if the passed in node is not. Create a variable for the
    // children elements we find.
    //
    if ( ! node)
        {return;}
    var children_elements = new Array();

    // Add only the children of the passed in node that have a node type that
    // indicates the node is an element.
    //
    for (var index in node.childNodes)
        {
        if (node.childNodes[index] && node.childNodes[index].nodeType == 1)
            {children_elements.push(node.childNodes[index]);}}

    // Return any children elements we found.
    //
    return children_elements;
    }

// Pass in a radio button node, and have its preferences saved and as well
// that of all of its fellow radio buttons with the same name.
//
function save_radio_prefs(radio_node)
    {

    // Just eject if the passed in node is not.
    //
    if ( ! radio_node)
        {return;}

    // Get our other radio nodes by the name of the passed-in radio node,
    // which should, by radio node requirement, be the same for all the radio
    // nodes. If we failed to get any radio nodes (like the passed-in radio
    // node), just eject.
    //
    var radio_nodes = document.getElementsByName(radio_node.name);
    if ( ! radio_nodes)
        {return;}

    // Save the prefs for all the radio nodes, as the unselecting of a radio
    // button does not trigger a change in itself.
    //
    for (var index = 0; index < radio_nodes.length; index++)
        {save_pref(radio_nodes[index].id);}}

// Pass in a node identifier, which will have anything after a dash in it
// removed, and the node's preference's value will be saved as appropriate,
// based on the type. The ID must begin with a handled node type.
//
function save_pref(id)
    {

    // Strip off any default specification from the node ID, which is given
    // after a dash and is not part of the official ID.  Get the type from the
    // node ID, which is just the first lower case letters, by convention.
    // Also, get the node that has that ID, and create a preference name to
    // store the node's preference value under.
    //
    id = id.replace(/-.*/, "");
    var type = id.match("[a-z]+");
    var node = document.getElementById(id);
    var pref_name = "cal_" + id;

    // If we failed to find the node, eject with an error.
    //
    if ( ! node)
        {die("Code error: Save pref: No node found with ID: " + id);}

    // Define the way we read attribute settings for tags types that we
    // routinely deal with for storing their preferences.  The latter is the
    // Java code to read whether the tag node's attribute value, eg:
    // <node>.<read>. However, if we can't read the type that was passed in,
    // eject with an error.
    //
    var value;
    if (type == "arrow")
        {value = (node.src == open_arrow);}
    else if (type == "check")
        {value = node.checked;}
    else if (type == "display")
        {value = (node.style.display == "inline");}
    else if (type == "hidden")
        {value = node.value;}
    else if (type == "radio")
        {value = node.checked;}
    else if (type == "text")
        {value = node.value;}
    else if (type == "visibility")
        {value = (node.style.visibility == "visible");}
    else
        {die("Code error: Save pref: Unhandled node type: node ID: " + id);}

    // Save a cookie for the preference.
    //
    set_cookie(pref_name, value, cookie_max_days);
    }

// Pass in the name and value of a cookie to set, and optionally, the number
// of days the cookie should be kept before being thrown out. The cookie's
// escaped value will be saved, and 1 is returned. Otherwise, null is
// returned.
//
function set_cookie(name, value, days_to_expire)
    {

    // If we can't set a cookie, just return null. Otherwise define the
    // setting of the cookie. We have to do a full escape of spooky characters
    // of the value, and escape the escaped value, because Expression Engine
    // WILL MODIFY our cookies, without telling anyone! Expression Engine will
    // somehow, from all this, give the PHP _COOKIE value correctly--I'm sure
    // by mistake. :)
    //
    if ( ! navigator.cookieEnabled)
        {return null}
    setting = name + "=" + escape(encodeURIComponent(value));

    // If the cookie can expire, add information to the setting (for the
    // browser to deal with).
    //
    if (days_to_expire != null)
        {
        var current_date = new Date();
        current_date.setDate(current_date.getDate() + days_to_expire);
        setting += ";domain=.thepeoplescoalition.org;path=/";
        setting += ";expires=" + current_date.toGMTString();
        }

    // Store the cookie with the settings that we made.
    //
    document.cookie = setting;
    }

// Pass in the name of the requested cookie and you will get back its raw
// string value. If the cookie was not found, null is returned.
//
function get_cookie(name)
    {

    // If we can't find the right cookie--which is of the format
    // "<cookie_name>=<cookie_value>"--then eject.
    //
    if (document.cookie.length == -1) {return null}
    cookie_start = document.cookie.indexOf(name + "=");
    if (cookie_start == -1) {return null}

    // Find the character start and end index of the cookie value. We are
    // assuming the cookie is baked: any ";" character in it would be escaped.
    // (Storing raw cookies is no good.) Then go ahead and pull the baked
    // cookie out of the cupbord.
    //
    cookie_start = cookie_start + name.length + 1;
    cookie_end = document.cookie.indexOf(";", cookie_start);
    if (cookie_end == -1) {cookie_end = document.cookie.length}
    cookie = document.cookie.substring(cookie_start, cookie_end);

    // Return the raw cookie, not our strangley baked version (just in case
    // they want to bake it just the way they want it!, of course).
    //
    return decodeURIComponent(unescape(cookie));
    }

// Pass in the cookie name to eat and 1 is returned upon eating it, or null if
// we failed. The cookie is "eaten" by setting the expiration date to
// yesterday.
//
function eat_cookie(name)
    {

    // If we can't eat any cookies right now, just return null.
    //
    if ( ! navigator.cookieEnabled)
        {return null}

    // Set the cookie to expire a hundred days ago: the browser should
    // immediately handle the rest.
    //
    var current_date = new Date();
    current_date.setDate(current_date.getDate() - 100);
    document.cookie = name + "=;domain=.thepeoplescoalition.org;path=/"
        + ";expires=" + current_date.toGMTString();

    // Return an indication that we successfully ate the cookie.
    //
    return true;
    }

// This function redirects a key event of the enter key, calls the node's
// onchange event, and returns false so that the this key can be intercepted.
// Otherwise, true is returned.
//
function change_enter(node, key_event)
    {

    // Try to find, from the passed in event, and some wackie IE ways, which
    // key was pressed. By default, assume that we will intercept the key
    // event.
    //
    var key, pass_through = false;
    if (key_event && key_event.which != undefined)
        {key = key_event.which;}
    else if (key_event && key_event.keyCode != undefined)
        {key = key_event.keyCode;}
    else if (window.event)
        {key = window.event.keyCode;}

    // If the enter key was hit, kick off the onchange event for the node and
    // Otherwise, indicate to the caller that key event should pass through.
    //
    if (node && key == 13)
        {node.onchange();}
    else
        {pass_through = true;}

    // Indicate whether the caller whether to continue or not.
    //
    return pass_through;
    }

// This function does a synchronous AJAX call to check to see if the user is
// currently logged in or not, and returns the corresponding boolean value. If
// the user is not logged in, she or he is asked whether they want to log in
// now. If so, a new window is opened up to do this.
//
function logged_in()
    {

    // Assume the user is not logged in, and make a synchronous AJAX request
    // to the script that reveals whether the user is logged in or not. Define
    // the ask message for the user to log in.
    //
    var is_logged_in = false;
    var request = ajax_request("http://thepeoplescoalition.org/home/logged_in/");
    var ask = "You must be logged in to use that. Log in (or register) now?";

    // If the request did not go through for some reason, complain about it.
    //
    if ( ! (request && request.statusText == "OK" && request.responseText))
        {die("Code error: Logged in: Got a bad request object back");}

    // Identify whether the user is logged in or not. If the user is not
    // logged in, ask if he or she would like to do that now, and, if
    // requested, open a new log in window.
    //
    if (request.responseText.match(/special_log_in_identifier/))
        {is_logged_in = true;}
    else
        {
        if (confirm(ask) == true)
            {window.open("http://thepeoplescoalition.org/member/login/");}}

    // Return whether the user is logged in or not.
    //
    return is_logged_in;
    }

// This function just reports any given message to the user and then throws
// the message as an error. Any mouse cursor setting is set back to normal.
//
function die(message)
    {

    // Make sure any mouse cursor settings are set back to normal. If there is
    // a message, alert the user about it.
    //
    document.body.style.cursor = "default";
    if (message)
        {alert(message);}

    // Throw any message we have as an error.
    //
    throw(message);
    }

// Pass in the URL to open and, if this URL is to be opened asynchronously, a
// callback function. This callback will be called when the request is
// finished processing with the AJAX request object as the argument of the
// callback function. If a parameters hash is specified, those parameters will
// be posed to the URL. If something goes wrong, nothing is returned.
//
function ajax_request(url, callback, parameters)
    {

    // Make the request object we are to return and, by default, assume that
    // we are going to make an asynchronous call to the passed-in URL.
    //
    var request;
    var asynchronous = true;

    // Professional browsers support the standard method for getting the AJAX
    // request object. If we are not using such, try to go back in time some.
    //
    try
        {request = new XMLHttpRequest();}
    catch (error)
        {

        // And then there's Internet Explorer, and its older versions. If we
        // fail, just return nothing to indicate this.
        //
        try
            {request = new ActiveXObject("Msxml2.XMLHTTP");}
        catch (error)
            {
            try {request = new ActiveXObject("Microsoft.XMLHTTP");}
            catch (error) {return;}}
        }

    // Try to get the hostname from the URL we are getting the request from,
    // as well as our own hostname.
    //
    var url_hostname = get_hostname(url);
    var our_hostname = get_hostname("http://thepeoplescoalition.org/");

    // If the URL appears to be on a remote host, use our local URL proxy
    // server, to bypass security measures against remote host access.
    // Otherwiese, if just make sure security knows we are using the local
    // hostname by removing any ambiguities.
    //
    if (url_hostname && url_hostname != our_hostname)
        {url = url_proxy_server + "?" + url;}
    else if (url_hostname)
        {

        // Make sure that the requested URL host exactly matches the current
        // browser URL host, so we don't wrongly get flagged by security as
        // using a different host. Apparently, my logic is better than theirs.
        //
        var href_matches = location.href.match(hostname_from_url_id);
        if (href_matches && href_matches.length)
            {url = url.replace(hostname_from_url_id, href_matches[0]);}
        }

    // If we have a callback function, it means that we are doing an
    // asynchronous request. In that case, when the AJAX request is complete,
    // have the server send back the AJAX request object as an argument to the
    // user's passed in callback function, if it exists.
    //
    if (callback)
        {
        request.onreadystatechange =
            function()
                {
                if (request.readyState == 4 && typeof(callback) == "function")
                    {callback(request);}}
        }
    else
        {asynchronous = false;}

    // If we have any parameters, it means that we are posting to the URL.
    //
    var method_type = "GET";
    if (parameters)
        {method_type = "POST";}

    // Open the URL with the specifications that we have. By default, assume
    // we that we have no parameters to send to the URL.
    //
    request.open(method_type, url, asynchronous);
    var parameter_string = null;

    // If parameters are specified, set request method to post them and set
    // the headers to let the receiving end know what to expect. 
    //
    if (parameters)
        {

        // Set the request header to indicate what kind of data and how much
        // we are going to supply it.
        //
        request.setRequestHeader("Content-type",
            "application/x-www-form-urlencoded");
        request.setRequestHeader("Content-length", parameters.length);
        request.setRequestHeader("Connection", "close");

        // Assemble the parameter string from the passed in parameters hash.
        // Make sure each setting value is encoded.
        //
        var settings = new Array();
        for (index in parameters)
            {
            settings.push(index + "="
                + encodeURIComponent(parameters[index]));
            }
        parameter_string = settings.join("&");
        }

    // Send off our request with any parameter string we have. Indicate
    // whether the AJAX request was successful or not by returning the AJAX
    // request object.
    //
    request.send(parameter_string);
    return request;
    }

// Pass in a URL, and if any hostname can be determined from it, it is
// returned. For example, the hostname of
// "http://www.ThePeoplesCoalition.org/calendar/" would be
// "thepeoplescoalition.org". The returned string is always in all lowercase
// letters.
//
function get_hostname(url)
    {

    // Extract a lowercase hostname from the passed in URL, if possible.
    //
    var hostname;
    var components = url.match(hostname_from_url_id);
    if (components && components.length > 2)
        {hostname = components[2].toLowerCase();}

    // Return any hostname that we found.
    //
    return hostname;
    }

// Pass in an identifier of a node, and the node's value is set and its
// preferences saved.
//
function save_node_value(identifier, value)
    {

    // Get the node with the passed in identifier, and update its value and
    // save its preferences, if we can find the node.
    //
    var node = document.getElementById(identifier);
    if (node)
        {
        node.value = value;
        save_pref(node.id);
        }
    }

// Pass in an element identifier, and get its value, if one exists.
//
function get_element_value(identifier)
    {

    // Tru to find the node with the passed in identifier, and the nodes
    // value.
    //
    var value;
    var node = document.getElementById(identifier);
    if (node)
        {value = node.value;}

    // Return any value that we obtained, or not.
    //
    return value;
    }

// If we are using Internet Explorer and we are passed a node, blur the focus
// of it, as IE does not normally do this and it delays change recognition and
// looks bad.
//
function ie_blur(node)
    {
    /*@cc_on
        if (node && node.blur)
            {node.blur();}
    @*/
    }

// Pass in a node string, such as "window" or "document", an event string,
// such as "onload" or "onunload", and an associated function to execute on
// that window event, and it will be executed, with any arguments passed
// through. Any previous event is executed before the passed-in one: events
// can be stacked.
//
function add_event_function(node_string, event_string, new_function)
    {
    var old_function = eval(node_string + "." + event_string);
    eval(node_string + "." + event_string + " = function()\n"
        + "    {\n"
        + "    if (typeof(old_function) == \"function\")\n"
        + "        {old_function.apply(this, arguments);}\n"
        + "    if (typeof(new_function) == \"function\")\n"
        + "        {return new_function.apply(this, arguments);}\n"
        + "    }\n");
    }