/**
 * Shortcut to document.getElementById().
 * 
 * @param {String} id The HTML id to search for.
 * @return {Object} A DOM Element.
 */
function $(id) {

    return document.getElementById(id);

}


/**
 * Gets an element by class name.
 * 
 * @param {String} class_name A class name to search for.
 * @param {String|DOMElement} (optional) Searches from this element downward.
 * @return {Array} An array of DOM Elements.
 */
function $$(class_name, parent_element) {
    
    var elements = new Array;
    
    if (typeof parent_element == 'string') {
        
        parent_element = $(parent_element);
        
    }
    
    var all = (parent_element || document.body).getElementsByTagName('*');
    
    for (var i = 0; i < all.length; i++) {
        
        if (all[i].className.match("(^|\\s)" + class_name + "(\\s|$)")) {
            
            elements.push(all[i]);
            
        }
    }
    
    return elements;
    
};


/**
 * Binds an object to a function; "this" will point to the object passed.
 * 
 * @param {Object} An object instance to apply.
 * @param {Mixed} (optional) Any number of parameters to pass as arguments to
 *        the function.
 * @return {Function} A new reference to the function with the object applied.
 */
Function.prototype.bind = function(obj) {

    var method = this, args = Array.prototype.slice.call(arguments, 1);
    
    return function(event) {
        
        if (event || window.event) {
            
            return method.call(obj, event || window.event, args[0], args[1]);
            
        }
        
        return method.apply(obj, args);
        
    }
};


/**
 * Stores events in an array. This is mainly for IE, so that event listeners
 * can be removed on page unload.
 * 
 * 
 */
EventCache = {
    
    /**
     * An array of event handler particulars.
     * 
     * @type Array
     */
    cache: [],


    /**
     * Adds an event to the event cache, and registers the event handler.
     * 
     * @param element {Object | String} A DOM element or the HTML id of a DOM
     *        element.
     * @param name {String} The name of the event, sans "on".
     * @param method {Function} The event handler callback function.
     * @param use_capture {Boolean} Sets the useCapture property when
     *        registering the handler.
     */
    add: function(element, name, method, use_capture) {
        
        var el = $(element) || element;
        
        use_capture = !!use_capture;
        
        EventCache.cache.push({
            element: el,
            name: name,
            method: method,
            use_capture: use_capture
        });
    
        if (el.addEventListener) {
        
            el.addEventListener(name, method, use_capture);
        
        } else if (element.attachEvent) {
            
            el.attachEvent('on' + name, method, use_capture);
        
        }
    },
    

    /**
     * Removes an element from the event cache.
     * 
     * @param element {Object | String} A DOM element or the HTML id of a DOM
     *        element.
     * @param name {String} The name of the event, sans "on".
     * @param method {Function} The event handler callback function.
     * @param use_capture {Boolean} Sets the useCapture property when
     *        registering the handler.
     * 
     */
    remove: function(element, name, method, use_capture) {
        
        if (element.removeEventListener) {
        
            element.removeEventListener(name, method, use_capture);
        
        } else if (element.attachEvent) {
            
            element.detachEvent('on' + name, method, use_capture);
        
        }
    },
    

    /**
     * Detaches all events in the cache array.
     * 
     */
    unload: function() {
        
        for (var i = 0; i < EventCache.cache.length; i++) {
            
            var c = EventCache.cache[i];
            
            EventCache.remove(c.name, c.method, c.use_capture);
            
        }

        EventCache.cache = [];
        
    }
};


// For IE < 7.

EventCache.add(window, 'unload', EventCache.unload);


/**
 * Trims whitespace from either side of a string.
 * 
 * @return {String} The trimmed string.
 */
String.prototype.trim = function() {
    
    return this.replace(/^\s+|\s+$/g, '');
    
};


/**
 * Ajax implementation.
 * 
 * 
 */
