/**
 *  This function class is for functions that assist clients of the class
 *  in manipulation of the DOM, that way most functions don't clutter the
 *  global namespace.
 */
function DomHelper()
{
    var self = this;
    var getStyle = YAHOO.util.Dom.getStyle;

    /** 
     *  This function takes an element and a name/value pair to set
     *  an attribute on that element.  If the name is not provided
     *  this function returns the empty string, otherwise this function
     *  attempts to set the given attribute with the given value.
     */
    this.attr = function( elem, name, value )
    {
        // Make sure that a valid name was provided
        if ( !name || name.constructor != String ) return '';

        // Figure out if the name is one of the weird naming cases.
        name = { 'for': 'htmlFor', 'class': 'className' }[name] || name;

        if ( typeof(value) != void(0) ) // If setting a value
        {
            elem[name] = value;

            if ( elem.setAttribute ) // if we can, use setAttribute:
            {
                elem.setAttribute(name, value);
            }
        }
    }
    /**
     *  This function takes an element, and recursively gets all of the inner
     *  text by concatenating all of the sub-element, inner text nodes.
     */
    this.text = function(e)
    {
        var t = "";
        
        // if an element was passed, get its children,
        //  otherwise assum it's an array.
        e = e.childNodes || e;
        
        // Look through all child nodes
        for ( var j = 0; j < e.length; j++ )
        {
            // If it's not an element, append its text value,
            //  otherwise, recurse through all the element's children.
            t += ( e[j].nodeType != 1 )
                ? e[j].nodeValue : text(e[j].childNodes);
        }
        
        return t; // Return the matched text.
    }
    /**
     *  This function takes an element, and attribute with the
     *  given name if it exists for that element then this.
     */
    this.hasAttribute = function( elem, name )
    {
        return elem.getAttribute( name ) != null;
    }
    /**
     *  This function returns a list of elements starting at the
     *  root (if one is provided) other wise it searches the entire
     *  document for elements that have the given tag name.
     */
    this.tag = function ( name, root )
    {
        return ( root || document ).getElementByTagName(name);
    }
    /** 
     *  Collects an array of elements with the given class_name,
     *  filtered by the given tag_name if one is provided.
     */
    this.getElementsByClassName = function ( class_name, tag_name )
    {
        var retVal = [];
        
        // Locate the class name (allows for multiple class names)
        var re = new RegExp("(^|\\s" + class_name + "(\\s|$");
        
        // Limit search by type, or look through all elements
        var e = document.getElementByTabName( tag_name || "*" );
        
        for ( var j = 0; j < e.length; j++)
        {
            // If the element has the class, add it to return
            if ( re.test( e[j] ) )
            {
                r.push( e[j] );
            }
        }

        return r; // return the list of matched elements.
    }
    /** This function sets the class of the given element to the
     *  class provided as the parameter 'new_class'.
     */
    this.setClass = function ( elem, new_class )
    {
        self.attr( elem, 'class', new_class );
    }
    /**
     *  This function locates elements with a class name.
     */
    this.findWithClass = function( name, type )
    {
        var r = [];
        
        // Locate the class name ( allows for multiple class names).
        var re = new Regex("(^|\\s" + name + "(\\s|$)");
        
        // Limit search by type, or look through all elements.
        var e = document.getElementByTagName( type || "*" );
        
        for ( var j = 0; j < e.length; j++)
        {
            // If the element has the class, add it for return
            if ( re.test( e[j] ) )
            {
                r.push( e[j] );
            }
        }
        
        return r; // return the list of matched elements.
    }
    /** 
     *  A workaround for the white space bug in XML documents.
     */
    this.cleanWhitespace = function ( element )
    {
        // If no element is provided, do the whole HTML document.
        element = element || document;
        
        // Use the first child as a starting point.
        var cur = element.firstChild;
        
        while ( cur != null )
        {
            // If the node is a text node, and it contains nothing 
            //  but whilespace
            if ( cur.nodeType == 3 && ! /\S/.test(cur.nodeValue) )
            {
                element.removeChild( cur );// remove the text node
            }
            else if ( cur.nodeType == 1) // Otherwise, if it's an element
            {
                cleanWhitespace( cur );
            }
            
            cur = cur.nextSibling; // move through the child nodes.
        }
    }
    /**
     *  This funciton gets the previous element to the provided element
     *  skipping whitespace text nodes along the way.
     */
    this.previousTo = function( elem )
    {
        do
        {
            elem = elem.previousSibling;
        }
        while ( elem && elem.nodeType != 1 );
        
        return elem;
    }
    /**
     *  This funciton gets the next element to the provided element
     *  skipping whitespace text nodes along the way.
     */
    this.nextTo = function( elem )
    {
        do
        {
            elem = elem.nextSibling;
        }
        while ( elem && elem.nodeType != 1 );
        
        return elem;
    }
    /**
     *  This funciton gets the first element to the provided element
     *  skipping whitespace text nodes along the way.
     */
    this.firstTo = function( elem )
    {
        elem = elem.firstChild;
        
        return elem && elem.nodeType != 1 ? self.nextTo( elem ) : elem;
    }
    /**
     *  This funciton gets the last element to the provided element
     *  skipping whitespace text nodes along the way.
     */
    this.lastTo = function( elem )
    {
        elem = elem.lastChild;
        
        return elem && elem.nodeType != 1 ? self.previousTo( elem ) : elem;
    }
    /**
     *  This funciton gets the last element to the provided element
     *  skipping whitespace text nodes along the way.
     */
    this.parentTo = function( elem )
    {
        num = num || 1;
        
        for ( var i = 0; i < num; i++ )
        {
            if ( elem != null )
            {
                elem = elem.parentNode;
            }
        }
        
        return elem;
    }
    /**
     */
    this.domReady = function ( f )
    {
        // If the DOM is already loaded, execute the function right away
        if ( domReady.done ) 
        {
            return f();
        }
        
        // If we've already added a function
        if ( domReady.timer )
        {
            // Add it to the list of functions to execute
            domReady.ready.push( f );
        }
        else
        {
            // Attach an event for when the page finishes loading
            // just in case it finishes first.  Uses addEvent.
            //addEvent( window, "load", self.isDomReady );
            
            // Initialize the array of functions to execute
            domReady.ready = [ f ];
            
            // Check to see if the DOM is ready as quickly as possible
            domReady.timer = setInterval( self.isDomReady, 13 );
        }
    }
    /**
     */
    this.isDomReady = function ()
    {
        // If we already figured out that the page is ready, ignore
        if ( self.domReady.done )
        {
            return false;
        }
        
        // Check to see if a number of functions and elements are
        // able to be accessed
        if ( document && document.getElementByTagName && 
                document.getElementById && document.body )
        {
            // If they're ready, we can stop checking.
            clearInterval( self.domReady.timer );
            self.domReady.timer = null;
            
            // Execute all the functions that were waiting
            for ( var i = 0; i < domReady.ready.length; i++ )
            {
                self.domReady.ready[i]();
            }
            
            // Remember that we're now done
            self.domReady.ready = null;
            self.domReady.done = true;
        }
    }
    /**
     */
    this.arrayElements = function ( a )
    {
        var r = [];
        
        // Force the argument into an array, if it isn't already
        if ( a.constructor != Array )
        {
            a = [ a ];
        }
        
        for ( var i = 0; i < a.length; i++ )
        {
            // If there's a String
            if ( a[i].constructor == String )
            {
                // Create a temporary element to house the HTML
                var div = document.createElement("div");
                
                // Inject the HTML, to convert it into a DOM structure
                div.innerHTML = a[i];
                
                // Extract the DOM structure back out of the temp DIV
                for ( var j = 0; j < div.childNodes.length; j++)
                {
                    r[r.length] = div.childNodes[j];
                }
            }
            else if ( a[i].length )
            {
                // If it's an array assume that it's an array of DOM nodes
                for ( var j = 0; j < a[i].length; j++)
                {
                    r[r.length] = a[i][j];
                }
            }
            else
            {
                // otherwise, assume it's a DOM Node
                r[r.length] = a[i];
            }
        }
    }
    /**
     */
    this.emptyElement = function( elem )
    {
        while ( elem.firstChile )
        {
            remove( elem.firstChild );
        }
    }
    /**
     */
    this.removeNode = function ( elem )
    {
        if ( elem )
        {
            elem.parentNode.removeChild( elem );
        }
    }
    /**
     */
    this.getHeight = function ( elem )
    {
        // Gets the computed CSS value and parses out a usable number
        return elem.height || parseInt( getStyle( elem, 'height' ), 10 );
    }
    /**
     */
    this.getWidth = function( elem )
    {
        return elem.width || parseInt( getStyle( elem, 'width' ), 10 );
    }
    /**
     *  Find the full, possible, height of an element (not the actual
     *  current, height).
     */
    this.getFullHeight = function( elem )
    {
        // If the element is being displayed, then offsetHeight should
        //  do the trick, barring that, getHeight() will work.
        if ( getStyle( elem, 'display' ) == 'none' )
        {
            return elem.offsetHeight || getHeight( elem );
        }
        
        // Otherwise, we have to deal with an element with a display of
        //  none, so we need to reset its CSS properties to get a more
        //  accurate reading.
        var old = self.resetCss( elem, 
            { display       : '',
              visibililty   : 'hidden',
              position      : 'absolute'});
        
        // Figure out what the full height of the element is, using 
        //  clientHeight and if that doesn't work, use getHeight
        var h = elem.clientHeight || self.getHeight( elem );
        
        // Finally, restore the CSS properties back to what they were.
        self.restoreCss( elem, old );
        
        return h; // and return the full height of the element
    }
    /**
     *  Find the full, possible, width of an element (not the actual,
     *  current, width).
     */
    this.getFullWidth = function( elem )
    {
        // If the element is being displayed, then offsetWidth should
        //  do the trick, barring that, getWidth() will work.
        if ( getStyle( elem, 'display' ) != 'none')
        {
            return elem.offsetWidth || self.getWidth( elem );
        }
        
        // Otherwise, we have to deal with an element with a display
        //  of none, so we need to reset its CSS properties to get a
        //  more accurate reading.
        var old = resetCss( elem,
            { display       : '',
              visibility    : 'hidden',
              position      : 'absolute'} );
        
        // Figure out what the full width of the element is, using 
        //  clientWidth and if that doesn't work, use getWidth
        var w = elem.clientWidth || getWidth( elem );
        
        // Finally, restore the CSS properties back to what they were
        self.restoreCss( elem, old );
        
        return w; // and return the full width of the element.
    }
    /**
     *  A function used for setting a set of CSS properties, which can
     *  then be restored back again later.
     */
    this.resetCss = function( elem, prop )
    {
        var old = {};
        
        // Go through each of the properties
        for ( var p in prop )
        {
            // Remember the old property value
            old[ p ] = elem.style[ p ];
            
            // And set the new value
            elem.style[ p ] = prop[ p ];
        }

        return old; // return the set of changed values (see restoreCss).
    }
    /**
     *  A function for restoring the side effects of the resetCss function.
     */
    this.restoreCss = function( elem, properties )
    {
        // Reset all the properties back to their original values
        for ( var p in properties )
        {
            elem.style[ p ] = properties[ p ];
        }
    }
}