/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.renderkit.html.images;

import java.awt.Color;

/**
 * Utility methods that are useful for color processing.
 *
 * @author carcasser
 */
public abstract class ColorUtils {

    /**
     * Converts the components of a color, as specified by the default RGB
     * model, to an equivalent set of values for hue, saturation, and lightness
     * that are the three components of the HSL model.
     *
     * @param r
     *            the red component of the color
     * @param g
     *            the green component of the color
     * @param b
     *            the blue component of the color
     * @return an array of three elements containing the hue, saturation, and
     *         lightness (in that order), of the color with the indicated red,
     *         green, and blue components.
     */
    public static float[] RGBtoHSL(int r, int g, int b) {
        float varR = (r / 255f);
        float varG = (g / 255f);
        float varB = (b / 255f);

        float varMin = Math.min(varR, Math.min(varG, varB)); // Min value of RGB
        float varMax = Math.max(varR, Math.max(varG, varB)); // Max value of RGB
        float delMax = varMax - varMin; // Delta RGB value

        float h = 0;
        float s = 0;
        float l = (varMax + varMin) / 2;

        if (delMax == 0 || l == 0) {
            s = 0;
        } else if (l == 1) {
            s = 1;
        } else if (l <= 0.5) {
            s = delMax / (2 * (1 - l));
        } else if (l > 0.5) {
            s = delMax / (2 * l);
        }

        if (delMax == 0) {
            h = 0;
        } else if (varMax == varR && g >= b) {
            h = 60 * (varG - varB) / delMax + 0;
        } else if (varMax == varR && varG < b) {
            h = 60 * (varG - varB) / delMax + 360;
        } else if (varMax == varG) {
            h = 60 * (varB - varR) / delMax + 120;
        } else if (varMax == varB) {
            h = 60 * (varR - varG) / delMax + 240;
        }

        return new float[] { h, s, l };
    }

    /**
     * Converts the components of a color, as specified by the HSL model, to an
     * equivalent set of values for the default RGB model.
     * <p>
     * The <code>saturation</code> and <code>lightness</code> components
     * should be floating-point values between zero and one (numbers in the
     * range 0.0-1.0). The <code>hue</code> component can be any
     * floating-point number. The floor of this number is subtracted from it to
     * create a fraction between 0 and 1. This fractional number is then
     * multiplied by 360 to produce the hue angle in the HSB color model.
     * 
     * @param h
     *            the hue component of the color
     * @param s
     *            the saturation of the color
     * @param l
     *            the lightness of the color
     * @return the RGB value of the color with the indicated hue, saturation,
     *         and lightness
     */
    public static Color HSLtoRGB(float h, float s, float l) {
        float q;
        if (l < 0.5) {
            q = l * (1 + s);
        } else {
            q = l + s - (l * s);
        }

        float p = 2 * l - q;
        float hNorm = h / 360;

        float tR = hNorm + 1f / 3f;
        float tG = hNorm;
        float tB = hNorm - 1f / 3f;

        float r = tC2C(tR, p, q);
        float g = tC2C(tG, p, q);
        float b = tC2C(tB, p, q);

        return new Color(r, g, b);
    }

    private static float tC2C(float tC, float p, float q) {
        float retVal;

        if (tC < 0) {
            tC += 1;
        }

        if (tC > 1) {
            tC -= 1;
        }

        if ((6 * tC) < 1) {
            retVal = (p + (q - p) * 6 * tC);
        } else if ((2 * tC) < 1) {
            retVal = q;
        } else if ((3 * tC) < 2) {
            retVal = (p + (q - p) * 6 * (2f / 3f - tC));
        } else {
            retVal = p;
        }

        return retVal;
    }

    /**
     * Increases/decreases brightness of the given color by the specified
     * <code>difference</code>.
     * <p>
     * The <code>difference</code> values in the range (-1.0, 1.0): 1.0 - the
     * brightest value; -1.0 - the dimmest value.
     * 
     * @param c
     *            color to adjust
     * @param difference
     *            value to be added to the current brightness
     * 
     * @return a new <code>Color</code> instance with increased/decreased
     *         brightness by specified <code>difference</code>
     * @throws IllegalArgumentException
     *             if difference is outside of the range -1.0 to 1.0, inclusive
     */
    public static Color adjustBrightness(Color c, float difference) {
        if (difference < -1.0 || difference > 1.0) {
            throw new IllegalArgumentException("Difference parameter outside of expected range: "
                    + "Difference parameter should be floating-point values between -1 and 1");
        }

        Color retVal = null;
        if (c != null) {
            float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
            float brightness = Math.min(1.0f, Math.max(0.0f, hsb[2] + difference));
            retVal = new Color(Color.HSBtoRGB(hsb[0], hsb[1], brightness));
        }

        return retVal;
    }

    /**
     * Increases/decreases lightness of the given color by the specified
     * <code>difference</code>.
     * <p>
     * The <code>difference</code> values in the range (-1.0, 1.0): 1.0 - the
     * lightest value; -1.0 - on the contrary.
     * 
     * @param c
     *            color to adjust
     * @param difference
     *            value to be added to the current lightness
     * 
     * @return a new <code>Color</code> instance with increased/decreased
     *         lightness by specified <code>difference</code>
     * @throws IllegalArgumentException
     *             if difference is outside of the range -1.0 to 1.0, inclusive
     */
    public static Color adjustLightness(Color c, float difference) {
        if (difference < -1.0 || difference > 1.0) {
            throw new IllegalArgumentException("Difference parameter outside of expected range: "
                    + "Difference parameter should be floating-point values between -1 and 1");
        }

        Color retVal = null;
        if (c != null) {
            float[] hsl = RGBtoHSL(c.getRed(), c.getGreen(), c.getBlue());
            float lightness = Math.min(1.0f, Math.max(0.0f, hsl[2] + difference));
            retVal = HSLtoRGB(hsl[0], hsl[1], lightness);
        }

        return retVal;
    }

    /**
     * Overwrites alpha value for given color.
     *
     * @param c color to overwrite
     * @param alpha a new value of alpha
     * @return a new <code>Color</code> object with a new specified alpha value
     */
    public static Color overwriteAlpha(Color c, float alpha) {
        Color retVal = c;
        if (c != null) {
            retVal = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (alpha * 255 + 0.5));
        }
        return retVal;
    }

}
