/**
 * @class Ext.draw.Color
 * @extends Object
 *
 * Represents an RGB color and provides helper functions get
 * color components in HSL color space.
 */

Ext.define('Ext.draw.Color', {

   
/* Begin Definitions */

   
/* End Definitions */

    colorToHexRe
: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
    rgbRe
: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
    hexRe
: /\s*#([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)\s*/,

    /**
     * @cfg {Number} lightnessFactor
     *
     * The default factor to compute the lighter or darker color. Defaults to 0.2.
     */

    lightnessFactor
: 0.2,

    /**
     * @constructor
     * @param {Number} red Red component (0..255)
     * @param {Number} green Green component (0..255)
     * @param {Number} blue Blue component (0..255)
     */

    constructor
: function(red, green, blue) {
       
var me = this,
            clamp
= Ext.Number.constrain;
        me
.r = clamp(red, 0, 255);
        me
.g = clamp(green, 0, 255);
        me
.b = clamp(blue, 0, 255);
   
},

    /**
     * Get the red component of the color, in the range 0..255.
     * @return {Number}
     */

    getRed
: function() {
       
return this.r;
   
},

    /**
     * Get the green component of the color, in the range 0..255.
     * @return {Number}
     */

    getGreen
: function() {
       
return this.g;
   
},

    /**
     * Get the blue component of the color, in the range 0..255.
     * @return {Number}
     */

    getBlue
: function() {
       
return this.b;
   
},

    /**
     * Get the RGB values.
     * @return {Array}
     */

    getRGB
: function() {
       
var me = this;
       
return [me.r, me.g, me.b];
   
},

    /**
     * Get the equivalent HSL components of the color.
     * @return {Array}
     */

    getHSL
: function() {
       
var me = this,
            r
= me.r / 255,
            g
= me.g / 255,
            b
= me.b / 255,
            max
= Math.max(r, g, b),
            min
= Math.min(r, g, b),
            delta
= max - min,
            h
,
            s
= 0,
            l
= 0.5 * (max + min);

       
// min==max means achromatic (hue is undefined)
       
if (min != max) {
            s
= (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
           
if (r == max) {
                h
= 60 * (g - b) / delta;
           
} else if (g == max) {
                h
= 120 + 60 * (b - r) / delta;
           
} else {
                h
= 240 + 60 * (r - g) / delta;
           
}
           
if (h < 0) {
                h
+= 360;
           
}
           
if (h >= 360) {
                h
-= 360;
           
}
       
}
       
return [h, s, l];
   
},

    /**
     * Return a new color that is lighter than this color.
     * @param {Number} factor Lighter factor (0..1), default to 0.2
     * @return Ext.draw.Color
     */

    getLighter
: function(factor) {
       
var hsl = this.getHSL();
        factor
= factor || this.lightnessFactor;
        hsl
[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
       
return this.fromHSL(hsl[0], hsl[1], hsl[2]);
   
},

    /**
     * Return a new color that is darker than this color.
     * @param {Number} factor Darker factor (0..1), default to 0.2
     * @return Ext.draw.Color
     */

    getDarker
: function(factor) {
        factor
= factor || this.lightnessFactor;
       
return this.getLighter(-factor);
   
},

    /**
     * Return the color in the hex format, i.e. '#rrggbb'.
     * @return {String}
     */

    toString
: function() {
       
var me = this,
            round
= Math.round,
            r
= round(me.r).toString(16),
            g
= round(me.g).toString(16),
            b
= round(me.b).toString(16);
        r
= (r.length == 1) ? '0' + r : r;
        g
= (g.length == 1) ? '0' + g : g;
        b
= (b.length == 1) ? '0' + b : b;
       
return ['#', r, g, b].join('');
   
},

    /**
     * Convert a color to hexadecimal format.
     *
     * @param {String|Array} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
     * Can also be an Array, in this case the function handles the first member.
     * @returns {String} The color in hexadecimal format.
     */

    toHex
: function(color) {
       
if (Ext.isArray(color)) {
            color
= color[0];
       
}
       
if (!Ext.isString(color)) {
           
return '';
       
}
       
if (color.substr(0, 1) === '#') {
           
return color;
       
}
       
var digits = this.colorToHexRe.exec(color);

       
if (Ext.isArray(digits)) {
           
var red = parseInt(digits[2], 10),
                green
= parseInt(digits[3], 10),
                blue
= parseInt(digits[4], 10),
                rgb
= blue | (green << 8) | (red << 16);
           
return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
       
}
       
else {
           
return '';
       
}
   
},

    /**
     * Parse the string and create a new color.
     *
     * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
     *
     * If the string is not recognized, an undefined will be returned instead.
     *
     * @param {String} str Color in string.
     * @returns Ext.draw.Color
     */

    fromString
: function(str) {
       
var values, r, g, b,
            parse
= parseInt;

       
if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
            values
= str.match(this.hexRe);
           
if (values) {
                r
= parse(values[1], 16) >> 0;
                g
= parse(values[2], 16) >> 0;
                b
= parse(values[3], 16) >> 0;
               
if (str.length == 4) {
                    r
+= (r * 16);
                    g
+= (g * 16);
                    b
+= (b * 16);
               
}
           
}
       
}
       
else {
            values
= str.match(this.rgbRe);
           
if (values) {
                r
= values[1];
                g
= values[2];
                b
= values[3];
           
}
       
}

       
return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
   
},

    /**
     * Returns the gray value (0 to 255) of the color.
     *
     * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
     *
     * @returns {Number}
     */

    getGrayscale
: function() {
       
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
       
return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
   
},

    /**
     * Create a new color based on the specified HSL values.
     *
     * @param {Number} h Hue component (0..359)
     * @param {Number} s Saturation component (0..1)
     * @param {Number} l Lightness component (0..1)
     * @returns Ext.draw.Color
     */

    fromHSL
: function(h, s, l) {
       
var C, X, m, i, rgb = [],
            abs
= Math.abs,
            floor
= Math.floor;

       
if (s == 0 || h == null) {
           
// achromatic
            rgb
= [l, l, l];
       
}
       
else {
           
// http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
           
// C is the chroma
           
// X is the second largest component
           
// m is the lightness adjustment
            h
/= 60;
            C
= s * (1 - abs(2 * l - 1));
            X
= C * (1 - abs(h - 2 * floor(h / 2) - 1));
            m
= l - C / 2;
           
switch (floor(h)) {
               
case 0:
                    rgb
= [C, X, 0];
                   
break;
               
case 1:
                    rgb
= [X, C, 0];
                   
break;
               
case 2:
                    rgb
= [0, C, X];
                   
break;
               
case 3:
                    rgb
= [0, X, C];
                   
break;
               
case 4:
                    rgb
= [X, 0, C];
                   
break;
               
case 5:
                    rgb
= [C, 0, X];
                   
break;
           
}
            rgb
= [rgb[0] + m, rgb[1] + m, rgb[2] + m];
       
}
       
return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
   
}
}, function() {
   
var prototype = this.prototype;

   
//These functions are both static and instance. TODO: find a more elegant way of copying them
   
this.addStatics({
        fromHSL
: function() {
           
return prototype.fromHSL.apply(prototype, arguments);
       
},
        fromString
: function() {
           
return prototype.fromString.apply(prototype, arguments);
       
},
        toHex
: function() {
           
return prototype.toHex.apply(prototype, arguments);
       
}
   
});
});