/*******************************************************************************
 * This notice must be untouched at all times.
 * 
 * CSS Sandpaper: smooths out differences between CSS implementations.
 * 
 * This javascript library contains routines to implement the CSS transform,
 * box-shadow and gradient in IE.  It also provides a common syntax for other
 * browsers that support vendor-specific methods.
 * 
 * Written by: Zoltan Hawryluk. Version 1.0 beta 1 completed on March 8, 2010.
 * 
 * Some routines are based on code from CSS Gradients via Canvas v1.2 
 * by Weston Ruter <http://weston.ruter.net/projects/css-gradients-via-canvas/>
 * 
 * Requires sylvester.js by James Coglan http://sylvester.jcoglan.com/  
 *
 * cssSandpaper.js v.1.0 beta 1 available at http://www.useragentman.com/
 * 
 * released under the MIT License: 
 *   http://www.opensource.org/licenses/mit-license.php
 * 
 ******************************************************************************/


if (!document.querySelectorAll) {
    document.querySelectorAll = cssQuery;
}


var cssSandpaper = new function(){
    var me = this;
    
    var styleNodes, styleSheets = new Array();
    
    var ruleSetRe = /[^\{]*{[^\}]*}/g;
    var ruleSplitRe = /[\{\}]/g;
    
    var reGradient = /gradient\([\s\S]*\)/g;
    
    // This regexp from the article 
    http://james.padolsey.com/javascript/javascript-comment-removal-revisted/
 	var reMultiLineComment = /\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g;
	
	var reAtRule = /@[^\{\};]*;|@[^\{\};]*\{[^\}]*\}/g;
    
	var reFunctionSpaces = /\(\s*/g
    
    
    var ruleLists = new Array();
    var styleNode;
    
    var tempObj;
    var body;
	
    
    me.init = function(){
        if (EventHelpers.hasPageLoadHappened(arguments)) {
            return;
        }
        body = document.body;
        
        tempObj = document.createElement('div');
        
        getStyleSheets();
        
        indexRules();
        
        
        fixTransforms();
        fixBoxShadow();
		 
        fixLinearGradients();
		
        fixOpacity();
		
		setClasses();
        //fixBorderRadius();
    
    }
	
	me.setOpacity = function (obj, value){
		var property = CSS3Helpers.findProperty(document.body, 'opacity');
        
		if (property=="filter") {
			// IE must have layout, see 
			// http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
			// for details.
			obj.style.zoom="100%";
			
            var filter = CSS3Helpers.addFilter(obj, 'DXImageTransform.Microsoft.Alpha', 
				StringHelpers.sprintf(
					"opacity=%d", ((value)*100)));
					
			filter.opacity = value * 100;
		
			
        } else if (obj.style[property] != null) {
            obj.style[property] = value;
        }
	}
	
	
    function fixOpacity(){
   
        var transformRules = getRuleList('opacity').values;
       
        for (var i in transformRules) {
            var rule = transformRules[i];
            var nodes = document.querySelectorAll(rule.selector);
			
			for (var j = 0; j < nodes.length; j++) {	
				me.setOpacity(nodes[j], rule.value)
			}
			
        }
       
    }
	
	
	
	me.setTransform = function (obj, transformString) {
        var property = CSS3Helpers.findProperty(obj, 'transform');
        
        if (property=="filter") {
            var matrix = CSS3Helpers.getTransformationMatrix(transformString);
            CSS3Helpers.setMatrixFilter(obj, matrix)
        } else if (obj.style[property] != null) {
            obj.style[property] = transformString;
        }
	}
    
    function fixTransforms(){
    
        var transformRules = getRuleList('-sand-transform').values;
        var property = CSS3Helpers.findProperty(document.body, 'transform');
        
       
        for (var i in transformRules) {
            var rule = transformRules[i];
            var nodes = document.querySelectorAll(rule.selector);
			
			for (var j = 0; j < nodes.length; j++) {	
				me.setTransform(nodes[j], rule.value)
			}
			
        }
       
    }
	
    me.setBoxShadow = function(obj, value){
		var property = CSS3Helpers.findProperty(obj, 'boxShadow');
		
		var values = CSS3Helpers.getBoxShadowValues(value);
		
        if (property == "filter") {
            var filter = CSS3Helpers.addFilter(obj, 'DXImageTransform.Microsoft.DropShadow', 
				StringHelpers.sprintf(
					"color=%s,offX=%d,offY=%d", values.color, values.offsetX, values.offsetY));
			filter.color = values.color;
			filter.offX = values.offsetX;
			filter.offY = values.offsetY;
			
        } else if (obj.style[property] != null) {
            obj.style[property] = value;
        }
    }
    
    function fixBoxShadow(){
    
        var transformRules = getRuleList('-sand-box-shadow').values;
        
        //var matrices = new Array();
        
        
        for (var i in transformRules) {
            var rule = transformRules[i];
            
            var nodes = document.querySelectorAll(rule.selector);
            
            
            
            for (var j = 0; j < nodes.length; j++) {
                me.setBoxShadow(nodes[j], rule.value)
                    
                }
           
        }
        
    }
	
	me.setGradient = function(node, value){
    
        var support = CSS3Helpers.reportGradientSupport();
        
        var values = CSS3Helpers.getGradient(value);
        
        if (values == null) {
            return;
        }
        
        if (node.filters) {
            if (values.colorStops.length == 2 &&
            values.colorStops[0].stop == 0.0 &&
            values.colorStops[1].stop == 1.0) {
				var startColor = new RGBColor(values.colorStops[0].color);
				var endColor =  new RGBColor(values.colorStops[1].color);
				
				startColor = startColor.toHex();
				endColor = endColor.toHex();
				//jslog.debug(StringHelpers.sprintf('endColor before: %s, after: %s' , values.colorStops[1].color, endColor));
				
				
                var filter = CSS3Helpers.addFilter(node, 'DXImageTransform.Microsoft.Gradient', StringHelpers.sprintf("GradientType = %s, StartColorStr = '%s', EndColorStr = '%s'", values.IEdir, startColor, endColor));
				
				filter.GradientType = values.IEdir;
				filter.StartColorStr = startColor;
				filter.EndColorStr = endColor;
                
            }
        } else if (support == implementation.MOZILLA) {
            node.style.backgroundImage = StringHelpers.sprintf('-moz-gradient( %s, %s, from(%s), to(%s))', values.dirBegin, values.dirEnd, values.colorStops[0].color, values.colorStops[1].color);
        } else if (support == implementation.WEBKIT) {
            var tmp = StringHelpers.sprintf('-webkit-gradient(%s, %s, %s %s, %s %s)', values.type, values.dirBegin, values.r0 ? values.r0 + ", " : "", values.dirEnd, values.r1 ? values.r1 + ", " : "", listColorStops(values.colorStops));
            node.style.backgroundImage = tmp;
        } else if (support == implementation.WORKAROUND) {
            try {
                CSS3Helpers.applyCanvasGradient(node, values);
            } 
            catch (ex) {
                // do nothing (for now).
            }
        }
    }
    
    
    function fixLinearGradients(){
    
        var backgroundRules = getRuleList('background').values.concat(getRuleList('background-image').values);
        //var matrices = new Array();
        var support = CSS3Helpers.reportGradientSupport();
        for (var i in backgroundRules) {
            var rule = backgroundRules[i];
            var nodes = document.querySelectorAll(rule.selector);    
            for (var j = 0; j < nodes.length; j++) {
                me.setGradient(nodes[j], rule.value)
            }
        }
    }
    
    function listColorStops(colorStops){
        var sb = new StringBuffer();
        
        for (var i = 0; i < colorStops.length; i++) {
            sb.append(StringHelpers.sprintf("color-stop(%s, %s)", colorStops[i].stop, colorStops[i].color));
            if (i < colorStops.length - 1) {
                sb.append(', ');
            }
        }
        
        return sb.toString();
    }
    
    
    function getStyleSheet(node){
        var sheetCssText;
        switch (node.nodeName.toLowerCase()) {
            case 'style':
                sheetCssText = StringHelpers.uncommentHTML(node.innerHTML); //does not work with inline styles because IE doesn't allow you to get the text content of a STYLE element
                
                break;
            case 'link':
				
				var xhr = XMLHelpers.getXMLHttpRequest(node.href, null, "GET", null, false);
				sheetCssText = xhr.responseText;
                
                break;
        }
        
        sheetCssText = sheetCssText.replace(reMultiLineComment, '').replace(reAtRule, '');
        
        return sheetCssText;
    }
    
    function getStyleSheets(){
		 
        styleNodes = document.querySelectorAll('style, link[rel="stylesheet"]');
        
        for (var i = 0; i < styleNodes.length; i++) {
			if (!CSSHelpers.isMemberOfClass(styleNodes[i], 'cssSandpaper-noIndex')) {
				styleSheets.push(getStyleSheet(styleNodes[i]))
			}
        }
    }
    
    function indexRules(){
    
        for (var i = 0; i < styleSheets.length; i++) {
            var sheet = styleSheets[i];
            
            rules = sheet.match(ruleSetRe);
            if (rules) {
				for (var j = 0; j < rules.length; j++) {
					var parsedRule = rules[j].split(ruleSplitRe);
					var selector = parsedRule[0].trim();
					var propertiesStr = parsedRule[1];
					var properties = propertiesStr.split(';');
					for (var k = 0; k < properties.length; k++) {
						if (properties[k].trim() != '') {
							var splitProperty = properties[k].split(':')
							var name = splitProperty[0].trim().toLowerCase();
							var value = splitProperty[1];
							if (!ruleLists[name]) {
								ruleLists[name] = new RuleList(name);
							}
							
							if (value) {
								ruleLists[name].add(selector, value.trim());
							}
						}
					}
				}
			}
        }
		
    }
    
    function getRuleList(name){
        var list = ruleLists[name];
        if (!list) {
            list = new RuleList(name);
        }
        return list;
    }
    
    function setClasses() {
		
		
		var htmlNode = document.getElementsByTagName('html')[0];
		var properties = ['transform', 'opacity'];
		
		for (var i=0; i<properties.length; i++) {
			var prop = properties[i];
			if (CSS3Helpers.supports(prop)){
				CSSHelpers.addClass(htmlNode, 'cssSandpaper-' + prop);
			} 
		}
	}
}

