/*
This file is part of Ext JS 4.2

Copyright (c) 2011-2013 Sencha Inc

Contact:  http://www.sencha.com/contact

Commercial Usage
Licensees holding valid commercial licenses may use this file in accordance with the Commercial
Software License Agreement provided with the Software or, alternatively, in accordance with the
terms contained in a written agreement between you and Sencha.

If you are unsure which license is appropriate for your use, please contact the sales department
at http://www.sencha.com/contact.

Build date: 2013-09-18 17:18:59 (940c324ac822b840618a3a8b2b4b873f83a1a9b1)
*/
/**
 * Utility class for manipulating CSS rules
 * @singleton
 */
Ext.define('Ext.util.CSS', function() {
    var CSS,
        rules = null,
        doc = document,
        camelRe = /(-[a-z])/gi,
        camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };

    return {

        singleton: true,

        rules: rules,

        initialized: false,

        constructor: function() {
            // Cache a reference to the singleton
            CSS = this;
        },

        /**
         * Creates a stylesheet from a text blob of rules.
         * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
         * @param {String} cssText The text containing the css rules
         * @param {String} id An id to add to the stylesheet for later removal
         * @return {CSSStyleSheet}
         */
        createStyleSheet : function(cssText, id) {
            var ss,
                head = doc.getElementsByTagName("head")[0],
                styleEl = doc.createElement("style");

            styleEl.setAttribute("type", "text/css");
            if (id) {
               styleEl.setAttribute("id", id);
            }

            if (Ext.isIE) {
               head.appendChild(styleEl);
               ss = styleEl.styleSheet;
               ss.cssText = cssText;
            } else {
                try{
                    styleEl.appendChild(doc.createTextNode(cssText));
                } catch(e) {
                   styleEl.cssText = cssText;
                }
                head.appendChild(styleEl);
                ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
            }
            CSS.cacheStyleSheet(ss);
            return ss;
        },

        /**
         * Removes a style or link tag by id
         * @param {String} id The id of the tag
         */
        removeStyleSheet : function(id) {
            var existing = doc.getElementById(id);
            if (existing) {
                existing.parentNode.removeChild(existing);
            }
        },

        /**
         * Dynamically swaps an existing stylesheet reference for a new one
         * @param {String} id The id of an existing link tag to remove
         * @param {String} url The href of the new stylesheet to include
         */
        swapStyleSheet : function(id, url) {
            var ss;
            CSS.removeStyleSheet(id);
            ss = doc.createElement("link");
            ss.setAttribute("rel", "stylesheet");
            ss.setAttribute("type", "text/css");
            ss.setAttribute("id", id);
            ss.setAttribute("href", url);
            doc.getElementsByTagName("head")[0].appendChild(ss);
        },

        /**
         * Refresh the rule cache if you have dynamically added stylesheets
         * @return {Object} An object (hash) of rules indexed by selector
         */
        refreshCache : function() {
            return CSS.getRules(true);
        },

        // @private
        cacheStyleSheet : function(ss) {
            if (!rules) {
                rules = CSS.rules = {};
            }
            try {// try catch for cross domain access issue
                var ssRules = ss.cssRules || ss.rules,
                    i = ssRules.length - 1,
                    imports = ss.imports,
                    len = imports ? imports.length : 0,
                    rule, j;
                    
                // Old IE has a different way of handling imports
                for (j = 0; j < len; ++j) {
                    CSS.cacheStyleSheet(imports[j]);
                }

                for (; i >= 0; --i) {
                    rule = ssRules[i];
                    // If it's an @import rule, import its stylesheet
                    if (rule.styleSheet) {
                        CSS.cacheStyleSheet(rule.styleSheet);
                    }
                    CSS.cacheRule(rule, ss);
                }
            } catch(e) {}
        },

        cacheRule: function(cssRule, styleSheet) {
            // If it's an @import rule, import its stylesheet
            if (cssRule.styleSheet) {
                return CSS.cacheStyleSheet(cssRule.styleSheet);
            }

            var selectorText = cssRule.selectorText,
                selectorCount, j;

            if (selectorText) {

                // Split in case there are multiple, comma-delimited selectors
                selectorText = selectorText.split(',');
                selectorCount = selectorText.length;
                for (j = 0; j < selectorCount; j++) {
                    // IE<8 does not keep a reference to parentStyleSheet in the rule, so we
                    // must cache an object like this until IE<8 is deprecated.
                    rules[Ext.String.trim(selectorText[j]).toLowerCase()] = {
                        parentStyleSheet: styleSheet,
                        cssRule: cssRule
                    };
                };
            }
        },

        /**
         * Gets all css rules for the document
         * @param {Boolean} refreshCache true to refresh the internal cache
         * @return {Object} An object (hash) of rules indexed by selector
         */
        getRules : function(refreshCache) {
            var result = {},
                selector;

            if (rules === null || refreshCache) {
                CSS.refreshCache();
            }
            for (selector in rules) {
                result[selector] = rules[selector].cssRule;
            }
            return result;
        },
        
        refreshCache: function() {
            var ds = doc.styleSheets,
                i = 0,
                len = ds.length;

            rules = CSS.rules = {}
            for (; i < len; i++) {
                try {
                    if (!ds[i].disabled) {
                        CSS.cacheStyleSheet(ds[i]);
                    }
                } catch(e) {}
            }
        },

        /**
         * Gets an an individual CSS rule by selector(s)
         * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
         * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
         * @return {CSSStyleRule} The CSS rule or null if one is not found
         */
        getRule: function(selector, refreshCache, rawCache) {
            var i, result;

            if (!rules || refreshCache) {
                CSS.refreshCache();
            }
            if (!Ext.isArray(selector)) {
                result = rules[selector.toLowerCase()]
                if (result && !rawCache) {
                    result = result.cssRule;
                }
                return result || null;
            }
            for (i = 0; i < selector.length; i++) {
                if (rules[selector[i]]) {
                    return rawCache ? rules[selector[i].toLowerCase()] : rules[selector[i].toLowerCase()].cssRule;
                }
            }
            return null;
        },

        /**
         * Creates a rule.
         * @param {CSSStyleSheet} styleSheet The StyleSheet to create the rule in as returned from {@link #createStyleSheet}.
         * @param {String} selector The selector to target the rule.
         * @param {String} property The cssText specification eg `"color:red;font-weight:bold;text-decoration:underline"`
         * @return {CSSStyleRule} The created rule
         */
        createRule: function(styleSheet, selector, cssText) {
            var result,
                ruleSet = styleSheet.cssRules || styleSheet.rules,
                index = ruleSet.length;

            if (styleSheet.insertRule) {
                styleSheet.insertRule(selector + '{' + cssText + '}', index);
            } else {
                styleSheet.addRule(selector, cssText||' ');
            }
            CSS.cacheRule(result = ruleSet[index], styleSheet);
            return result;
        },

        /**
         * Updates a rule property
         * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
         * @param {String} property The css property or a cssText specification eg `"color:red;font-weight:bold;text-decoration:underline"`
         * @param {String} value The new value for the property
         * @return {Boolean} true If a rule was found and updated
         */
        updateRule : function(selector, property, value) {
            var rule, i, styles;
            if (!Ext.isArray(selector)) {
                rule = CSS.getRule(selector);
                if (rule) {
                    // 2 arg form means cssText sent, so parse it and update each style
                    if (arguments.length == 2) {
                        styles = Ext.Element.parseStyles(property);
                        for (property in styles) {
                            rule.style[property.replace(camelRe, camelFn)] = styles[property];
                        }
                    } else {
                        rule.style[property.replace(camelRe, camelFn)] = value;
                    }
                    return true;
                }
            } else {
                for (i = 0; i < selector.length; i++) {
                    if (CSS.updateRule(selector[i], property, value)) {
                        return true;
                    }
                }
            }
            return false;
        },

        deleteRule: function(selector) {
            var rule = CSS.getRule(selector, false, true),
                styleSheet, index;

            if (rule) {
                styleSheet = rule.parentStyleSheet;
                index = Ext.Array.indexOf(styleSheet.cssRules || styleSheet.rules, rule.cssRule);
                if (styleSheet.deleteRule) {
                    styleSheet.deleteRule(index);
                } else {
                    styleSheet.removeRule(index);
                }
                delete rules[selector];
            }
        }
    };
});