/**
 * @class Ext.util.CSS
 * Utility class for manipulating CSS rules
 * @singleton
 */

Ext.util.CSS = function(){
       
var rules = null;
       
var doc = document;

   
var camelRe = /(-[a-z])/gi;
   
var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };

   
return {
   
/**
    * 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 {StyleSheet}
    */

   createStyleSheet
: function(cssText, id){
       
var ss;
       
var head = doc.getElementsByTagName("head")[0];
       
var rules = doc.createElement("style");
       rules
.setAttribute("type", "text/css");
       
if(id){
           rules
.setAttribute("id", id);
       
}
       
if(Ext.isIE){
           head
.appendChild(rules);
           ss
= rules.styleSheet;
           ss
.cssText = cssText;
       
}else{
           
try{
                rules
.appendChild(doc.createTextNode(cssText));
           
}catch(e){
               rules
.cssText = cssText;
           
}
           head
.appendChild(rules);
           ss
= rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
       
}
       
this.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){
       
this.removeStyleSheet(id);
       
var 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 this.getRules(true);
   
},

   
// private
   cacheStyleSheet
: function(ss){
       
if(!rules){
           rules
= {};
       
}
       
try{// try catch for cross domain access issue
           
var ssRules = ss.cssRules || ss.rules;
           
for(var j = ssRules.length-1; j >= 0; --j){
               rules
[ssRules[j].selectorText] = ssRules[j];
           
}
       
}catch(e){}
   
},
   
   
/**
    * 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){
               
if(rules === null || refreshCache){
                        rules
= {};
                       
var ds = doc.styleSheets;
                       
for(var i =0, len = ds.length; i < len; i++){
                           
try{
                       
this.cacheStyleSheet(ds[i]);
                   
}catch(e){}
               
}
               
}
               
return rules;
       
},
       
       
/**
    * Gets an an individual CSS rule by selector(s)
    * @param {String/Array} 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 {CSSRule} The CSS rule or null if one is not found
    */

   getRule
: function(selector, refreshCache){
               
var rs = this.getRules(refreshCache);
               
if(!Ext.isArray(selector)){
                   
return rs[selector];
               
}
               
for(var i = 0; i < selector.length; i++){
                       
if(rs[selector[i]]){
                               
return rs[selector[i]];
                       
}
               
}
               
return null;
       
},
       
       
       
/**
    * Updates a rule property
    * @param {String/Array} 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
    * @param {String} value The new value for the property
    * @return {Boolean} true If a rule was found and updated
    */

   updateRule
: function(selector, property, value){
               
if(!Ext.isArray(selector)){
                       
var rule = this.getRule(selector);
                       
if(rule){
                                rule
.style[property.replace(camelRe, camelFn)] = value;
                               
return true;
                       
}
               
}else{
                       
for(var i = 0; i < selector.length; i++){
                               
if(this.updateRule(selector[i], property, value)){
                                       
return true;
                               
}
                       
}
               
}
               
return false;
       
}
   
};  
}();