function RuleList(propertyName){
    var me = this;
    me.values = new Array();
    me.propertyName = propertyName;
    me.add = function(selector, value){
        me.values.push(new CSSRule(selector, me.propertyName, value));
    }
}

function CSSRule(selector, name, value){
    var me = this;
    me.selector = selector;
    me.name = name;
    me.value = value;
    
    me.toString = function(){
        return StringHelpers.sprintf("%s { %s: %s}", me.selector, me.name, me.value);
    }
}

var MatrixGenerator = new function(){
    var me = this;
    var reUnit = /[a-z]+$/;
    me.identity = $M([[1, 0, 0], [0, 1, 0], [0, 0, 1]]);
    
    
    function degreesToRadians(degrees){
        return (degrees - 360) * Math.PI / 180;
    }
    
    function getRadianScalar(angleStr){
        var num = parseFloat(angleStr);
        var unit = angleStr.match(reUnit);
        
        if (unit.length != 1 || num == 0) {
            return 0;
        }
		
        
        unit = unit[0];
        
        
        var rad;
        switch (unit) {
            case "deg":
                rad = degreesToRadians(num);
                break;
            case "rad":
                rad = num;
                break;
            default:
                return me.identity;
        }
        return rad;
    }
	
	me.prettyPrint = function (m) {
		return StringHelpers.sprintf('| %s %s %s | - | %s %s %s | - |%s %s %s|', 
			m.e(1,1), m.e(1,2), m.e(1,3),
			m.e(2,1), m.e(2,2), m.e(2,3),
			m.e(3,1), m.e(3,2), m.e(3,3))
	}
    
    me.rotate = function(angleStr){
        var num = getRadianScalar(angleStr);
        return Matrix.RotationZ(num);
    }
    
    me.scale = function(sx, sy){
        sx = parseFloat(sx)
        
        if (!sy) {
            sy = sx;
        } else {
            sy = parseFloat(sy)
        }
        
        
        return $M([[sx, 0, 0], [0, sy, 0], [0, 0, 1]]);
    }
    
    me.scaleX = function(sx){
        return me.scale(sx, 1);
    }
    
    me.scaleY = function(sy){
        return me.scale(1, sy);
    }
    
    me.skew = function(ax, ay){
        var xRad = getRadianScalar(ax);
        var yRad;
        
        if (ay != null) {
            yRad = getRadianScalar(ay)
        } else {
			
            yRad = xRad
        }
        return $M([[1, Math.tan(xRad), 0], [Math.tan(yRad), 1, 0], [0, 0, 1]]);
    }
    
    me.skewX = function(ax){
        return me.skew(ax, 0);
    }
    
    me.skewY = function(ay){
        return me.skew(0, ay);
    }
	
	me.translate = function (tx, ty) {
		
		var TX = parseInt(tx);
		var TY = parseInt(ty)
		
		//jslog.debug(StringHelpers.sprintf('translate %f %f', TX, TY));
		
		return $M([[1, 0, TX], [0, 1, TY], [0, 0, 1]]);
	}
	
	me.translateX = function (tx) {
		return me.translate(tx, 0);
	}
    
	me.translateY = function (ty) {
		return me.translate(0, ty);
	}
    
    
    me.matrix = function(a, b, c, d, e, f){
    
        // for now, e and f are ignored
        return $M([[a, c, e], [b, d, f], [0, 0, 1]])
    }
}

