///////////////////////////////////////////////////////////////////////////////
//
//  HtmlUtils

/**
 *  Utility class containing static helpers for manipulating HTML elements.
 */
var HtmlUtils = new Object();

HtmlUtils.getSelectValue = _HtmlUtils_getSelectValue;
HtmlUtils.getSelectOptionValues = _HtmlUtils_getSelectOptionValues;
HtmlUtils.chooseSelectValue = _HtmlUtils_chooseSelectValue;

// Query element visibility.
HtmlUtils.isElementVisible = _HtmlUtils_isElementVisible;
HtmlUtils.isElementHidden = _HtmlUtils_isElementHidden;

// Class manipulation.
HtmlUtils.containsClass = _HtmlUtils_containsClass;
HtmlUtils.addClass = _HtmlUtils_addClass;
HtmlUtils.removeClass = _HtmlUtils_removeClass;

// Set element visibility.
HtmlUtils.showElement = _HtmlUtils_showElement;
HtmlUtils.hideElement = _HtmlUtils_hideElement;
HtmlUtils.toggleElements = _HtmlUtils_toggleElements;

// Hack for stupid IE bug.
HtmlUtils.ieSelectHack = _HtmlUtils_ieSelectHack;

// Element visibility class names.
HtmlUtils.HIDDEN_ELEMENT_CLASS = "hiddenElement";
HtmlUtils.VISIBLE_ELEMENT_CLASS = "visibleElement";


/**
 *  Returns the selected value of a drop-down.
 */
function _HtmlUtils_getSelectValue(select)
{
    var value = null;
    for (var i = 0, n = select.options.length; i < n; i++)
    {
        var option = select.options[i];
        if (option.selected)
        {
            value = option.value;
            break;
        }
    }
    return value;
}

/**
 *  Returns the values for the options of a drop-down.
 */
function _HtmlUtils_getSelectOptionValues(select)
{
    var values = new Array();
    for (var i = 0, n = select.options.length; i < n; i++)
    {
        var option = select.options.item(i);
        values.push(option.value);
    }
    return values;
}

/**
 *  Selects a value in a drop-down.
 */
function _HtmlUtils_chooseSelectValue(select, value)
{
    for (var i = 0, n = select.options.length; i < n; i++)
    {
        var option = select.options[i];
        if (option.value == value)
        {
            option.selected = true;
            break;
        }
    }
}

/**
 *  Does an element include this class specification?
 */
function _HtmlUtils_containsClass(element, className)
{
    var contains = false;
    if (element && className)
    {
        var elementClasses = element.className.split(/\s+/);
        for (var i = 0, n = elementClasses.length; i < n; i++)
        {
            var elementClass = elementClasses[i];
            if (elementClass == className)
            {
                contains = true;
                break;
            }
        }
    }
    return contains;
}

/**
 *  Adds a class to the element, if it doesn't already contain the class.
 */
function _HtmlUtils_addClass(element, className)
{
    if (element && className)
    {
        var contains = false;
        var elementClasses = element.className.split(/\s+/);
        for (var i = 0, n = elementClasses.length; i < n; i++)
        {
            var elementClass = elementClasses[i];
            if (elementClass == className)
            {
                contains = true;
                break;
            }
        }

        if (! contains)
        {
            elementClasses.push(className);
            element.className = elementClasses.join(" ");
        }
    }
}

/**
 *  Removes a class from the element.
 */
function _HtmlUtils_removeClass(element, className)
{
    if (element && className)
    {
        var contains = false;
        var elementClasses = element.className.split(/\s+/);
        for (var i = (elementClasses.length - 1); i >= 0; i--)
        {
            var elementClass = elementClasses[i];
            if (elementClass == className)
            {
                contains = true;
                elementClasses.splice(i, 1);
            }
        }

        if (contains)
        {
            element.className = elementClasses.join(" ");
        }
    }
}

/**
 *  Determines if an element is visible, i.e. does it contain the
 *  "visibleElement" class.
 */
function _HtmlUtils_isElementVisible(element)
{
    return _HtmlUtils_containsClass(element, HtmlUtils.VISIBLE_ELEMENT_CLASS)
           && (! _HtmlUtils_containsClass(element, HtmlUtils.HIDDEN_ELEMENT_CLASS));
}

/**
 *  Determines if an element is hidden, i.e. does it contain the "hiddenElement"
 *  class.
 */
function _HtmlUtils_isElementHidden(element)
{
    return _HtmlUtils_containsClass(element, HtmlUtils.HIDDEN_ELEMENT_CLASS)
           && (! _HtmlUtils_containsClass(element, HtmlUtils.VISIBLE_ELEMENT_CLASS));
}

/**
 *  Makes an element visible by replacing the "hiddenElement" class with
 *  "visibleElement".
 */
function _HtmlUtils_showElement(element)
{
    _HtmlUtils_removeClass(element, HtmlUtils.HIDDEN_ELEMENT_CLASS);
    _HtmlUtils_addClass(element, HtmlUtils.VISIBLE_ELEMENT_CLASS);
}

/**
 *  Hides an element by replacing the "visibleElement" class with
 *  "hiddenElement".
 */
function _HtmlUtils_hideElement(element)
{
    _HtmlUtils_removeClass(element, HtmlUtils.VISIBLE_ELEMENT_CLASS);
    _HtmlUtils_addClass(element, HtmlUtils.HIDDEN_ELEMENT_CLASS);
}