Ajax = {
    
    /**
     * Properties.
     * 
     * 
     */
    callback: {
        
        /**
         * Set a default Ajax timeout.
         * 
         */
        timeout: 7000,
        
        
        /**
         * A function callback to run after the default failure function.
         * 
         */
        failureCleanup: null,
        
        
        /**
         * Default function to call on failure.
         * 
         */
        failure: function(request) {
            
            if (request.status == -1) {

                var msg = 'Communication with server failed. Please check ' +
                          'your network connection.';
                
            } else {
                
                var msg = 'An error has occurred. Please try again.';

            }
            
            alert(msg);
            
            if (typeof Ajax.callback.failureCleanup == 'function') {
            
                Ajax.callback.failureCleanup(request);
            
            }
        }
    },
    
    
    /**
     * Makes an Ajax async request.
     * 
     * @param {String} method The request method: GET or POST.
     * @param {String} uri The URI of the Ajax script on the server.
     * @param {Object} callback Various properties and callback methods.
     * @param {String} post_params (optional) Parameters for a POST call.
     * 
     */
    request: function(method, uri, callback, post_params) {
        
        if (typeof callback.timeout != 'undefined') {
            
            Ajax.callback.timeout = callback.timeout;
            
        }
        
        if (typeof callback.failure != 'undefined') {
            
            Ajax.callback.failure = callback.failure;
            
        }
        
        Ajax.callback.failureCleanup = callback.failureCleanup;
        
        Ajax.callback.success = callback.success;
        
        Ajax.callback.scope = callback.scope;
        
        Ajax.callback.argument = callback.argument;
        
        post_params = (typeof post_params == 'undefined') ? null : post_params;
        
        return YAHOO.util.Connect.asyncRequest(
            method, uri, Ajax.callback, post_params
        );
    },
    

    /**
     * Returns the object used to make Ajax calls.
     * 
     * @return {Object} Yahoo.util.Connect
     * 
     */
    getAjaxObject: function() {
        
        return YAHOO.util.Connect;
        
    }
};


/**
 * Adds an onclick with confirm() functionality to all links of a certain
 * class name.
 * 
 * @param {String} class_name The class of links to add.
 * @param {Object} parent_element (optional) The parent element of the links.
 * @param {String} msg The message to show in the confirm() dialog.
 * 
 */
function ConfirmLinks(class_name, parent_element, msg) {
    
    this.links = $$(class_name, parent_element);
    
    this.msg = msg;
    
    this.configure();
    
    EventCache.add(window, 'unload', this.onunload.bind(this));
    
    this.conditions = new Array;
    
}

ConfirmLinks.prototype = {
    
    /**
     * Loops through links and adds the onclick.
     * 
     * 
     */
    configure: function() {
        
        if (!this.links.length) {
            
            return;
            
        }
        
        for (var i = 0; i < this.links.length; i++) {
            
            this.links[i].onclick = this.linkOnclick.bind(this, this.links[i]);
            
        }
    },
    

    /**
     * Onclick handler that contains the confirm() dialog.
     * 
     * @param {Object} evt The event object passed from the link.
     * 
     */
    linkOnclick: function(evt, link) {
        
        // Run through conditions functions, if any.
        
        if (this.conditions.length) {
            
            for (i = 0; i < this.conditions.length; i++) {
                
                if (true === this.conditions[i](this, link)) {
                    
                    return true;
                    
                }
            }
        }
        
        if (!confirm(this.msg)) {
            
            return false;
            
        }
    },
    
    
    /**
     * Adds a condition function to the conditions array. If the condition
     * returns true, the confirmation dialog will not be shown.
     * 
     * 
     */
    addCondition: function(func) {
        
        this.conditions.push(func);
        
    },

    
    /** 
     * For IE to prevent leaks, since we can't use EventCache here.
     * 
     * 
     */
    onunload: function() {
        
        for (var i = 0; i < this.links.length; i++) {
            
            this.links[i].onclick = null;
            
        }
    }
};

String.prototype.htmlEntityEncode = function() {
    
    var str = this.replace(/&/g, '&amp;');

    str = str.replace(/</g, '&lt;');

    str = str.replace(/>/g, '&gt;');

    return str;
    
};

String.prototype.htmlEntityDecode = function() {
    
    var str = this.replace(/&amp;/g, '&');

    str = str.replace(/&lt;/g, '<');

    str = str.replace(/&gt;/g, '>');

    return str;
    
};

/**
 * For cookie manipulation.
 * 
 * 
 */
Cookie = {
    
    create: function(name, value, days) {
        var expires = '';
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 *60 * 60 *1000));
            var expires = '; expires=' + date.toGMTString();
        }
        document.cookie = name + "=" + value + expires + '; path=/';
    },

    read: function(name) {
        var nameEQ = name + '=';
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) {
            var c = ca[i];
            while (c.charAt(0)==' ') {
                c = c.substring(1,c.length);
            }
            if (c.indexOf(nameEQ) == 0) {
                return c.substring(nameEQ.length,c.length);
            }
        }
        return null;
    },

    erase: function(name) {
        Cookie.create(name, '', -1);
    }
};