var CSS3Helpers = new function(){
    var me = this;
    
    
    var reTransformListSplitter = /[a-z]+\([^\)]*\)\s*/g;
    
    var reLeftBracket = /\(/g;
    var reRightBracket = /\)/g;
    var reComma = /,/g;
    
    var reSpaces = /\s+/g
    
    var reFilterNameSplitter = /progid:([^\(]*)/g;
    
    var reLinearGradient
    
    var canvas;
	
	var cache = new Array();
    
	
	me.supports = function (cssProperty) {
		if (CSS3Helpers.findProperty(document.body, cssProperty) != null) {
			return true;
		} else {
			return false;
		}
	}	
	
    me.getCanvas = function(){
    
        if (canvas) {
            return canvas;
        } else {
            canvas = document.createElement('canvas');
            return canvas;
        }
    }
    
    me.getTransformationMatrix = function(CSS3TransformProperty){
		
        var transforms = CSS3TransformProperty.match(reTransformListSplitter);
        var resultantMatrix = MatrixGenerator.identity;
        
        for (var j = 0; j < transforms.length; j++) {
        
            var transform = transforms[j];
            transform = transform.replace(reLeftBracket, '("').replace(reComma, '", "').replace(reRightBracket, '")');
            
            
            try {
                var matrix = eval('MatrixGenerator.' + transform);
				//jslog.debug( transform + ': ' + MatrixGenerator.prettyPrint(matrix))
                resultantMatrix = resultantMatrix.x(matrix);
            } 
            catch (ex) {
                // do nothing;
            }
        }
		
        return resultantMatrix;
        
    }
    
    me.getBoxShadowValues = function(propertyValue){
        var r = new Object();
        
        var values = propertyValue.split(reSpaces);
        
        if (values[0] == 'inset') {
            r.inset = true;
            values = values.reverse().pop().reverse();
        } else {
            r.inset = false;
        }
        
        r.offsetX = parseInt(values[0]);
        r.offsetY = parseInt(values[1]);
        
        if (values.length > 3) {
            r.blurRadius = values[2];
            
            if (values.length > 4) {
                r.spreadRadius = values[3]
            }
        }
        
        r.color = values[values.length - 1];
        
        return r;
    }
    
    me.getGradient = function(propertyValue){
        var r = new Object();
        r.colorStops = new Array();
        
        
        var substring = me.getBracketedSubstring(propertyValue, '-sand-gradient');
        if (substring == undefined) {
            return null;
        }
        var parameters = substring.match(/[^\(,]+(\([^\)]*\))?[^,]*/g); //substring.split(reComma);
        r.type = parameters[0].trim();
        
        if (r.type == 'linear') {
            r.dirBegin = parameters[1].trim();
            r.dirEnd = parameters[2].trim();
            var beginCoord = r.dirBegin.split(reSpaces);
            var endCoord = r.dirEnd.split(reSpaces);
            
            for (var i = 3; i < parameters.length; i++) {
                r.colorStops.push(parseColorStop(parameters[i].trim(), i - 3));
            }
            
            
            
            
            /* The following logic only applies to IE */
            if (document.body.filters) {
                if (r.x0 == r.x1) {
                    /* IE only supports "center top", "center bottom", "top left" and "top right" */
                    
                    switch (beginCoord[1]) {
                        case 'top':
                            r.IEdir = 0;
                            break;
                        case 'bottom':
                            swapIndices(r.colorStops, 0, 1);
                            r.IEdir = 0;
                            /* r.from = parameters[4].trim();
                         r.to = parameters[3].trim(); */
                            break;
                    }
                }
                
                if (r.y0 == r.y1) {
                    switch (beginCoord[0]) {
                        case 'left':
                            r.IEdir = 1;
                            break;
                        case 'right':
                            r.IEdir = 1;
                            swapIndices(r.colorStops, 0, 1);
                            
                            break;
                    }
                }
            }
        } else {
        
            // don't even bother with IE
            if (document.body.filters) {
                return null;
            }
            
            
            r.dirBegin = parameters[1].trim();
            r.r0 = parameters[2].trim();
            
            r.dirEnd = parameters[3].trim();
            r.r1 = parameters[4].trim();
            
            var beginCoord = r.dirBegin.split(reSpaces);
            var endCoord = r.dirEnd.split(reSpaces);
            
            for (var i = 5; i < parameters.length; i++) {
                r.colorStops.push(parseColorStop(parameters[i].trim(), i - 5));
            }
            
        }
        
        
        r.x0 = beginCoord[0];
        r.y0 = beginCoord[1];
        
        r.x1 = endCoord[0];
        r.y1 = endCoord[1];
        
        return r;
    }
    
    function swapIndices(array, index1, index2){
        var tmp = array[index1];
        array[index1] = array[index2];
        array[index2] = tmp;
    }
    
    function parseColorStop(colorStop, index){
        var r = new Object();
        var substring = me.getBracketedSubstring(colorStop, 'color-stop');
        var from = me.getBracketedSubstring(colorStop, 'from');
        var to = me.getBracketedSubstring(colorStop, 'to');
        
        
        if (substring) {
            //color-stop
            var parameters = substring.split(',')
            r.stop = normalizePercentage(parameters[0].trim());
            r.color = parameters[1].trim();
        } else if (from) {
            r.stop = 0.0;
            r.color = from.trim();
        } else if (to) {
            r.stop = 1.0;
            r.color = to.trim();
        } else {
            if (index <= 1) {
                r.color = colorStop;
                if (index == 0) {
                    r.stop = 0.0;
                } else {
                    r.stop = 1.0;
                }
            } else {
                throw (StringHelpers.sprintf('invalid argument "%s"', colorStop));
            }
        }
        return r;
    }
    
    function normalizePercentage(s){
        if (s.substring(s.length - 1, s.length) == '%') {
            return parseFloat(s) / 100 + "";
        } else {
            return s;
        }
        
    }
    
    me.reportGradientSupport = function(){
		
		if (!cache["gradientSupport"]) {
			var r;
			var div = document.createElement('div');
			div.style.cssText = "background-image:-webkit-gradient(linear, 0% 0%, 0% 100%, from(red), to(blue));";
			
			if (div.style.backgroundImage) {
				r = implementation.WEBKIT;
				
			} else {
			
				/* div.style.cssText = "background-image:-moz-linear-gradient(top, blue, white 80%, orange);";
				
				if (div.style.backgroundImage) {
					
					r = implementation.MOZILLA;
					
				} else { */
				
					var canvas = CSS3Helpers.getCanvas();
					if (canvas.getContext && canvas.toDataURL) {
						r = implementation.WORKAROUND;
						
					} else {
						r = implementation.NONE;
					}
				/* } */
			}
			
			cache["gradientSupport"] = r;
		}
		
		
		return cache["gradientSupport"];
		
        
        
        
    }
    
    me.getBracketedSubstring = function(s, header){
        var gradientIndex = s.indexOf(header + '(')
        
        if (gradientIndex != -1) {
            var substring = s.substring(gradientIndex);
            
            var openBrackets = 1;
            for (var i = header.length + 1; i < 100 || i < substring.length; i++) {
                var c = substring.substring(i, i + 1);
                switch (c) {
                    case "(":
                        openBrackets++;
                        break;
                    case ")":
                        openBrackets--;
                        break;
                }
                
                if (openBrackets == 0) {
                    break;
                }
                
            }
            
            return substring.substring(gradientIndex + header.length + 1, i);
        }
        
        
    }
    
    
    me.setMatrixFilter = function(obj, matrix){
    
    
        if (!hasIETransformWorkaround(obj)) {
            addIETransformWorkaround(obj)
        }
        
        var container = obj.parentNode;
        //container.xTransform = degrees;
        
        
        filter = obj.filters.item('DXImageTransform.Microsoft.Matrix');
        //jslog.debug(MatrixGenerator.prettyPrint(matrix))
        filter.M11 = matrix.e(1, 1);
        filter.M12 = matrix.e(1, 2);
        filter.M21 = matrix.e(2, 1);
        filter.M22 = matrix.e(2, 2);
        
        // Now, adjust the margins of the parent object
        var originalWidth = parseFloat(container.xOriginalWidth);
        var originalHeight = parseFloat(container.xOriginalHeight);
        
        var offset;
        if (obj.currentStyle.display == 'inline') {
            offset = 0;
        } else {
            offset = 13;  // This works ... don't know why.
        }
        
        container.style.marginLeft = (((originalWidth - obj.offsetWidth) / 2) - offset + matrix.e(1,3)) + 'px'
        container.style.marginTop = (((originalHeight - obj.offsetHeight) / 2) - offset + matrix.e(2,3)) + 'px'
        
    }
    
    function hasIETransformWorkaround(obj){
    
        return CSSHelpers.isMemberOfClass(obj.parentNode, 'IETransformContainer');
    }
    
    function addIETransformWorkaround(obj){
        if (!hasIETransformWorkaround(obj)) {
            var parentNode = obj.parentNode;
            var filter;
            
            // This is the container to offset the strange rotation behavior
            var container = document.createElement('div');
            CSSHelpers.addClass(container, 'IETransformContainer');
            
            
            container.style.width = obj.offsetWidth + 'px';
            container.style.height = obj.offsetHeight + 'px';
            container.xOriginalWidth = obj.offsetWidth;
            container.xOriginalHeight = obj.offsetHeight;
            container.style.position = 'absolute'
            container.style.zIndex = obj.currentStyle.zIndex;
		
            
            var horizPaddingFactor = 0; //parseInt(obj.currentStyle.paddingLeft); 
            var vertPaddingFactor = 0; //parseInt(obj.currentStyle.paddingTop);
            if (obj.currentStyle.display == 'block') {
                container.style.left = obj.offsetLeft + 13 - horizPaddingFactor + "px";
                container.style.top = obj.offsetTop + 13 + -vertPaddingFactor + 'px';
            } else {
                container.style.left = obj.offsetLeft + "px";
                container.style.top = obj.offsetTop + 'px';
                
            }
            //container.style.float = obj.currentStyle.float;
            
            
            obj.style.top = "auto";
            obj.style.left = "auto"
            obj.style.bottom = "auto";
            obj.style.right = "auto";
            // This is what we need in order to insert to keep the document
            // flow ok
            var replacement = obj.cloneNode(true);
            replacement.style.visibility = 'hidden';
            
            obj.replaceNode(replacement);
            
            // now, wrap container around the original node ... 
            
            obj.style.position = 'absolute';
            container.appendChild(obj);
            parentNode.insertBefore(container, replacement);
            container.style.backgroundColor = 'transparent';
            
            container.style.padding = '0';
            
            filter = me.addFilter(obj, 'DXImageTransform.Microsoft.Matrix', "sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1")
            var bgImage = obj.currentStyle.backgroundImage.split("\"")[1];
			/*
			if (bgImage) {
				var alphaFilter = me.addFilter(obj, "DXImageTransform.Microsoft.AlphaImageLoader", "src='" + bgImage + "', sizingMethod='scale'");
				alert(bgImage)
				alphaFilter.src = bgImage;
				sizingMethod = 'scale';
				obj.style.background = 'none';
				obj.style.backgroundImage = 'none';
			}
				*/		
        }
        
    }
    
    me.addFilter = function(obj, filterName, filterValue){
        // now ... insert the filter so we can exploit its wonders
        
        var filter;
        try {
            filter = obj.filters.item(filterName);
        } 
        catch (ex) {
            // dang! We have to go through all of them and make sure filter
            // is set right before we add the new one.
            
            
            var filterList = new MSFilterList(obj)
            
            filterList.fixFilterStyle();
           
            var comma = ", ";
            
            if (obj.filters.length == 0) {
                comma = "";
            }
            
            obj.style.filter += StringHelpers.sprintf("%sprogid:%s(%s)", comma, filterName, filterValue);
            
            filter = obj.filters.item(filterName);
            
        }
        
        return filter;
    }
    
    
    function degreesToRadians(degrees){
        return (degrees - 360) * Math.PI / 180;
    }
    
    me.findProperty = function (obj, type){
		capType = type.capitalize();
		
		var r = cache[type]
		if (!r) {
			
		
			var style = obj.style;
			
			
			var properties = [type, 'Moz' + capType, 'Webkit' + capType, 'O' + capType, 'filter'];
			for (var i = 0; i < properties.length; i++) {
				if (style[properties[i]] != null) {
					r = properties[i];
					break;
				}
			}
			
			if (r == 'filter' && document.body.filters == undefined) {
				r = null;
			}
			 cache[type] = r;
		} 
        return r;
    }
    
    /*
     * "A point is a pair of space-separated values. The syntax supports numbers,
     *  percentages or the keywords top, bottom, left and right for point values."
     *  This keywords and percentages into pixel equivalents
     */
    me.parseCoordinate = function(value, max){
        //Convert keywords
        switch (value) {
            case 'top':
            case 'left':
                return 0;
            case 'bottom':
            case 'right':
                return max;
            case 'center':
                return max / 2;
        }
        
        //Convert percentage
        if (value.indexOf('%') != -1) 
            value = parseFloat(value.substr(0, value.length - 1)) / 100 * max;
        //Convert bare number (a pixel value)
        else 
            value = parseFloat(value);
        if (isNaN(value)) 
            throw Error("Unable to parse coordinate: " + value);
        return value;
    }
    
    me.applyCanvasGradient = function(el, gradient){
    
        var canvas = me.getCanvas();
        var computedStyle = document.defaultView.getComputedStyle(el, null);
        
        canvas.width = parseInt(computedStyle.width) + parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight) + 1; // inserted by Zoltan
        canvas.height = parseInt(computedStyle.height) + parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom) + 2; // 1 inserted by Zoltan
        var ctx = canvas.getContext('2d');
        
        //Iterate over the gradients and build them up
        
        var canvasGradient;
        // Linear gradient
        if (gradient.type == 'linear') {
        
        
            canvasGradient = ctx.createLinearGradient(me.parseCoordinate(gradient.x0, canvas.width), me.parseCoordinate(gradient.y0, canvas.height), me.parseCoordinate(gradient.x1, canvas.width), me.parseCoordinate(gradient.y1, canvas.height));
        } // Radial gradient
 else /*if(gradient.type == 'radial')*/ {
            canvasGradient = ctx.createRadialGradient(me.parseCoordinate(gradient.x0, canvas.width), me.parseCoordinate(gradient.y0, canvas.height), gradient.r0, me.parseCoordinate(gradient.x1, canvas.width), me.parseCoordinate(gradient.y1, canvas.height), gradient.r1);
        }
        
        //Add each of the color stops to the gradient
        for (var i = 0; i < gradient.colorStops.length; i++) {
            var cs = gradient.colorStops[i];
            
            canvasGradient.addColorStop(cs.stop, cs.color);
        };
        
        //Paint the gradient
        ctx.fillStyle = canvasGradient;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        
        //Apply the gradient to the selectedElement
        el.style.backgroundImage = "url('" + canvas.toDataURL() + "')";
        
    }
    
}