/**
 *  Toggles visibility of its arguments.
 */
function _HtmlUtils_toggleElements()
{
    for (var i = 0, n = arguments.length; i < n; i++)
    {
        var element = document.getElementById(arguments[i]);
        if (element)
        {
            if (HtmlUtils.isElementVisible(element))
            {
                HtmlUtils.hideElement(element);
            }
            else if (HtmlUtils.isElementHidden(element))
            {
                HtmlUtils.showElement(element);
            }
        }
    }
}

/**
 *  Implements the <select> hack for IE.  This hides all <select> elements on
 *  the page becuase in IE 6.0, they will appear above other HTML elements, even
 *  those that should be on top.  So this is necessary to work around the stupid
 *  IE problem.  Set "isVisible" to false to hide the <select> elements, and set
 *  to true to display them.
 */
function _HtmlUtils_ieSelectHack(isVisible)
{
    var selects = document.getElementsByTagName("select");
    for (var i = 0, n = selects.length; i < n; i++)
    {
        var select = selects.item(i);
        if (isVisible)
        {
            select.style.visibility = "visible";
        }
        else
        {
            select.style.visibility = "hidden";
        }
    }
}

/**
 *  Static inner class that matches a node to a class name.
 */
HtmlUtils.ClassMatcher =
        function(className)
        {
            var _className = className;

            this.matchNode = function(node)
            {
                return ((node.nodeType == 1) // TODO
                        && HtmlUtils.containsClass(node, _className));
            }
        };



///////////////////////////////////////////////////////////////////////////////
//
//  DomUtils

/**
 *  Utility class containing static helpers for manipulating the DOM.
 */
var DomUtils = new Object();

DomUtils.clearNode = _DomUtils_clearNode;
DomUtils.getNodeText = _DomUtils_getNodeText;
DomUtils.setNodeText = _DomUtils_setNodeText;
DomUtils.getDescendants = _DomUtils_getDescendants;
DomUtils.getMatchingDescendants = _DomUtils_getMatchingDescendants;

/**
 *  Convenience method to return all descendants of a node that are elements.
 */
DomUtils.getElementDescendants = function(node)
{
    return DomUtils.getDescendants(node, 1); // TODO
};


/**
 *  Clears all child elements in a node.
 */
function _DomUtils_clearNode(node)
{
    while (node.hasChildNodes())
    {
        node.removeChild(node.firstChild);
    }
}

/**
 *  Returns the text for a node.
 */
function _DomUtils_getNodeText(node)
{
    var text = "";
    for (var i = 0, n = node.childNodes.length; i < n; i++)
    {
        var child = node.childNodes.item(i);
        if (child.nodeType == 3) // TEXT_NODE
        {
            text += child.nodeValue;
        }
    }
    return text;
}

/**
 *  Adds text to a node.
 */
function _DomUtils_setNodeText(node, text)
{
    node.appendChild(document.createTextNode(text));
}

/**
 *  Returns an array of all the descendants of the specified node.  If the
 *  nodeType parameter is specified, only nodes of that type are returned.
 *  The descendants are returned depth-first, so if this function were called
 *  on the <html> node of the following very simple structure, with nodeType of
 *  1 (Element):
 *
 *      <html>
 *          <head>
 *              <title>Covergence</title>
 *          </head>
 *          <body>
 *              <h1>Covergence</h1>
 *          </body>
 *      </html>
 *
 *  The order of the returned nodes would be <head>, <title>, <body>, <h1>.
 */
function _DomUtils_getDescendants(node, nodeType)
{
    var descendants = new Array();
    var children = node.childNodes;
    if (children != null)
    {
        for (var i = 0, n = children.length; i < n; i++)
        {
            var child = children.item(i);
            if ((nodeType == null) || (child.nodeType == nodeType))
            {
                descendants.push(child);
                var subDescendants = _DomUtils_getDescendants(child, nodeType);
                if ((subDescendants != null) && (subDescendants.length > 0))
                {
                    Array.prototype.push.apply(descendants, subDescendants);
                }
            }
        }
    }
    return descendants;
}

/**
 *  Returns an array of all the descendants of the specified node, with an
 *  option to include those modes that pass a matching function.  If the
 *  matchFn parameter is specified, each node is passed to the function.  It is
 *  included in the result only if the function returns true.
 */
function _DomUtils_getMatchingDescendants(node, matchFn)
{
    var descendants = new Array();
    var children = node.childNodes;
    if (children != null)
    {
        for (var i = 0, n = children.length; i < n; i++)
        {
            var child = children.item(i);
            if ((matchFn == null) || matchFn.call(null, child))
            {
                descendants.push(child);
            }
            var subDescendants = _DomUtils_getMatchingDescendants(child, matchFn);
            if ((subDescendants != null) && (subDescendants.length > 0))
            {
                Array.prototype.push.apply(descendants, subDescendants);
            }
        }
    }
    return descendants;
}


///////////////////////////////////////////////////////////////////////////////
//
//  DateConstants

/**
 *  Class containing constants for dates and calendars.
 */
var DateConstants = new Object();

DateConstants.MILLISECONDS_IN_MINUTE = 60000;
DateConstants.MILLISECONDS_IN_DAY    = 86400000;
DateConstants.DAYS_IN_WEEK           = 7;
