package flare.util
{    
    /**
     * Utility methods for working with colors.
     */
    public class Colors
    {
        /**
         * Constructor, throws an error if called, as this is an abstract class.
         */
        public function Colors() {
            throw new Error("This is an abstract class.");
        }
                
        /**
         * Returns the alpha component of a color value
         * @param c the color value
         * @return the alpha component
         */
        public static function a(c:uint):uint
        {
            return (c >> 24) & 0xFF;
        }
        
        /**
         * Returns the red component of a color value
         * @param c the color value
         * @return the red component
         */
        public static function r(c:uint):uint
        {
            return (c >> 16) & 0xFF;
        }
        
        /**
         * Returns the green component of a color value
         * @param c the color value
         * @return the green component
         */
        public static function g(c:uint):uint
        {
            return (c >> 8) & 0xFF;
        }
        
        /**
         * Returns the blue component of a color value
         * @param c the color value
         * @return the blue component
         */
        public static function b(c:uint):uint
        {
            return (c & 0xFF);
        }
        
        /**
         * Returns a grayscale color value with the given brightness
         * @param v the brightness component (0-255)
         * @param a the alpha component (0-255, 255 by default)
         * @return the color value
         */
        public static function gray(v:uint, a:uint=255):uint
        {
            return ((a & 0xFF) << 24) | ((v & 0xFF) << 16) |
                   ((v & 0xFF) <<  8) |  (v & 0xFF);
        }
        
        /**
         * Returns a color value with the given red, green, blue, and alpha
         * components
         * @param r the red component (0-255)
         * @param g the green component (0-255)
         * @param b the blue component (0-255)
         * @param a the alpha component (0-255, 255 by default)
         * @return the color value
         * 
         */
        public static function rgba(r:uint, g:uint, b:uint, a:uint=255):uint
        {
            return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) |
                   ((g & 0xFF) <<  8) |  (b & 0xFF);
        }
        
        /**
         * Returns a color value by updating the alpha component of another
         * color value.
         * @param c a color value
         * @param a the desired alpha component (0-255)
         * @return a color value with adjusted alpha component
         */
        public static function setAlpha(c:uint, a:uint):uint
        {
            return ((a & 0xFF) << 24) | (c & 0x00FFFFFF);
        }
        
        /**
         * Returns the RGB color value for a color specified in HSV (hue,
         * saturation, value) color space.
         * @param h the hue, a value between 0 and 1
         * @param s the saturation, a value between 0 and 1
         * @param v the value (brighntess), a value between 0 and 1
         * @param a the (optional) alpha value, an integer between 0 and 255
         *  (255 is the default)
         * @return the corresponding RGB color value
         */
        public static function hsv(h:Number, s:Number, v:Number, a:uint=255):uint
        {
            var r:uint=0, g:uint=0, b:uint=0;
            if (s == 0) {
                r = g = b = uint(v * 255 + .5);
            } else {
                var i:Number = (h - Math.floor(h)) * 6.0;
                var f:Number = i - Math.floor(i);
                var p:Number = v * (1 - s);
                var q:Number = v * (1 - s * f);
                var t:Number = v * (1 - (s * (1 - f)));
                switch (int(i))
                {
                    case 0:
                        r = uint(v * 255 + .5);
                        g = uint(t * 255 + .5);
                        b = uint(p * 255 + .5);
                        break;
                    case 1:
                        r = uint(q * 255 + .5);
                        g = uint(v * 255 + .5);
                        b = uint(p * 255 + .5);
                        break;
                    case 2:
                        r = uint(p * 255 + .5);
                        g = uint(v * 255 + .5);
                        b = uint(t * 255 + .5);
                        break;
                    case 3:
                        r = uint(p * 255 + .5);
                        g = uint(q * 255 + .5);
                        b = uint(v * 255 + .5);
                        break;
                    case 4:
                        r = uint(t * 255 + .5);
                        g = uint(p * 255 + .5);
                        b = uint(v * 255 + .5);
                        break;
                    case 5:
                        r = uint(v * 255 + .5);
                        g = uint(p * 255 + .5);
                        b = uint(q * 255 + .5);
                        break;
                }
            }
            return rgba(r, g, b, a);
        }
        
        /**
         * Returns the hue component of an ARGB color. 
         * @param c the input color
         * @return the hue component of the color is HSV color space as a
         *  number between 0 and 1
         */
        public static function hue(c:uint):Number
        {
            var r:uint, g:uint, b:uint, cmax:uint, cmin:uint;
            var h:Number, range:Number;
            
            r = (c >> 16) & 0xFF;
            g = (c >> 8) & 0xFF;
            b = (c & 0xFF);
            cmax = (r > g) ? r : g; if (b > cmax) cmax = b;
            cmin = (r < g) ? r : g; if (b < cmin) cmin = b;
            range = Number(cmax - cmin);

            if (range == 0) {
                h = 0;
            } else {
                var rc:Number = Number(cmax - r) / range;
                var gc:Number = Number(cmax - g) / range;
                var bc:Number = Number(cmax - b) / range;
                if (r == cmax)
                    h = bc - gc;
                else if (g == cmax)
                    h = 2.0 + rc - bc;
                else
                    h = 4.0 + gc - rc;
                    h = h / 6.0;
                if (h < 0)
                    h = h + 1.0;
            }
            return h;
        }
        
        /**
         * Returns the saturation component of an ARGB color. 
         * @param c the input color
         * @return the saturation of the color is HSV color space as a
         *  number between 0 and 1
         */
        public static function saturation(c:uint):Number
        {
            var r:uint, g:uint, b:uint, cmax:uint, cmin:uint;
            r = (c >> 16) & 0xFF;
            g = (c >> 8) & 0xFF;
            b = (c & 0xFF);
            cmax = (r > g) ? r : g; if (b > cmax) cmax = b;
            if (cmax==0) return 0;
            cmin = (r < g) ? r : g; if (b < cmin) cmin = b;
            return Number(cmax-cmin) / cmax;
        }
        
        /**
         * Returns the value (brightness) component of an ARGB color. 
         * @param c the input color
         * @return the value component of the color is HSV color space as a
         *  number between 0 and 1
         */
        public static function value(c:uint):Number
        {
            var r:uint, g:uint, b:uint, cmax:uint;
            r = (c >> 16) & 0xFF;
            g = (c >> 8) & 0xFF;
            b = (c & 0xFF);
            cmax = (r > g) ? r : g; if (b > cmax) cmax = b;
            return cmax / 255.0;
        }
        
        /**
         * Interpolate between two color values by the given mixing proportion.
         * A mixing fraction of 0 will result in c1, a value of 1.0 will result
         * in c2, and value of 0.5 will result in the color mid-way between the
         * two in RGB color space.
         * @param c1 the starting color
         * @param c2 the target color
         * @param f a fraction between 0 and 1 controlling the interpolation
         * @return the interpolated color
         */
        public static function interpolate(c1:uint, c2:uint, f:Number):uint
        {
            var t:uint;
            return rgba(
                (t=r(c1)) + f*(r(c2)-t),
                (t=g(c1)) + f*(g(c2)-t),
                (t=b(c1)) + f*(b(c2)-t),
                (t=a(c1)) + f*(a(c2)-t)
            );
        }
    
        /**
         * Get a darker shade of an input color.
         * @param c a color value
         * @return a darkened color value
         */
        public static function darker(c:uint, s:Number=1):uint
        {
            s = Math.pow(0.7, s);
            return rgba(Math.max(0, int(s*r(c))),
                        Math.max(0, int(s*g(c))),
                        Math.max(0, int(s*b(c))),
                        a(c));
        }
    
        /**
         * Get a brighter shade of an input color.
         * @param c a color value
         * @return a brighter color value
         */
        public static function brighter(c:uint, s:Number=1):uint
        {
            var cr:uint, cg:uint, cb:uint, i:uint;
            s = Math.pow(0.7, s);
            
            cr = r(c), cg = g(c), cb = b(c);
            i = 30;
            if (cr == 0 && cg == 0 && cb == 0) {
               return rgba(i, i, i, a(c));
            }
            if ( cr > 0 && cr < i ) cr = i;
            if ( cg > 0 && cg < i ) cg = i;
            if ( cb > 0 && cb < i ) cb = i;
    
            return rgba(Math.min(255, (int)(cr/s)),
                        Math.min(255, (int)(cg/s)),
                        Math.min(255, (int)(cb/s)),
                        a(c));
        }
        
        /**
         * Get a desaturated shade of an input color.
         * @param c a color value
         * @return a desaturated color value
         */
        public static function desaturate(c:uint):uint
        {
            var a:uint = c & 0xff000000;
            var cr:Number = Number(r(c));
            var cg:Number = Number(g(c));
            var cb:Number = Number(b(c));
    
            cr *= 0.2125; // red band weight
            cg *= 0.7154; // green band weight
            cb *= 0.0721; // blue band weight
    
            var gray:uint = uint(Math.min(int(cr+cg+cb),0xff)) & 0xff;
            return a | (gray << 16) | (gray << 8) | gray;
        }
        
        /**
         * A color transform matrix that desaturates colors to corresponding
         * grayscale values. Can be used with the
         * <code>flash.filters.ColorMatrixFilter</code> class.
         */
        public static function get desaturationMatrix():Array {
            return [0.2125, 0.7154, 0.0721, 0, 0,
                    0.2125, 0.7154, 0.0721, 0, 0,
                    0.2125, 0.7154, 0.0721, 0, 0,
                         0,      0,      0, 1, 0];
        }
        
    } // end of class Colors
}