function MSFilterList(node){
    var me = this;
    
    me.list = new Array();
    me.node = node;
    
    var reFilterListSplitter = /[\s\S]*\([\s\S]*\)/g;
    
    var styleAttr = node.style;
    
    function init(){
    
        var filterCalls = styleAttr.filter.match(reFilterListSplitter);
        
        if (filterCalls != null) {
        
            for (var i = 0; i < filterCalls.length; i++) {
                var call = filterCalls[i];
                
                me.list.push(new MSFilter(node, call));
                
            }
        }
        
        
    }
    
    me.toString = function(){
        var sb = new StringBuffer();
        
        for (var i = 0; i < me.list.length; i++) {
        
            sb.append(me.list[i].toString());
            if (i < me.list.length - 1) {
                sb.append(',')
            }
        }
        return sb.toString();
    }
    
    
    me.fixFilterStyle = function(){
    
        try {
            me.node.style.filter = me.toString();
        } 
        catch (ex) {
            // do nothing.
        }
        
    }
    
    init();
}

function MSFilter(node, filterCall){
    var me = this;
    
    me.node = node;
    me.filterCall = filterCall;
    
    var reFilterNameSplitter = /progid:([^\(]*)/g;
    var reParameterName = /([a-zA-Z0-9]+\s*)=/g;
    
    
    function init(){
        me.name = me.filterCall.match(reFilterNameSplitter)[0].replace('progid:', '');
        
        //This may not be the best way to do this.
        var parameterString = filterCall.split('(')[1].replace(')', '');
        me.parameters = parameterString.match(reParameterName);
		
        for (var i = 0; i < me.parameters.length; i++) {
            me.parameters[i] = me.parameters[i].replace('=', '');
        }
        
    }
    
    me.toString = function(){
    
        var sb = new StringBuffer();
        
        sb.append(StringHelpers.sprintf('progid:%s(', me.name));
        
        for (var i = 0; i < me.parameters.length; i++) {
            var param = me.parameters[i];
            var filterObj = me.node.filters.item(me.name);
            var paramValue = filterObj[param];
            if (typeof(paramValue) == 'string') {
                sb.append(StringHelpers.sprintf('%s="%s"', param, filterObj[param]));
            } else {
                sb.append(StringHelpers.sprintf('%s=%s', param, filterObj[param]));
            }
            
            if (i != me.parameters.length - 1) {
                sb.append(', ')
            }
        }
        sb.append(')');
        
        return sb.toString();
    }
    
    
    
    init();
}

var implementation = new function(){
    this.NONE = 0;
    this.MOZILLA = 1;
    this.WEBKIT = 2;
    this.IE = 3;
    this.OPERA = 4;
    this.WORKAROUND = 5;
}

/*
 * Extra helper routines
 */


var StringHelpers = new function () {
	var me = this;
	
	// used by the String.prototype.trim()			
	me.initWhitespaceRe = /^\s\s*/;
	me.endWhitespaceRe = /\s\s*$/;
	me.whitespaceRe = /\s/;
	
	/*******************************************************************************
	 * Function sprintf(format_string,arguments...) Javascript emulation of the C
	 * printf function (modifiers and argument types "p" and "n" are not supported
	 * due to language restrictions)
	 * 
	 * Copyright 2003 K&L Productions. All rights reserved
	 * http://www.klproductions.com
	 * 
	 * Terms of use: This function can be used free of charge IF this header is not
	 * modified and remains with the function code.
	 * 
	 * Legal: Use this code at your own risk. K&L Productions assumes NO
	 * resposibility for anything.
	 ******************************************************************************/
	me.sprintf = function (fstring)
	  { var pad = function(str,ch,len)
	      { var ps='';
	        for(var i=0; i<Math.abs(len); i++) ps+=ch;
	        return len>0?str+ps:ps+str;
	      }
	    var processFlags = function(flags,width,rs,arg)
	      { var pn = function(flags,arg,rs)
	          { if(arg>=0)
	              { if(flags.indexOf(' ')>=0) rs = ' ' + rs;
	                else if(flags.indexOf('+')>=0) rs = '+' + rs;
	              }
	            else
	                rs = '-' + rs;
	            return rs;
	          }
	        var iWidth = parseInt(width,10);
	        if(width.charAt(0) == '0')
	          { var ec=0;
	            if(flags.indexOf(' ')>=0 || flags.indexOf('+')>=0) ec++;
	            if(rs.length<(iWidth-ec)) rs = pad(rs,'0',rs.length-(iWidth-ec));
	            return pn(flags,arg,rs);
	          }
	        rs = pn(flags,arg,rs);
	        if(rs.length<iWidth)
	          { if(flags.indexOf('-')<0) rs = pad(rs,' ',rs.length-iWidth);
	            else rs = pad(rs,' ',iWidth - rs.length);
	          }    
	        return rs;
	      }
	    var converters = new Array();
	    converters['c'] = function(flags,width,precision,arg)
	      { if(typeof(arg) == 'number') return String.fromCharCode(arg);
	        if(typeof(arg) == 'string') return arg.charAt(0);
	        return '';
	      }
	    converters['d'] = function(flags,width,precision,arg)
	      { return converters['i'](flags,width,precision,arg); 
	      }
	    converters['u'] = function(flags,width,precision,arg)
	      { return converters['i'](flags,width,precision,Math.abs(arg)); 
	      }
	    converters['i'] =  function(flags,width,precision,arg)
	      { var iPrecision=parseInt(precision);
	        var rs = ((Math.abs(arg)).toString().split('.'))[0];
	        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
	        return processFlags(flags,width,rs,arg); 
	      }
	    converters['E'] = function(flags,width,precision,arg) 
	      { return (converters['e'](flags,width,precision,arg)).toUpperCase();
	      }
	    converters['e'] =  function(flags,width,precision,arg)
	      { iPrecision = parseInt(precision);
	        if(isNaN(iPrecision)) iPrecision = 6;
	        rs = (Math.abs(arg)).toExponential(iPrecision);
	        if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs.replace(/^(.*)(e.*)$/,'$1.$2');
	        return processFlags(flags,width,rs,arg);        
	      }
	    converters['f'] = function(flags,width,precision,arg)
	      { iPrecision = parseInt(precision);
	        if(isNaN(iPrecision)) iPrecision = 6;
	        rs = (Math.abs(arg)).toFixed(iPrecision);
	        if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs + '.';
	        return processFlags(flags,width,rs,arg);
	      }
	    converters['G'] = function(flags,width,precision,arg)
	      { return (converters['g'](flags,width,precision,arg)).toUpperCase();
	      }
	    converters['g'] = function(flags,width,precision,arg)
	      { iPrecision = parseInt(precision);
	        absArg = Math.abs(arg);
	        rse = absArg.toExponential();
	        rsf = absArg.toFixed(6);
	        if(!isNaN(iPrecision))
	          { rsep = absArg.toExponential(iPrecision);
	            rse = rsep.length < rse.length ? rsep : rse;
	            rsfp = absArg.toFixed(iPrecision);
	            rsf = rsfp.length < rsf.length ? rsfp : rsf;
	          }
	        if(rse.indexOf('.')<0 && flags.indexOf('#')>=0) rse = rse.replace(/^(.*)(e.*)$/,'$1.$2');
	        if(rsf.indexOf('.')<0 && flags.indexOf('#')>=0) rsf = rsf + '.';
	        rs = rse.length<rsf.length ? rse : rsf;
	        return processFlags(flags,width,rs,arg);        
	      }  
	    converters['o'] = function(flags,width,precision,arg)
	      { var iPrecision=parseInt(precision);
	        var rs = Math.round(Math.abs(arg)).toString(8);
	        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
	        if(flags.indexOf('#')>=0) rs='0'+rs;
	        return processFlags(flags,width,rs,arg); 
	      }
	    converters['X'] = function(flags,width,precision,arg)
	      { return (converters['x'](flags,width,precision,arg)).toUpperCase();
	      }
	    converters['x'] = function(flags,width,precision,arg)
	      { var iPrecision=parseInt(precision);
	        arg = Math.abs(arg);
	        var rs = Math.round(arg).toString(16);
	        if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
	        if(flags.indexOf('#')>=0) rs='0x'+rs;
	        return processFlags(flags,width,rs,arg); 
	      }
	    converters['s'] = function(flags,width,precision,arg)
	      { var iPrecision=parseInt(precision);
	        var rs = arg;
	        if(rs.length > iPrecision) rs = rs.substring(0,iPrecision);
	        return processFlags(flags,width,rs,0);
	      }
	    farr = fstring.split('%');
	    retstr = farr[0];
	    fpRE = /^([-+ #]*)(\d*)\.?(\d*)([cdieEfFgGosuxX])(.*)$/;
	    for(var i=1; i<farr.length; i++)
	      { fps=fpRE.exec(farr[i]);
	        if(!fps) continue;
	        if(arguments[i]!=null) retstr+=converters[fps[4]](fps[1],fps[2],fps[3],arguments[i]);
	        retstr += fps[5];
	      }
	    return retstr;
	}
	
	/**
	 * Take out the first comment inside a block of HTML
	 * 
	 * @param {String} s - an HTML block
	 * @return {String} s - the HTML block uncommented.
	 */
	me.uncommentHTML = function(s) {
		if (s.indexOf('-->')!= -1 && s.indexOf('<!--') != -1) {
			return s.replace("<!--", "").replace("-->", "");
		} else {
			return s;
		}
	}
}

var XMLHelpers = new function() {
	
	var me = this;
	
	/**
	 * Wrapper for XMLHttpRequest Object.  Grabbing data (XML and/or text) from a URL.
	 * Grabbing data from a URL. Input is one parameter, url. It returns a request
	 * object. Based on code from
	 * http://www.xml.com/pub/a/2005/02/09/xml-http-request.html.  IE caching problem
	 * fix from Wikipedia article http://en.wikipedia.org/wiki/XMLHttpRequest
	 * 
	 * @param {String} url - the URL to retrieve
	 * @param {Function} processReqChange - the function/method to call at key events of the URL retrieval.
	 * @param {String} method - (optional) "GET" or "POST" (default "GET")
	 * @param {String} data - (optional) the CGI data to pass.  Default null.
	 * @param {boolean} isAsync - (optional) is this call asyncronous.  Default true.
	 * 
	 * @return {Object} a XML request object.
	 */
	me.getXMLHttpRequest = function (url, processReqChange) //, method, data, isAsync)
	{
		var argv = me.getXMLHttpRequest.arguments;
		var argc = me.getXMLHttpRequest.arguments.length;
		var httpMethod = (argc > 2) ? argv[2] : 'GET';
		var data = (argc > 3) ? argv[3] : "";
		var isAsync = (argc > 4) ? argv[4] : true;
		
		var req;
		// branch for native XMLHttpRequest object
		if (window.XMLHttpRequest) {
			req = new XMLHttpRequest();	
		// branch for IE/Windows ActiveX version
		} else if (window.ActiveXObject) {
			try {
				req = new ActiveXObject('Msxml2.XMLHTTP');
			} catch (ex) {
				req = new ActiveXObject("Microsoft.XMLHTTP");
			} 
			// the browser doesn't support XML HttpRequest. Return null;
		} else {
			return null;
		}
		
		if (isAsync) {
			req.onreadystatechange = processReqChange;
		}
		
		if (httpMethod == "GET" && data != "") {
			url += "?" + data;
		}
		
		req.open(httpMethod, url, isAsync);
		
		//Fixes IE Caching problem
		req.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" );
		req.send(data);
		
		return req;
	}
}

var CSSHelpers = new function () {
	var me = this;
	
	var blankRe = new RegExp('\\s');
	
	/**
	 * Determines if an HTML object is a member of a specific class.
	 * @param {Object} obj - an HTML object.
	 * @param {Object} className - the CSS class name.
	 */
	me.isMemberOfClass = function (obj, className) {
		
		if (blankRe.test(className))
			return false;
		
		var re = new RegExp(getClassReString(className) , "g");
	
		return (re.test(obj.className));
	
	
	}
	
	/**
	 * Make an HTML object be a member of a certain class.
	 * 
	 * @param {Object} obj - an HTML object
	 * @param {String} className - a CSS class name.
	 */
	me.addClass = function (obj, className) {
		if (blankRe.test(className)) {
			return;
		}
		
		// only add class if the object is not a member of it yet.
		if (!me.isMemberOfClass(obj, className)) {
			obj.className += " " + className;
		}
	}
	
	/**
	 * Make an HTML object *not* be a member of a certain class.
	 * 
	 * @param {Object} obj - an HTML object
	 * @param {Object} className - a CSS class name.
	 */
	me.removeClass = function (obj, className) {
	
		if (blankRe.test(className)) {
			return; 
		}
		
		
		var re = new RegExp(getClassReString(className) , "g");
		
		var oldClassName = obj.className;
	
	
		if (obj.className) {
			obj.className = oldClassName.replace(re, '');
		}
	
	
	}
	
	/**
	 * Generates a regular expression string that can be used to detect a class name
	 * in a tag's class attribute.  It is used by a few methods, so I 
	 * centralized it.
	 * 
	 * @param {String} className - a name of a CSS class.
	 */
	
	function getClassReString(className) {
		return '\\s'+className+'\\s|^' + className + '\\s|\\s' + className + '$|' + '^' + className +'$';
	}
	
	
}

/* 
 * Adding trim method to String Object.  Ideas from 
 * http://www.faqts.com/knowledge_base/view.phtml/aid/1678/fid/1 and
 * http://blog.stevenlevithan.com/archives/faster-trim-javascript
 */
String.prototype.trim = function() { 
	var str = this;
	
	// The first method is faster on long strings than the second and 
	// vice-versa.
	if (this.length > 6000) {
		str = this.replace(StringHelpers.initWhitespaceRe, '');
		var i = str.length;
		while (StringHelpers.whitespaceRe.test(str.charAt(--i)));
		return str.slice(0, i + 1);
	} else {
		return this.replace(StringHelpers.initWhitespaceRe, '')
			.replace(StringHelpers.endWhitespaceRe, '');
	}  
	
	
};

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/string/capitalize [v1.0]

String.prototype.capitalize = function(){ //v1.0
    
        return this.charAt(0).toUpperCase() + this.substr(1);
    
};


/*
*  stringBuffer.js - ideas from 
*  http://www.multitask.com.au/people/dion/archives/000354.html
*/

function StringBuffer() {
	var me = this;

	var buffer = []; 
	

	me.append = function(string)
	{
		buffer.push(string);
		return me;
	}
	
	me.appendBuffer = function(bufferToAppend) {
		buffer = buffer.concat(bufferToAppend);
	}
	
	me.toString = function()
	{
		return buffer.join("");
	}
	
	me.getLength = function() 
	{
		return buffer.length;
	}
	
	me.flush = function () 
	{
		buffer.length=0;
	}

}

/**
 * A class to parse color values
 * @author Stoyan Stefanov <sstoo@gmail.com> (with modifications)
 * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
 * @license Use it if you like it
 */
function RGBColor(color_string)
{
	
	var me = this;	
	
	

    me.ok = false;

    // strip any leading #
    if (color_string.charAt(0) == '#') { // remove # if any
        color_string = color_string.substr(1,6);
    }

    color_string = color_string.replace(/ /g,'');
    color_string = color_string.toLowerCase();

    // before getting into regexps, try simple matches
    // and overwrite the input
    var simple_colors = {
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dodgerblue: '1e90ff',
        feldspar: 'd19275',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred : 'cd5c5c',
        indigo : '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslateblue: '8470ff',
        lightslategray: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        metle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        violetred: 'd02090',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32'
    };
    for (var key in simple_colors) {
        if (color_string == key) {
            color_string = simple_colors[key];
        }
    }
    // emd of simple type-in colors

    // array of color definition objects
    var color_defs = [
        {
            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
            process: function (bits){
                return [
                    parseInt(bits[1]),
                    parseInt(bits[2]),
                    parseInt(bits[3])
                ];
            }
        },
        {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            example: ['#00ff00', '336699'],
            process: function (bits){
                return [
                    parseInt(bits[1], 16),
                    parseInt(bits[2], 16),
                    parseInt(bits[3], 16)
                ];
            }
        },
        {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            example: ['#fb0', 'f0f'],
            process: function (bits){
                return [
                    parseInt(bits[1] + bits[1], 16),
                    parseInt(bits[2] + bits[2], 16),
                    parseInt(bits[3] + bits[3], 16)
                ];
            }
        },
		{
            re: /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0{0,1}\.\d{1,}|0\.{0,}0*|1\.{0,}0*)\)$/,
            example: ['rgba(123, 234, 45, 22)', 'rgba(255, 234,245, 34)'],
            process: function (bits){
                return [
                    parseInt(bits[1]),
                    parseInt(bits[2]),
                    parseInt(bits[3]),
					parseFloat(bits[4])
                ];
            }
        }
    ];

    // search through the definitions to find a match
    for (var i = 0; i < color_defs.length; i++) {
        var re = color_defs[i].re;
        var processor = color_defs[i].process;
        var bits = re.exec(color_string);
        if (bits) {
            channels = processor(bits);
            me.r = channels[0];
            me.g = channels[1];
            me.b = channels[2];
			me.a = channels[3];
            me.ok = true;
        }

    }

    // validate/cleanup values
    me.r = (me.r < 0 || isNaN(me.r)) ? 0 : ((me.r > 255) ? 255 : me.r);
    me.g = (me.g < 0 || isNaN(me.g)) ? 0 : ((me.g > 255) ? 255 : me.g);
    me.b = (me.b < 0 || isNaN(me.b)) ? 0 : ((me.b > 255) ? 255 : me.b);
	
	
	
	me.a = (isNaN(me.a)) ? 1 : ((me.a > 255) ? 255 : (me.a < 0 )? 0: me.a);

	
	
    // some getters
    me.toRGB = function () {
        return 'rgb(' + me.r + ', ' + me.g + ', ' + me.b + ')';
    }
	
	// some getters
    me.toRGBA = function () {
        return 'rgba(' + me.r + ', ' + me.g + ', ' + me.b + ', ' + me.a + ')';
    }
	
	/**
	 * Converts an RGB color value to HSV. Conversion formula
	 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
	 * Assumes r, g, and b are contained in the set [0, 255] and
	 * returns h, s, and v in the set [0, 1].
	 * 
	 * This routine by Michael Jackson (not *that* one),
	 * from http://www.mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
	 *
	 * @param   Number  r       The red color value
	 * @param   Number  g       The green color value
	 * @param   Number  b       The blue color value
	 * @return  Array           The HSV representation
	 */
	me.toHSV = function (){
	    var r = me.r/255, g = me.g/255, b = me.b/255;
	    var max = Math.max(r, g, b), min = Math.min(r, g, b);
	    var h, s, v = max;
	
	    var d = max - min;
	    s = max == 0 ? 0 : d / max;
	
	    if(max == min){
	        h = 0; // achromatic
	    }else{
	        switch(max){
	            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
	            case g: h = (b - r) / d + 2; break;
	            case b: h = (r - g) / d + 4; break;
	        }
	        h /= 6;
	    }
	
	    return {
			h: h,
			s: s,
			v: v
		};
	}

	
    me.toHex = function () {
        var r = me.r.toString(16);
        var g = me.g.toString(16);
        var b = me.b.toString(16);
		
		var a = Math.floor((me.a * 255)).toString(16);
		
        if (r.length == 1) r = '0' + r;
        if (g.length == 1) g = '0' + g;
        if (b.length == 1) b = '0' + b;
		
		
		if (a == 'ff') {
			a = '';
		} else if (a.length == 1) {
			a = '0' + a;
		}
        return '#' + a + r + g + b;
    }

    

}



EventHelpers.addPageLoadEvent('cssSandpaper.init')


