package com.srbenoit.color;

import java.awt.Color;

/**
 * A class that generates a sweep of color through a range of hues, and can convert a data value to
 * a color given a value range.
 */
public class Gradient {

    /** the colors */
    private final transient Color[] colors;

    /**
     * Constructs a new <code>Gradient</code> using a given number of colors.
     *
     * @param  numColors  the number of colors
     * @param  numCycles  the number of cycles about hue to use (if this is 1 or less, a single
     *                    cycle with lightness 0.5 will be used; otherwise, we will spiral up from
     *                    black to white through the given number of hue cycles)
     */
    public Gradient(final int numColors, final int numCycles) {

        float fraction;
        float hue;
        float lightness;
        this.colors = new Color[numColors];

        for (int i = 0; i < numColors; i++) {
            hue = 90 + ((360.0f * i * numCycles) / numColors);

            while (hue > 360) {
                hue -= 360;
            }

            fraction = (float) i / numColors;

            if (i == 0) {
                lightness = 0;
            } else {
                lightness = (float) (0.5 - (Math.log((1 / fraction) - 1) / 12.0));
            }

            this.colors[i] = colorFromHSL(hue, 1.0f, lightness);
        }
    }

    /**
     * Gets the number of colors.
     *
     * @return  the number of colors
     */
    public int getNumColors() {

        return this.colors.length;
    }

    /**
     * Gets a particular color.
     *
     * @param   index  the index of the color to get
     * @return  the color
     */
    public Color getColor(final int index) {

        return this.colors[index];
    }

    /**
     * Given a hue in the range 0-1 and a lightness in the range 0-1, generates the corresponding
     * <code>Color</code>. Saturation is assumed to be maximum.
     *
     * @param   hue         the hue angle (0-360)
     * @param   saturation  the saturation
     * @param   lightness   the lightness
     * @return  the color
     */
    private Color colorFromHSL(final float hue, final float saturation, final float lightness) {

        float temp1;
        float temp2;
        float[] color;

        if (saturation == 0) {
            color = new float[] { lightness, lightness, lightness };
        } else {

            if (lightness < 0.5) {
                temp2 = 2 * (lightness + saturation) * lightness;
            } else {
                temp2 = lightness + saturation - (lightness * saturation);
            }

            temp1 = (2 * lightness) - temp2;

            color = new float[] {
                    computeColor(temp1, temp2, hue + 120), computeColor(temp1, temp2, hue),
                    computeColor(temp1, temp2, hue - 120)
                };
        }

        return new Color(color[0], color[1], color[2]);
    }

    /**
     * Computes a color component.
     *
     * @param   temp1  lightness/saturation factor 1
     * @param   temp2  lightness/saturation factor 2
     * @param   hue    the hue component (0-360)
     * @return  the color component
     */
    private float computeColor(final float temp1, final float temp2, final float hue) {

        float hue2;
        float color;

        if (hue < 0) {
            hue2 = hue + 360;
        } else if (hue > 360) {
            hue2 = hue - 360;
        } else {
            hue2 = hue;
        }

        if (hue2 < 60) {
            color = temp1 + ((temp2 - temp1) * hue2 / 60);
        } else if (hue2 < 180) {
            color = temp2;
        } else if (hue2 < 240) {
            color = temp1 + ((temp2 - temp1) * (240 - hue2) / 60);
        } else {
            color = temp1;
        }

        if (color > 1) {
            color = 1;
        } else if (color < 0) {
            color = 0;
        }

        return color;
    }
}
