/**
 * @name        Global Dom
 * @overview    An automated documentation publishing system for JavaScript.
 * @version        0.1
 * @revision    2007-06-01 00:00:00
 * @author        Michael Ord <a href="mailto:michael.ord@think.eu">michael.ord@think.eu</a>
 */

/**
 * @function    YAHOO.util.Dom.toggleClass
 * @description    Toggles the class of an element(s) either on or off
 * @param        {String|HTMLElement|Array} el The element or collection to add the class to
 * @param        {String} className The class name to add to the class attribute
 */
YAHOO.util.Dom.toggleClass = function (el, className) {
    if (YAHOO.util.Dom.hasClass (el, className)) {
        YAHOO.util.Dom.removeClass (el, className);
    } else {
        YAHOO.util.Dom.addClass (el, className);
    };
};
/**
 * @function    YAHOO.util.Dom.getAncestorsByClass
 * @description    DESCRIPTION HERE
 * @param        {HTMLElement|String} obj The starting point, this can be either a string id or an HTMLElement
 * @param        {Object} [className] The class to look for
 * @param        {Object} [tag] The HTML tag to look for
 * @param        {Number} [iterations] The number of ancestors/iterations to do
 * @param        {Boolean} [findFirst] Set this to true to break the find loop if you only want to return the first ancestor found
 * @return        {Array|Boolean|HTMLElement} This will return an array of HTML elements false if nothing is found, or an HTMLElement if findFirst is set to true
 */
YAHOO.util.Dom.getAncestorsByClass = function (obj, className, tag, iterations, findFirst) {
    className    = className        || false;
    tag            = tag            || false;
    obj            = YAHOO.util.Dom.get (obj);
    if (!obj) {
        return false;
    };
    // an array to store the ancestors/parent nodes that match the className
    var ancestors    = new Array;
    // start with the parent of the object
    var parent        = obj.parentNode;
    // set a maximum number or parent nodes to look through - may be removed
    var iteration    = iterations || 20;
    // loop backwards through the DOM
    while (parent && iteration--){
        // if the element has a matching class, add it to the array
        if (YAHOO.util.Dom.hasClass (parent, className) || !className){
            if (!tag || (tag && parent.nodeName.toLowerCase () == tag)){
                ancestors.push (parent);
                if (findFirst) {
                    return parent;
                };
            };
        };
        // move on to the next parent
        parent    = parent.parentNode;
    };
    // return the list
    return ancestors.length ? ancestors : false;
};
/**
 * @function        YAHOO.util.Dom.getElementsBySelector
 * @description        DESCRIPTION HERE
 * @param            {String} selector An HTML selector to match for
 * @return            {Array} An array of elements that metch the specified selector
 */
YAHOO.util.Dom.getElementsBySelector = function (selector) {
    if (!document.getElementsByTagName){
        return new Array;
    };
    var getAllChildren = function (e)    {
        return e.all ? e.all : e.getElementsByTagName ('*');
    };
    // Split selector in to tokens
    var tokens            = selector.split (' ');
    var currentContext    = new Array (document);
    for (var i = 0; i < tokens.length; i++) {
        token    = tokens [ i ].replace (/^\s+/,'').replace (/\s+$/, '');
        if (token.indexOf ('#') > -1) {
            // Token is an ID selector
            var bits    = token.split ('#');
            var tagName    = bits [ 0 ];
            var id        = bits [ 1 ];
            var element    = YAHOO.util.Dom.get(id);
            if ( !element ) {
                return new Array;// tag with that ID not found, return false
            }
            if (tagName && element.nodeName.toLowerCase() != tagName) {
                return new Array;// tag with that ID not found, return false
            };
            // Set currentContext to contain just this element
            currentContext    = new Array (element);
            continue; // Skip to next token
        };
        if (token.indexOf ('.') > -1) {
            // Token contains a class selector
            var bits        = token.split('.');
            var tagName        = bits[0];
            var className    = bits[1];
            if (!tagName) {
                tagName        = '*';
            };
            // Get elements matching tag, filter them for class selector
            var found        = new Array;
            var foundCount    = 0;
            for (var h = 0; h < currentContext.length; h++) {
                var elements    = tagName == '*' ? getAllChildren (currentContext [ h ]) : currentContext [ h ].getElementsByTagName (tagName);
                for (var j = 0; j < elements.length; j++) {
                    found [ foundCount++ ]    = elements [ j ];
                };
            };
            currentContext            = new Array;
            var currentContextIndex    = 0;
            for (var k = 0; k < found.length; k++) {
                if (found [ k ].className && found [ k ].className.match (new RegExp ('\\b' + className + '\\b'))) {
                    currentContext [ currentContextIndex++ ] = found [ k ];
                };
            };
            continue; // Skip to next token
        };
        // Code to deal with attribute selectors
        if (token.match (/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
            var tagName            = RegExp.$1;
            var attrName        = RegExp.$2;
            var attrOperator    = RegExp.$3;
            var attrValue        = RegExp.$4;
            if (!tagName) {
                tagName    = '*';
            };
            // Grab all of the tagName elements within current context
            var found        = new Array;
            var foundCount    = 0;
            var elements;
            for (var h = 0; h < currentContext.length; h++) {
                if (tagName == '*') {
                    elements    = getAllChildren (currentContext [ h ]);
                } else {
                    elements    = currentContext [ h ].getElementsByTagName (tagName);
                };
                for (var j = 0; j < elements.length; j++) {
                    found [ foundCount++ ]    = elements [ j ];
                };
            };
            currentContext            = new Array;
            var currentContextIndex    = 0;
            var checkFunction; // This function will be used to filter the elements
            switch (attrOperator) {
                case '=': // Equality
                    checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
                    break;
                case '~': // Mach one of space seperated words
                    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
                    break;
                case '|': // Match start with value followed by optional hyphen
                    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
                    break;
                case '^': // Match starts with value
                    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
                    break;
                case '$': // Match ends with value - fails with "Warning" in Opera 7
                    checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
                    break;
                case '*': // Match ends with value
                    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
                    break;
                default :
                    // Just test for existence of attribute
                    checkFunction = function(e) { return e.getAttribute(attrName); };
            };
            currentContext        = new Array;
            var currentContextIndex    = 0;
            for (var k = 0; k < found.length; k++) {
                if (checkFunction (found [ k ])) {
                    currentontext [ currentContextIndex++ ]    = found [ k ];
                };
            };
            continue; // Skip to next token
        };
        if (!currentContext [ 0 ]) {
            return;
        };
        // If we get here, token is JUST an element (not a class or ID selector)
        tagName            = token;
        var found        = new Array;
        var foundCount    = 0;
        for (var h = 0; h < currentContext.length; h++) {
            var elements    = currentContext [ h ].getElementsByTagName (tagName);
            for (var j = 0; j < elements.length; j++)    {
                found [ foundCount++ ]    = elements [ j ];
            };
        };
        currentContext    = found;
    };
    return currentContext;
};