/*
 selectivizr v1.0.3b - (c) Keith Clark, freely distributable under the terms
 of the MIT license.

 selectivizr.com
 */
/* 

 Notes about this source
 -----------------------

 * The #DEBUG_START and #DEBUG_END comments are used to mark blocks of code
 that will be removed prior to building a final release version (using a
 pre-compression script)


 References:
 -----------

 * CSS Syntax          : http://www.w3.org/TR/2003/WD-css3-syntax-20030813/#style
 * Selectors           : http://www.w3.org/TR/css3-selectors/#selectors
 * IE Compatability    : http://msdn.microsoft.com/en-us/library/cc351024(VS.85).aspx
 * W3C Selector Tests  : http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/tests/

 */

(function(win) {

    // Determine IE version and stop execution if browser isn't IE. This
    // handles the script being loaded by non IE browsers because the
    // developer didn't use conditional comments.
    var ieUserAgent = navigator.userAgent.match(/MSIE (\d+)/);
    if (!ieUserAgent) {
        return false;
    }

    // =========================== Init Objects ============================

    var doc = document;
    var root = doc.documentElement;
    var xhr = getXHRObject();
    var ieVersion = ieUserAgent[1];

    // If were not in standards mode, IE is too old / new or we can't create
    // an XMLHttpRequest object then we should get out now.
    if (doc.compatMode != 'CSS1Compat' || ieVersion<6 || ieVersion>8 || !xhr) {
        return;
    }


    // ========================= Common Objects ============================

    // Compatiable selector engines in order of CSS3 support. Note: '*' is
    // a placholder for the object key name. (basically, crude compression)
    var selectorEngines = {
        "NW"								: "*.Dom.select",
        "MooTools"							: "$$",
        "DOMAssistant"						: "*.$",
        "Prototype"							: "$$",
        "YAHOO"								: "*.util.Selector.query",
        "Sizzle"							: "*",
        "jQuery"							: "*",
        "dojo"								: "*.query"
    };

    var selectorMethod;
    var enabledWatchers 					= [];     // array of :enabled/:disabled elements to poll
    var domPatches							= [];
    var ie6PatchID 							= 0;      // used to solve ie6's multiple class bug
    var patchIE6MultipleClasses				= true;   // if true adds class bloat to ie6
    var namespace 							= "slvzr";

    // Stylesheet parsing regexp's
    var RE_COMMENT							= /(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*?/g;
    var RE_IMPORT							= /@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))\s*([^;]*);/g;
    var RE_ASSET_URL 						= /(behavior\s*?:\s*)?\burl\(\s*(["']?)(?!data:)([^"')]+)\2\s*\)/g;
    var RE_PSEUDO_STRUCTURAL				= /^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/;
    var RE_PSEUDO_ELEMENTS					= /:(:first-(?:line|letter))/g;
    var RE_SELECTOR_GROUP					= /((?:^|(?:\s*})+)(?:\s*@media[^{]+{)?)\s*([^\{]*?[\[:][^{]+)/g;
    var RE_SELECTOR_PARSE					= /([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g;
    var RE_LIBRARY_INCOMPATIBLE_PSEUDOS		= /(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g;
    var RE_PATCH_CLASS_NAME_REPLACE			= /[^\w-]/g;

    // HTML UI element regexp's
    var RE_INPUT_ELEMENTS					= /^(INPUT|SELECT|TEXTAREA|BUTTON)$/;
    var RE_INPUT_CHECKABLE_TYPES			= /^(checkbox|radio)$/;

    // Broken attribute selector implementations (IE7/8 native [^=""], [$=""] and [*=""])
    var BROKEN_ATTR_IMPLEMENTATIONS			= ieVersion>6 ? /[\$\^*]=(['"])\1/ : null;

    // Whitespace normalization regexp's
    var RE_TIDY_TRAILING_WHITESPACE			= /([(\[+~])\s+/g;
    var RE_TIDY_LEADING_WHITESPACE			= /\s+([)\]+~])/g;
    var RE_TIDY_CONSECUTIVE_WHITESPACE		= /\s+/g;
    var RE_TIDY_TRIM_WHITESPACE				= /^\s*((?:[\S\s]*\S)?)\s*$/;

    // String constants
    var EMPTY_STRING						= "";
    var SPACE_STRING						= " ";
    var PLACEHOLDER_STRING					= "$1";

    // =========================== Patching ================================

    // --[ patchStyleSheet() ]----------------------------------------------
    // Scans the passed cssText for selectors that require emulation and
    // creates one or more patches for each matched selector.
    function patchStyleSheet( cssText ) {
        return cssText.replace(RE_PSEUDO_ELEMENTS, PLACEHOLDER_STRING).
            replace(RE_SELECTOR_GROUP, function(m, prefix, selectorText) {
                var selectorGroups = selectorText.split(",");
                for (var c = 0, cs = selectorGroups.length; c < cs; c++) {
                    var selector = normalizeSelectorWhitespace(selectorGroups[c]) + SPACE_STRING;
                    var patches = [];
                    selectorGroups[c] = selector.replace(RE_SELECTOR_PARSE,
                        function(match, combinator, pseudo, attribute, index) {
                            if (combinator) {
                                if (patches.length>0) {
                                    domPatches.push( { selector: selector.substring(0, index), patches: patches } )
                                    patches = [];
                                }
                                return combinator;
                            }
                            else {
                                var patch = (pseudo) ? patchPseudoClass( pseudo ) : patchAttribute( attribute );
                                if (patch) {
                                    patches.push(patch);
                                    return "." + patch.className;
                                }
                                return match;
                            }
                        }
                    );
                }
                return prefix + selectorGroups.join(",");
            });
    };

    // --[ patchAttribute() ]-----------------------------------------------
    // returns a patch for an attribute selector.
    function patchAttribute( attr ) {
        return (!BROKEN_ATTR_IMPLEMENTATIONS || BROKEN_ATTR_IMPLEMENTATIONS.test(attr)) ?
        { className: createClassName(attr), applyClass: true } : null;
    };

    // --[ patchPseudoClass() ]---------------------------------------------
    // returns a patch for a pseudo-class
    function patchPseudoClass( pseudo ) {

        var applyClass = true;
        var className = createClassName(pseudo.slice(1));
        var isNegated = pseudo.substring(0, 5) == ":not(";
        var activateEventName;
        var deactivateEventName;

        // if negated, remove :not()
        if (isNegated) {
            pseudo = pseudo.slice(5, -1);
        }

        // bracket contents are irrelevant - remove them
        var bracketIndex = pseudo.indexOf("(")
        if (bracketIndex > -1) {
            pseudo = pseudo.substring(0, bracketIndex);
        }

        // check we're still dealing with a pseudo-class
        if (pseudo.charAt(0) == ":") {
            switch (pseudo.slice(1)) {

                case "root":
                    applyClass = function(e) {
                        return isNegated ? e != root : e == root;
                    }
                    break;

                case "target":
                    // :target is only supported in IE8
                    if (ieVersion == 8) {
                        applyClass = function(e) {
                            var handler = function() {
                                var hash = location.hash;
                                var hashID = hash.slice(1);
                                return isNegated ? (hash == EMPTY_STRING || e.id != hashID) : (hash != EMPTY_STRING && e.id == hashID);
                            };
                            addEvent( win, "hashchange", function() {
                                toggleElementClass(e, className, handler());
                            })
                            return handler();
                        }
                        break;
                    }
                    return false;

                case "checked":
                    applyClass = function(e) {
                        if (RE_INPUT_CHECKABLE_TYPES.test(e.type)) {
                            addEvent( e, "propertychange", function() {
                                if (event.propertyName == "checked") {
                                    toggleElementClass( e, className, e.checked !== isNegated );
                                }
                            })
                        }
                        return e.checked !== isNegated;
                    }
                    break;

                case "disabled":
                    isNegated = !isNegated;

                case "enabled":
                    applyClass = function(e) {
                        if (RE_INPUT_ELEMENTS.test(e.tagName)) {
                            addEvent( e, "propertychange", function() {
                                if (event.propertyName == "$disabled") {
                                    toggleElementClass( e, className, e.$disabled === isNegated );
                                }
                            });
                            enabledWatchers.push(e);
                            e.$disabled = e.disabled;
                            return e.disabled === isNegated;
                        }
                        return pseudo == ":enabled" ? isNegated : !isNegated;
                    }
                    break;

                case "focus":
                    activateEventName = "focus";
                    deactivateEventName = "blur";

                case "hover":
                    if (!activateEventName) {
                        activateEventName = "mouseenter";
                        deactivateEventName = "mouseleave";
                    }
                    applyClass = function(e) {
                        addEvent( e, isNegated ? deactivateEventName : activateEventName, function() {
                            toggleElementClass( e, className, true );
                        })
                        addEvent( e, isNegated ? activateEventName : deactivateEventName, function() {
                            toggleElementClass( e, className, false );
                        })
                        return isNegated;
                    }
                    break;

                // everything else
                default:
                    // If we don't support this pseudo-class don't create
                    // a patch for it
                    if (!RE_PSEUDO_STRUCTURAL.test(pseudo)) {
                        return false;
                    }
                    break;
            }
        }
        return { className: className, applyClass: applyClass };
    };

    // --[ applyPatches() ]-------------------------------------------------
    function applyPatches() {
        var elms, selectorText, patches, domSelectorText;

        for (var c=0; c<domPatches.length; c++) {
            selectorText = domPatches[c].selector;
            patches = domPatches[c].patches;

            // Although some selector libraries can find :checked :enabled etc.
            // we need to find all elements that could have that state because
            // it can be changed by the user.
            domSelectorText = selectorText.replace(RE_LIBRARY_INCOMPATIBLE_PSEUDOS, EMPTY_STRING);

            // If the dom selector equates to an empty string or ends with
            // whitespace then we need to append a universal selector (*) to it.
            if (domSelectorText == EMPTY_STRING || domSelectorText.charAt(domSelectorText.length - 1) == SPACE_STRING) {
                domSelectorText += "*";
            }

            // Ensure we catch errors from the selector library
            try {
                elms = selectorMethod( domSelectorText );
            } catch (ex) {
                // #DEBUG_START
                log( "Selector '" + selectorText + "' threw exception '" + ex + "'" );
                // #DEBUG_END
            }


            if (elms) {
                for (var d = 0, dl = elms.length; d < dl; d++) {
                    var elm = elms[d];
                    var cssClasses = elm.className;
                    for (var f = 0, fl = patches.length; f < fl; f++) {
                        var patch = patches[f];
                        if (!hasPatch(elm, patch)) {
                            if (patch.applyClass && (patch.applyClass === true || patch.applyClass(elm) === true)) {
                                cssClasses = toggleClass(cssClasses, patch.className, true );
                            }
                        }
                    }
                    elm.className = cssClasses;
                }
            }
        }
    };

    // --[ hasPatch() ]-----------------------------------------------------
    // checks for the exsistence of a patch on an element
    function hasPatch( elm, patch ) {
        return new RegExp("(^|\\s)" + patch.className + "(\\s|$)").test(elm.className);
    };


    // =========================== Utility =================================

    function createClassName( className ) {
        return namespace + "-" + ((ieVersion == 6 && patchIE6MultipleClasses) ?
            ie6PatchID++
            :
            className.replace(RE_PATCH_CLASS_NAME_REPLACE, function(a) { return a.charCodeAt(0) }));
    };

    // --[ log() ]----------------------------------------------------------
    // #DEBUG_START
    function log( message ) {
        if (win.console) {
            win.console.log(message);
        }
    };
    // #DEBUG_END

    // --[ trim() ]---------------------------------------------------------
    // removes leading, trailing whitespace from a string
    function trim( text ) {
        return text.replace(RE_TIDY_TRIM_WHITESPACE, PLACEHOLDER_STRING);
    };

    // --[ normalizeWhitespace() ]------------------------------------------
    // removes leading, trailing and consecutive whitespace from a string
    function normalizeWhitespace( text ) {
        return trim(text).replace(RE_TIDY_CONSECUTIVE_WHITESPACE, SPACE_STRING);
    };

    // --[ normalizeSelectorWhitespace() ]----------------------------------
    // tidies whitespace around selector brackets and combinators
    function normalizeSelectorWhitespace( selectorText ) {
        return normalizeWhitespace(selectorText.
            replace(RE_TIDY_TRAILING_WHITESPACE, PLACEHOLDER_STRING).
            replace(RE_TIDY_LEADING_WHITESPACE, PLACEHOLDER_STRING)
        );
    };

    // --[ toggleElementClass() ]-------------------------------------------
    // toggles a single className on an element
    function toggleElementClass( elm, className, on ) {
        var oldClassName = elm.className;
        var newClassName = toggleClass(oldClassName, className, on);
        if (newClassName != oldClassName) {
            elm.className = newClassName;
            elm.parentNode.className += EMPTY_STRING;
        }
    };

    // --[ toggleClass() ]--------------------------------------------------
    // adds / removes a className from a string of classNames. Used to
    // manage multiple class changes without forcing a DOM redraw
    function toggleClass( classList, className, on ) {
        var re = RegExp("(^|\\s)" + className + "(\\s|$)");
        var classExists = re.test(classList);
        if (on) {
            return classExists ? classList : classList + SPACE_STRING + className;
        } else {
            return classExists ? trim(classList.replace(re, PLACEHOLDER_STRING)) : classList;
        }
    };

    // --[ addEvent() ]-----------------------------------------------------
    function addEvent(elm, eventName, eventHandler) {
        elm.attachEvent("on" + eventName, eventHandler);
    };

    // --[ getXHRObject() ]-------------------------------------------------
    function getXHRObject() {
        if (win.XMLHttpRequest) {
            return new XMLHttpRequest;
        }
        try	{
            return new ActiveXObject('Microsoft.XMLHTTP');
        } catch(e) {
            return null;
        }
    };

    // --[ loadStyleSheet() ]-----------------------------------------------
    function loadStyleSheet( url ) {
        xhr.open("GET", url, false);
        xhr.send();
        return (xhr.status==200) ? xhr.responseText : EMPTY_STRING;
    };

    // --[ resolveUrl() ]---------------------------------------------------
    // Converts a URL fragment to a fully qualified URL using the specified
    // context URL. Returns null if same-origin policy is broken
    function resolveUrl( url, contextUrl, ignoreSameOriginPolicy ) {

        function getProtocol( url ) {
            return url.substring(0, url.indexOf("//"));
        };

        function getProtocolAndHost( url ) {
            return url.substring(0, url.indexOf("/", 8));
        };

        if (!contextUrl) {
            contextUrl = baseUrl;
        }

        // protocol-relative path
        if (url.substring(0,2)=="//") {
            url = getProtocol(contextUrl) + url;
        }

        // absolute path
        if (/^https?:\/\//i.test(url)) {
            return !ignoreSameOriginPolicy && getProtocolAndHost(contextUrl) != getProtocolAndHost(url) ? null : url ;
        }

        // root-relative path
        if (url.charAt(0)=="/")	{
            return getProtocolAndHost(contextUrl) + url;
        }

        // relative path
        var contextUrlPath = contextUrl.split(/[?#]/)[0]; // ignore query string in the contextUrl
        if (url.charAt(0) != "?" && contextUrlPath.charAt(contextUrlPath.length - 1) != "/") {
            contextUrlPath = contextUrlPath.substring(0, contextUrlPath.lastIndexOf("/") + 1);
        }

        return contextUrlPath + url;
    };

    // --[ parseStyleSheet() ]----------------------------------------------
    // Downloads the stylesheet specified by the URL, removes it's comments
    // and recursivly replaces @import rules with their contents, ultimately
    // returning the full cssText.
    function parseStyleSheet( url ) {
        if (url) {
            return loadStyleSheet(url).replace(RE_COMMENT, EMPTY_STRING).
                replace(RE_IMPORT, function( match, quoteChar, importUrl, quoteChar2, importUrl2, media ) {
                    var cssText = parseStyleSheet(resolveUrl(importUrl || importUrl2, url));
                    return (media) ? "@media " + media + " {" + cssText + "}" : cssText;
                }).
                replace(RE_ASSET_URL, function( match, isBehavior, quoteChar, assetUrl ) {
                    quoteChar = quoteChar || EMPTY_STRING;
                    return isBehavior ? match : " url(" + quoteChar + resolveUrl(assetUrl, url, true) + quoteChar + ") ";
                });
        }
        return EMPTY_STRING;
    };

    // --[ getStyleSheets() ]-----------------------------------------------
    function getStyleSheets() {
        var url, stylesheet;
        for (var c = 0; c < doc.styleSheets.length; c++) {
            stylesheet = doc.styleSheets[c];
            if (stylesheet.href != EMPTY_STRING) {
                url = resolveUrl(stylesheet.href);
                if (url) {
                    stylesheet.cssText = stylesheet["rawCssText"] = patchStyleSheet( parseStyleSheet( url ) );
                }
            }
        }
    };

    // --[ init() ]---------------------------------------------------------
    function init() {
        applyPatches();

        // :enabled & :disabled polling script (since we can't hook
        // onpropertychange event when an element is disabled)
        if (enabledWatchers.length > 0) {
            setInterval( function() {
                for (var c = 0, cl = enabledWatchers.length; c < cl; c++) {
                    var e = enabledWatchers[c];
                    if (e.disabled !== e.$disabled) {
                        if (e.disabled) {
                            e.disabled = false;
                            e.$disabled = true;
                            e.disabled = true;
                        }
                        else {
                            e.$disabled = e.disabled;
                        }
                    }
                }
            }, 250)
        }
    };

    // Determine the baseUrl and download the stylesheets
    var baseTags = doc.getElementsByTagName("BASE");
    var baseUrl = (baseTags.length > 0) ? baseTags[0].href : doc.location.href;
    getStyleSheets();

    // Bind selectivizr to the ContentLoaded event.
    ContentLoaded(win, function() {
        // Determine the "best fit" selector engine
        for (var engine in selectorEngines) {
            var members, member, context = win;
            if (win[engine]) {
                members = selectorEngines[engine].replace("*", engine).split(".");
                while ((member = members.shift()) && (context = context[member])) {}
                if (typeof context == "function") {
                    selectorMethod = context;
                    init();
                    return;
                }
            }
        }
    });


    /*!
     * ContentLoaded.js by Diego Perini, modified for IE<9 only (to save space)
     *
     * Author: Diego Perini (diego.perini at gmail.com)
     * Summary: cross-browser wrapper for DOMContentLoaded
     * Updated: 20101020
     * License: MIT
     * Version: 1.2
     *
     * URL:
     * http://javascript.nwbox.com/ContentLoaded/
     * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
     *
     */

    // @w window reference
    // @f function reference
    function ContentLoaded(win, fn) {

        var done = false, top = true,
            init = function(e) {
                if (e.type == "readystatechange" && doc.readyState != "complete") return;
                (e.type == "load" ? win : doc).detachEvent("on" + e.type, init, false);
                if (!done && (done = true)) fn.call(win, e.type || e);
            },
            poll = function() {
                try { root.doScroll("left"); } catch(e) { setTimeout(poll, 50); return; }
                init('poll');
            };

        if (doc.readyState == "complete") fn.call(win, EMPTY_STRING);
        else {
            if (doc.createEventObject && root.doScroll) {
                try { top = !win.frameElement; } catch(e) { }
                if (top) poll();
            }
            addEvent(doc,"readystatechange", init);
            addEvent(win,"load", init);
        }
    };
})(this);