/*!
 * Hydra JavaScript Library v0.0.1
 * http://www.kylecrystal.com/
 *
 * Copyright 2010 Kyle Crystal
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Date: Sunday March 14 12:05:21 2010 +0900
 */

(function ()
{
    // Variable used later for intervals and timeouts.
    var _intervals = new Array(),

    // Hydra library definition.
    Hydra = {
        /**
         * Searches the specified array for the specified value.
         * @param {Array} array The array to search.
         * @param {Object} value The value to search the array for.
         * @return {Integer} -1 if the value is not found, and the array index
         * value if the value is found.
         */
        arrSearch:function(array, value)
        {
            // Set the index to default of -1 (nothing found).
            var index = -1;
            // Loop through the array.
            for (key in array)
            {
                // Check if the search value is the current array loop value.
                if (array[key] == value)
                {
                    // Set the index to the found index value and stop looping.
                    index = key;
                    break;
                }
            }

            // Return the index value.
            return index;
        },

        /**
         * Adds a specified CSS class to a specified HTML element.
         * @param {String} elementId The ID value of the element to add the
         * class to.
         * @param {String} classString The name of the class to add to the
         * element.
         */
        addClass:function(elementId, classString)
        {
            // Declare the required variables.
            var ele = document.getElementById(elementId);
            var eleClass = ele.className;
            var eleClasses = eleClass.split(" ");
            var foundIndex;
            var newClasses = classString.split(" ");
            var newClass;

            // Loop through the classes that are tbe added.
            for (var i = 0; i < newClasses.length; ++i)
            {
                // Check if the HTML element has any classes.
                if (eleClass == "")
                {
                    // If no classes are found, add the classes and break the
                    // loop.
                    eleClasses[0] = classString;
                    break;
                }

                // Check to see if the element already has the current class.
                newClass = newClasses[i];
                foundIndex = Hydra.arrSearch(eleClasses, newClass);

                // If the current class is not found, add it to the element.
                if (foundIndex == -1)
                    eleClasses.push(newClass);
            }

            // Rejoin the split array of class names and set it to the element.
            eleClasses = eleClasses.join(" ");
            ele.className = eleClasses;
        },

        /**
         * Removes a specified class from a specified HTML element.
         * @param {String} elementId The ID value of the element to remove the
         * class from.
         * @param {String} classString The name of the class to remove from the
         * element.
         */
        removeClass:function(elementId, classString)
        {
            // Declare the required variables.
            var ele = document.getElementById(elementId);
            var eleClass = ele.className;
            var eleClasses = eleClass.split(" ");
            var foundIndex;
            var removeClasses = classString.split(" ");
            var removeClass;

            // Loop through the classes that are to be removed.
            for (var i = 0; i < removeClasses.length; ++i)
            {
                // Check to see if the element has the current class.
                removeClass = removeClasses[i];
                foundIndex = Hydra.arrSearch(eleClasses, removeClass);

                // If the class is found, remove it from the element.
                if (foundIndex != -1)
                    eleClasses.splice(foundIndex, 1);
            }

            // Rejoin the split array of class names and set it to the element.
            eleClasses = eleClasses.join(" ");
            ele.className = eleClasses;
        },

        /**
         * Hides and shows a given HTML element. NOTE: This function requires a
         * CSS class called "hidden" to be declared in the stylesheet that hides
         * an element.
         * @param {String} elementId The ID of the HTML element to show/hide.
         * @param {Boolean} state Optional boolean for the visibility state
         * where true is visible and false is invisible.
         */
        changeVisibility:function(elementId, state)
        {
            // Declare the required variables.
            var hiddenClass = "hidden";
            var ele = document.getElementById(elementId);
            var eleClasses = ele.className.split(" ");
            // If state is not set, default to the array search method (switch
            // visibility on and off).
            state = state || Hydra.arrSearch(eleClasses, hiddenClass);

            // Check if the element has the "hidden" class. If so, remove it, if
            // not, add it.
            if (state == true || state != -1)
                Hydra.removeClass(elementId, hiddenClass);
            else
                Hydra.addClass(elementId, hiddenClass);
        },

        /**
         * Fades a selected element in or out depending on the parameters.
         * @param {String} elementId The ID of the HTML element to fade.
         * @param {Integer} endOpacity A number between 0 and 100 that
         * represents the ending visibility state of the element.
         * @param {Integer} speed The amount of time (in ms) that each fade step
         * takes.
         * @param {Integer} steps The number of steps the fade takes to
         * complete.
         */
        fade:function(elementId, endOpacity, speed, steps)
        {
            // If the element is already fading in or out, stop it so a new fade
            // can begin.
            if (_intervals[elementId + "_fade"])
                clearInterval(_intervals[elementId + "_fade"]);

            // Specify the required variables.
            var currentOpacity = Hydra.getStyle(elementId, 'opacity');
            var increment;
            var length = speed * steps;

            // Get the current opacity of the element. If none exists, set it to
            // 100 (fully visible).
            if (currentOpacity == "" || currentOpacity == null)
                currentOpacity = 100;
            else
                currentOpacity *= 100;

            // If the current opacity is somehow below 0 (due to previous fading
            // having integer increments when doubles are possible) set it to 0.
            if (currentOpacity < 0)
                currentOpacity = 0;

            // Determine the change in visibility for each step based on the
            // given parameters.
            increment = Math.abs(parseInt(currentOpacity) - parseInt(endOpacity)) / steps;
            // Begin the fade.
            _intervals[elementId + "_fade"] = setInterval("Hydra.changeOpacity('" + elementId + "','" + increment + "', '" + endOpacity + "')", length);
        },

        /**
         * The function that actually changes te opacity of an element (used for
         * fading).
         * @param {String} elementId The ID of the HTML element that will have
         * its opacity changed.
         * @param {Integer} increment The amount that the opacity of the element
         * will change.
         * @param {Integer} endOpacity The final opacity of the element.
         */
        changeOpacity:function(elementId, increment, endOpacity)
        {
            // Declare the required variables.
            var ele = document.getElementById(elementId);
            var currentOpacity = Hydra.getStyle(elementId, 'opacity');
            var newOpacity;

            // Get the current opacity of the element. If none exists, set it to
            // 100 (fully visible).
            if (currentOpacity == "" || currentOpacity == null)
                currentOpacity = 100;
            else
                currentOpacity *= 100;

            // If the current opacity is somehow below 0 (due to previous fading
            // having integer increments when doubles are possible) set it to 0.
            if (currentOpacity < 0)
                currentOpacity = 0;

            // Determine if the element should fade in or out based on the
            // current opacity and the end opacity.
            if (endOpacity < currentOpacity)
            {
                // For fading out, remove the incremental value from the current
                // opacity.
                newOpacity = parseInt(currentOpacity) - parseInt(increment);

                // If the ending opacity is greater then or equal to the new
                // opacity, end the fade.
                if (endOpacity >= newOpacity)
                    clearInterval(_intervals[elementId + "_fade"]);
            }
            else
            {
                // For fading in, add the incremental value to the current
                // opacity.
                newOpacity = parseInt(currentOpacity) + parseInt(increment);

                // If the ending opacity is less then or equal to the new
                // opacity, end the fade.
                if (endOpacity <= newOpacity)
                    clearInterval(_intervals[elementId + "_fade"]);
            }

            // Set the new opacity value to the element (filter style for IE,
            // opacity for everything else).
            ele.style.opacity = newOpacity / 100;
            ele.style.filter = "alpha(opacity=" + newOpacity + ")";
        },

        /**
         * Sets up a new AJAX object with the specified parameters and returns
         * -1 if the browser doesn't support AJAX, and returns the AJAX object
         * if the browser does support it. Using the returned object the user
         * can use the readyState method to preform actions using the
         * response as follows:
         * 0 - Request not initialized.
         * 1 - Request set up.
         * 2 - Request sent.
         * 3 - Request in process.
         * 4 - Request complete.
         * @param {String} method GET or POST method for sending data.
         * @param {String} url The URL in which to invoke.
         * @param {Array} arguements An array of arguements to be sent (if POST
         * method is used.
         * @return {Object} -1 if AJAX can not be used, otherwise, returns the
         * AJAX object.
         */
        ajaxSetup:function(method, url, arguements)
        {
            // Variable to hold the AJAX object.
            var xmlhttp;

            // Code for IE7+, Firefox, Chrome, Opera, Safari.
            if (window.XMLHttpRequest)
                xmlhttp = new XMLHttpRequest();
            // Code for IE5 and 6.
            else if (window.ActiveXObject)
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            else
                // Return -1 if AJAX can not be used, this allows the user to
                // decide how to handle the problem.
                return -1;

            // Start setting up the AJAX request, using asyncronous settings.
            xmlhttp.open(method, url, 'true');
            // If arguements are used, then begin the syntax to send them.
            if (arguements != null)
            {
                // Set defaults for the arguements length and string.
                var arguementsLength = "";
                var arguementsString = "";
                // Loop through the arguements.
                for (key in arguements)
                {
                    // Get the total length of arguements.
                    arguementsLength += arguements[key].length;
                    // Also, determine the total string.
                    if (arguementsString != "")
                        arguementsString += "&";
                    arguementsString += key + "=" + arguements[key];
                }
                // Complete the AJAX settings and send the request with
                // arguements.
                xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xmlhttp.setRequestHeader("Content-length", arguementsLength);
                xmlhttp.setRequestHeader("Connection", "close");
                xmlhttp.send(arguementsString);
            }
            else
                // Send the AJAX request without arguements.
                xmlhttp.send(null);

            // Return the AJAX object for the user to handle.
            return xmlhttp;
        },

        /**
         * Grows or shrinks a specified element to specified parameters. This
         * gives the illusion that the element is sliding up or down to reveal
         * itself.
         * @param {String} elementId The ID of the element to slide.
         * @param {Integer} newHeight The height that the element will expand
         * or contract to.
         * @param {Integer} fade This value determines the speed in which the
         * element will expand or contract, but slow it's movement as it reaches
         * it's final height.
         * @param {Integer} speed This value determines how fast the elements
         * expands or contracts.
         * @param {Function} onFinish The function to execute when the fade is
         * completed.
         */
        slide:function(elementId, newHeight, fade, speed)
        {
            // Check if a slide is in progress for the given element, and if
            // there is, cancel it.
            if (_intervals[elementId + "_slide"])
                clearTimeout(_intervals[elementId + "_slide"]);
            // Set the required variables.
            var ele = document.getElementById(elementId);
            var height;
            var oldHeight = ele.offsetHeight;
            // Remove the extras from the oldHeight (padding and border, top and
            // bottom, but only remove them for FireFox, Chrome, etc.).
            if (Hydra.detectBrowser() != "Internet Explorer")
            {
                var extraHeight = 0;
                var extraStyles = new Array("padding-top", "padding-bottom", "border-top-width", "border-bottom-width");
                for (styleProp in extraStyles)
                {
                    var tempProp = Hydra.getStyle(elementId, extraStyles[styleProp], true);
                    extraHeight += parseInt(tempProp);
                }
                oldHeight -= extraHeight;
            }
            // Set the interval to the ceiling of the absolute value of the
            // difference of the new height and previous height.
            var interval = Math.ceil(Math.abs(newHeight - oldHeight) / fade);

            // Check if the div needs to grow, or shrink.
            if (oldHeight > newHeight)
            {
                // If it shrinks, then find the height and set it to the div.
                height = parseInt(oldHeight) - parseInt(interval);
                ele.style.height = height + 'px';

                // Check if the process should repeat.
                if (height > newHeight)
                    _intervals[elementId + "_slide"] = setTimeout(function() {Hydra.slide(elementId, newHeight, fade, speed);}, speed);
            }
            else
            {
                // If it grows, then find the height and set it to the div.
                height = parseInt(oldHeight) + parseInt(interval);
                ele.style.height = height + 'px';

                // Check if the process should repeat.
                if (height < newHeight)
                    _intervals[elementId + "_slide"] = setTimeout(function() {Hydra.slide(elementId, newHeight, fade, speed);}, speed);
            }
        },

        /**
         * Gets the specified style from the specified element. Gets styles that
         * are in external stylesheets, automatically generated, or from inline
         * styles.
         * @param {String} elementId The ID of the element to get the style
         * from.
         * @param {String} style The style to get from the element.
         * @param {Boolean} makeNumerical Optional value if the user wants the
         * letters 'px' removed from the value (not avaliable on all styles).
         * @return {String} The value of the style that was retrieved.
         */
        getStyle:function(elementId, style, makeNumerical)
        {
            // Set makeNumerical to default to false.
            makeNumerical = makeNumerical || false;
            // Get the element object from the ID given.
            var ele = document.getElementById(elementId);
            // Variable used later incase makeNumerical is set to true.
            var returnStyle;
            // IE element properties.
            if (ele.currentStyle)
            {
                // Check if style is has a hyphen.
                if (style.search("-") != -1)
                {
                    // Split the style by hyphens to seperate property values.
                    style = style.split("-");
                    // Loop through the values.
                    for (word in style)
                    {
                        // Split the word into letters.
                        var splitWord = style[word].split("");
                        // If the word isn't the first word, upper case the
                        // first letter of the word.
                        if (word != 0)
                        {
                            for (letter in splitWord)
                                if (letter == 0)
                                {
                                    var newLetter = splitWord[letter].toUpperCase();
                                    splitWord[letter] = newLetter;
                                }
                        }
                        // Re-join the word together, with the new uppercase
                        // letter.
                        style[word] = splitWord.join("");
                    }
                    // Re-join the style together to that it can be read by IE
                    // (ex. "border-top-width" becomes "borderTopWidth").
                    style = style.join("");
                }

                // Set the return style to the style value.
                returnStyle = ele.currentStyle[style];
            }
            // FireFox, Chrome, etc.
            else if (document.defaultView && document.defaultView.getComputedStyle)
                returnStyle = document.defaultView.getComputedStyle(ele, null)[style];
            // Inline style property.
            else
                returnStyle = ele.style[style];

            // Check if makeNumerical is true, if so remove the "px" from
            // the style.
            if (makeNumerical)
            {
                // Make sure the style is defined, and search for the letters
                // 'px' in the string, if it's found, remove it.
                if (returnStyle && returnStyle.search("px") != -1)
                {
                    returnStyle = returnStyle.split("px");
                    returnStyle = returnStyle[0];
                }

                // If the user wants a number, but the style isn't a number,
                // than set the return to 0.
                if (isNaN(returnStyle))
                    returnStyle = 0;
            }

            // Return the style.
            return returnStyle;
        },

        /**
         * Detects the users browser and returns data based on the discovered
         * information.
         * @return {String} The detected browser, or -1 if browser is not found.
         */
        detectBrowser:function()
        {
            // Use the userAgent to get data.
            var userAgent = navigator.userAgent;
            // Create and set an array with browser information.
            var browsers = new Array();
            browsers["MSIE"] = "Internet Explorer";
            browsers["Chrome"] = "Chrome";
            browsers["Firefox"] = "Firefox";
            browsers["iPhone"] = "iPhone";

            // Loop through browers and try and find a match.
            for (identity in browsers)
            {
                // If the browser is found, return the data.
                if (userAgent.search(identity) != -1)
                    return browsers[identity];
            }

            // No match was found, return -1.
            return -1;
        },

        /**
         * Creates a tooltip that will show when the mouse hovers over a given
         * element.
         * @param {String} elementId The ID of the HTML element the tooltip will
         * show when the mouse is over.
         * @param {String} text The text that will appear in the tooltip (HTML
         * code is allowed as well).
         * @param {String} position The position of the tooltip relative to the
         * given element. Accepted values are:
         * "mouse" - Tooltip will appear where the mouse entered the element.
         * "mouse-follow" - Tooltip will follow the mouse in the element.
         * "top-left" - To the left and above the element.
         * "top" - Directly above (and centered horizontally) the element.
         * "top-right" - To the right and above the element.
         * "left" - Directly to the left (and centered vertically) of the
         * element.
         * "right" - Directly to the right (and centered vertically) of the
         * element.
         * "bottom-left" - To the left and below the element.
         * "bottom" - Directly below (and centered horizontally) the element.
         * "bottom-right" - To the right and below the element.
         * @param {String} className A custom CSS class to give the tooltip.
         * @param {String} animations An optional set of animations to use when
         * showing and hiding the tooltip. Animations include:
         * "slide" - Uses the slide animation.
         * "fade" - Uses the fade animation.
         * Note that more than one animation can be used, sepeated by a space,
         * therefore no spaces should be used when setting parameters.
         * Also, animation parameters must follow the animation call (minus the
         * elementId) in brackets. See animation documentation for specifics.
         * Also, animations can be delayed by a specified time, so that
         * one can 'fire' after a certain amount of time has passed, the time
         * in miliseconds should follow the call in parenthesies, this is
         * optional.
         * Also, all animations for hiding must be declared, then the
         * animations for showing must be declared, seperated by a colon.
         * Lastly, tooltip will always revert to hidden after animations, and
         * visible again on mouse over.
         * For example: "slide[0,50,20](0) fade[0,20,20](100):slide[100,50,20]
         * (0) fade[100,20,20](100)"
         */
        createTooltip:function(elementId, text, position, className, animations)
        {
            // Set the animation default to simple show/hide.
            animations = animations || "changeVisibility";
            // Get the element that the tooltip will be attached to.
            var ele = document.getElementById(elementId);
            // Create the tooltip.
            var tooltip = document.createElement('div');
            // Variable used later for tooltip show and hide functions.
            var _tooltipDelay = new Array();

            // Set the tooltip attributes and innerHTML accordingly (note the
            // z-index value to make sure it appears in front, and float so that
            // the width is properly set).
            tooltip.setAttribute('id', elementId + '_tooltip');
            // Chrome, Firefox, etc.
            tooltip.style.cssFloat = 'left';
            // IE float style.
            tooltip.style.styleFloat = 'left';
            tooltip.style.zIndex = '100';
            tooltip.innerHTML = text;

            // Append the tooltip to the end of the body element.
            document.body.appendChild(tooltip);
            // Make sure the tooltip is hidden, and add the given class.
            Hydra.addClass(tooltip.id, "hidden " + className);

            // Create the function to hide the tooltip based on animation set.
            function tooltipHide()
            {
                // Make sure some animations are set.
                if (animations != "changeVisibility")
                {
                    // Check if animation is set to display, and if so,
                    // cancel it.
                    if (_tooltipDelay[tooltip.id])
                        clearTimeout(_tooltipDelay[tooltip.id]);

                    // Get the animation details.
                    var animationsHide = animations.split(":");
                    animationsHide = animationsHide[0];
                    animationsHide = animationsHide.split(" ");
                    var evalString = "";

                    // Loop through the animations.
                    for (animation in animationsHide)
                    {
                        // Get the animation name.
                        var animationName = animationsHide[animation].split("[");
                        animationName = animationName[0];
                        // Get the animation parameters.
                        var animationParams = animationsHide[animation].match(/\[([^\[\]]*)]/);
                        animationParams = animationParams[1].split(",");
                        // Get the animation delay time.
                        var animationDelay = animationsHide[animation].match(/\(([^\(\)]*)\)/);
                        if (animationDelay == null)
                            animationDelay = 0;
                        else
                            animationDelay = animationDelay[1];

                        // Create the evaluation string based on given data.
                        evalString += "Hydra." + animationName + "('" + tooltip.id + "'";
                        for (animationParam in animationParams)
                            evalString += ", " + animationParams[animationParam];
                        evalString += ");";
                    }

                    // Execute the animation.
                    _tooltipDelay[tooltip.id] = setTimeout(function() {eval(evalString);}, animationDelay);
                }
                else
                    // If not then just hide the tooltip.
                    Hydra.changeVisibility(tooltip.getAttribute('id'), false);
            }

            function tooltipShow()
            {
                if (animations != "changeVisibility")
                {
                    // Check if animation is set to display, and if so,
                    // cancel it.
                    if (_tooltipDelay[tooltip.id])
                        clearTimeout(_tooltipDelay[tooltip.id]);

                    // Make sure the tooltip is visible.
                    Hydra.changeVisibility(tooltip.getAttribute('id'), true);

                    // Get the animation details.
                    var animationsShow = animations.split(":");
                    animationsShow = animationsShow[1];
                    animationsShow = animationsShow.split(" ");
                    var evalString = "";

                    // Loop through the animations.
                    for (animation in animationsShow)
                    {
                        // Get the animation name.
                        var animationName = animationsShow[animation].split("[");
                        animationName = animationName[0];
                        // Get the animation parameters.
                        var animationParams = animationsShow[animation].match(/\[([^\[\]]*)]/);
                        animationParams = animationParams[1].split(",");
                        // Get the animation delay time.
                        var animationDelay = animationsShow[animation].match(/\(([^\(\)]*)\)/);
                        if (animationDelay == null)
                            animationDelay = 0;
                        else
                            animationDelay = animationDelay[1];

                        // Create the evaluation string based on given data.
                        evalString += "Hydra." + animationName + "('" + tooltip.id + "'";
                        for (animationParam in animationParams)
                            evalString += ", " + animationParams[animationParam];
                        evalString += ");";
                    }

                    // Execute the animation.
                    _tooltipDelay[tooltip.id] = setTimeout(function() {eval(evalString);}, animationDelay);
                }
                else
                    // If not then just show the tooltip.
                    Hydra.changeVisibility(tooltip.getAttribute('id'), true);
            }

            // Hide the tooltip according to parameters given.
            tooltipHide();

            // Attach the mouse over event to the element to show the
            // tooltip.
            Hydra.addEvent(elementId, 'mouseover', function(e) {
                // Before the tooltip can be shown, make sure that the mouse
                // is entering the element from outside (and not the tooltip
                // itself or a nested element).
                if (!Hydra.isNestedElement(e.relatedTarget, ele) && e.relatedTarget != tooltip)
                {
                    // Show the tooltip.
                    tooltipShow();

                    // Set the position of the tooltip accordingly. The
                    // reason this is done here and not in the creation of
                    // the tooltip is for the mouse positions, and incase
                    // the element is movable (drag and drop?).
                    if (position == "mouse")
                    {
                        // Set the position to where the mouse entered the
                        // element.
                        var coords = Hydra.getMouseCoords();
                        tooltip.style.position = "absolute";
                        tooltip.style.top = coords['y'] + "px";
                        tooltip.style.left = coords['x'] + "px";
                    }
                    else if (position == "mouse-follow")
                    {
                        // While the mouse is in the element, have the
                        // tooltip follow the mouse.
                        Hydra.addEvent(elementId, 'mousemove', function() {
                            var coords = Hydra.getMouseCoords();
                            tooltip.style.position = "absolute";
                            tooltip.style.top = (coords['y'] + 15) + "px";
                            tooltip.style.left = (coords['x'] + 15) + "px";
                        });
                    }
                    else
                    {
                        // Set the tooltip to the outer borders of the
                        // element.

                        // Split tye position by the hyphen to get each
                        // position value separate.
                        var posSplit = position.split("-");

                        // Set the default position values.
                        var pos = new Array();
                        pos['top'] = ele.offsetTop - tooltip.offsetHeight;
                        pos['bottom'] = ele.offsetTop + ele.offsetHeight;
                        pos['left'] = ele.offsetLeft - tooltip.offsetWidth;
                        pos['right'] = ele.offsetLeft + ele.offsetWidth;

                        // Set the position style to absolute.
                        tooltip.style.position = "absolute";
                        // Set the default position to the center of the
                        // element.
                        tooltip.style.left =
                            ele.offsetLeft +
                            (ele.offsetWidth / 2) -
                            (tooltip.offsetWidth / 2);
                        tooltip.style.top =
                            ele.offsetTop +
                            (ele.offsetHeight / 2) -
                            (tooltip.offsetHeight / 2);

                        // Loop through the positions.
                        for (posKey in posSplit)
                        {
                            // If the current position is top or bottom,
                            // set the top style to the current position.
                            if (posSplit[posKey] == "top" || posSplit[posKey] == "bottom")
                                tooltip.style.top = pos[posSplit[posKey]] + "px";
                            // If the current position is left or right,
                            // set the left style to the current position.
                            else
                                tooltip.style.left = pos[posSplit[posKey]] + "px";
                        }
                    }
                }
            });

            // Attach the mouse out event to the element to hide the
            // tooltip.
            Hydra.addEvent(elementId, 'mouseout', function(e) {
                // Before the tooltip can be hidden, make sure that the
                // mouse is going outside the element (and not to the
                // tooltip itself or a nested element).
                if (!Hydra.isNestedElement(e.relateTarget, ele) && e.relatedTarget != tooltip)
                    tooltipHide();
            });

            // Attach the mouse out event to the tooltip to hide itself.
            Hydra.addEvent(tooltip.id, 'mouseout', function (e) {
                // Before the tooltip can be hidden, make sure that the
                // mouse is going outside the element (and not back into the
                // element or a nested element).
                if (!Hydra.isNestedElement(e.relatedTarget, ele) && e.relatedTarget != ele)
                    tooltipHide();
            });
        },

        /**
         * Gets the current mouse X and Y positions.
         * @return {Array} Returns the X and Y positions of the mouse in array
         * form (['x'] for X position and ['y'] for Y position).
         */
        getMouseCoords:function(e)
        {
            // Create the return array.
            var coords = new Array();
            // IE code.
            if (event)
            {
                // Get the X and Y positions for IE.
                coords['x'] = event.clientX;
                coords['y'] = event.clientY;
            }
            // Firefox, Chrome, etc.
            else
            {
                // Get the X and Y positions for other browsers.
                coords['x'] = e.pageX;
                coords['y'] = e.pageY;
            }

            // Return the positions.
            return coords;
        },

        /**
         * Checks to see if a specified element is a child of the specified
         * parent element. Also checks to see if the element is the parent
         * element.
         * @param {Object} element The DOM object element to check if it is
         * nested.
         * @param {Object} parentElement The DOM object parent to check if the
         * element is a child of.
         * @return {Boolean} True is the element is a child of the parent, or
         * if the element is the parent, false otherwise.
         */
        isNestedElement:function(element, parentElement)
        {
            // Loop through all the parent element of the specified element.
            while(element != null)
            {
                // Check for a match.
                if (element == parentElement)
                    return true;

                // Set the element to its parent.
                element = element.parentNode;
            }

            // No match was found, the element is not nested in the parent.
            return false;
        },

        /**
         * Adds an event to a specified element and preforms the specified
         * function.
         * @param {String} elementId The ID of the HTML element that the event
         * will be applied to.
         * @param {String} eventName The name of the event to apply to the
         * specified element. Accepted values include:
         * click - The mouse click event.
         * mouseover - When the mouse moves over the element.
         * mouseout - When the mouse moves out of the element.
         * mousemove - When the mouse moves.
         * @param {Function} eventFunction The function that the event will
         * carry out when fired.
         */
        addEvent:function(elementId, eventName, eventFunction)
        {
            // Get the element from the ID.
            var ele = document.getElementById(elementId);
            // List of possible events.
            var eventList = new Array();
            eventList['click'] = "click|onclick";
            eventList['mouseover'] = "mouseover|onmouseover";
            eventList['mouseout'] = "mouseout|onmouseout";
            eventList['mousemove'] = "mousemove|onmousemove";
            // The split of the chosen event (0 for Chrome, Firefox, etc. and 1
            // for IE.
            var currentEvent = currentEvent = eventList[eventName].split('|');

            // Code for IE.
            if (ele.attachEvent)
                ele.attachEvent(currentEvent[1], eventFunction);
            // Code for Chrome, Firefox, etc.
            else
                ele.addEventListener(currentEvent[0], eventFunction, false);
        }
    };

    // Set the Hydra alias to be the same as window.Hydra.
    window.Hydra = Hydra;
})();
