package net.sourceforge.plantuml.klimt.color;

import net.sourceforge.plantuml.klimt.awt.XColor;

/**
 * Taken from http://www.camick.com/java/source/HSLColor.java
 * 
 * The HSLColor class provides methods to manipulate HSL (Hue, Saturation
 * Luminance) values to create a corresponding Color object using the RGB
 * ColorSpace.
 *
 * The HUE is the color, the Saturation is the purity of the color (with respect
 * to grey) and Luminance is the brightness of the color (with respect to black
 * and white)
 *
 * The Hue is specified as an angel between 0 - 360 degrees where red is 0,
 * green is 120 and blue is 240. In between you have the colors of the rainbow.
 * Saturation is specified as a percentage between 0 - 100 where 100 is fully
 * saturated and 0 approaches gray. Luminance is specified as a percentage
 * between 0 - 100 where 0 is black and 100 is white.
 *
 * In particular the HSL color space makes it easier change the Tone or Shade of
 * a color by adjusting the luminance value.
 */
public class HSLColor {
	// ::remove file when __HAXE__
	private XColor rgb;
	private float[] hsl;
	private float alpha;

	/**
	 * Create a HSLColor object using an RGB Color object.
	 *
	 * @param rgb the RGB Color object
	 */
	public HSLColor(XColor rgb) {
		this.rgb = rgb;
		hsl = fromRGB(rgb);
		alpha = rgb.getAlpha() / 255.0f;
	}

	/**
	 * Create a HSLColor object using individual HSL values and a default alpha
	 * value of 1.0.
	 *
	 * @param h is the Hue value in degrees between 0 - 360
	 * @param s is the Saturation percentage between 0 - 100
	 * @param l is the Lumanance percentage between 0 - 100
	 */
	public HSLColor(float h, float s, float l) {
		this(h, s, l, 1.0f);
	}

	/**
	 * Create a HSLColor object using individual HSL values.
	 *
	 * @param h     the Hue value in degrees between 0 - 360
	 * @param s     the Saturation percentage between 0 - 100
	 * @param l     the Lumanance percentage between 0 - 100
	 * @param alpha the alpha value between 0 - 1
	 */
	public HSLColor(float h, float s, float l, float alpha) {
		hsl = new float[] { h, s, l };
		this.alpha = alpha;
		rgb = toRGB(hsl, alpha);
	}

	/**
	 * Create a HSLColor object using an an array containing the individual HSL
	 * values and with a default alpha value of 1.
	 *
	 * @param hsl array containing HSL values
	 */
	public HSLColor(float[] hsl) {
		this(hsl, 1.0f);
	}

	/**
	 * Create a HSLColor object using an an array containing the individual HSL
	 * values.
	 *
	 * @param hsl   array containing HSL values
	 * @param alpha the alpha value between 0 - 1
	 */
	public HSLColor(float[] hsl, float alpha) {
		this.hsl = hsl;
		this.alpha = alpha;
		rgb = toRGB(hsl, alpha);
	}

	/**
	 * Create a RGB Color object based on this HSLColor with a different Hue value.
	 * The degrees specified is an absolute value.
	 *
	 * @param degrees - the Hue value between 0 - 360
	 * @return the RGB Color object
	 */
	public XColor adjustHue(float degrees) {
		return toRGB(degrees, hsl[1], hsl[2], alpha);
	}

	/**
	 * Create a RGB Color object based on this HSLColor with a different Luminance
	 * value. The percent specified is an absolute value.
	 *
	 * @param percent - the Luminance value between 0 - 100
	 * @return the RGB Color object
	 */
	public XColor adjustLuminance(float percent) {
		return toRGB(hsl[0], hsl[1], percent, alpha);
	}

	/**
	 * Create a RGB Color object based on this HSLColor with a different Saturation
	 * value. The percent specified is an absolute value.
	 *
	 * @param percent - the Saturation value between 0 - 100
	 * @return the RGB Color object
	 */
	public XColor adjustSaturation(float percent) {
		return toRGB(hsl[0], percent, hsl[2], alpha);
	}

	/**
	 * Create a RGB Color object based on this HSLColor with a different Shade.
	 * Changing the shade will return a darker color. The percent specified is a
	 * relative value.
	 *
	 * @param percent - the value between 0 - 100
	 * @return the RGB Color object
	 */
	public XColor adjustShade(float percent) {
		float multiplier = (100.0f - percent) / 100.0f;
		float l = Math.max(0.0f, hsl[2] * multiplier);

		return toRGB(hsl[0], hsl[1], l, alpha);
	}

	/**
	 * Create a RGB Color object based on this HSLColor with a different Tone.
	 * Changing the tone will return a lighter color. The percent specified is a
	 * relative value.
	 *
	 * @param percent - the value between 0 - 100
	 * @return the RGB Color object
	 */
	public XColor adjustTone(float percent) {
		float multiplier = (100.0f + percent) / 100.0f;
		float l = Math.min(100.0f, hsl[2] * multiplier);

		return toRGB(hsl[0], hsl[1], l, alpha);
	}

	/**
	 * Get the Alpha value.
	 *
	 * @return the Alpha value.
	 */
	public float getAlpha() {
		return alpha;
	}

	/**
	 * Create a RGB Color object that is the complementary color of this HSLColor.
	 * This is a convenience method. The complementary color is determined by adding
	 * 180 degrees to the Hue value.
	 * 
	 * @return the RGB Color object
	 */
	public XColor getComplementary() {
		float hue = (hsl[0] + 180.0f) % 360.0f;
		return toRGB(hue, hsl[1], hsl[2]);
	}

	/**
	 * Get the Hue value.
	 *
	 * @return the Hue value.
	 */
	public float getHue() {
		return hsl[0];
	}

	/**
	 * Get the HSL values.
	 *
	 * @return the HSL values.
	 */
	public float[] getHSL() {
		return hsl;
	}

	/**
	 * Get the Luminance value.
	 *
	 * @return the Luminance value.
	 */
	public float getLuminance() {
		return hsl[2];
	}

	/**
	 * Get the RGB Color object represented by this HDLColor.
	 *
	 * @return the RGB Color object.
	 */
	public XColor getRGB() {
		return rgb;
	}

	/**
	 * Get the Saturation value.
	 *
	 * @return the Saturation value.
	 */
	public float getSaturation() {
		return hsl[1];
	}

	public String toString() {
		String toString = "HSLColor[h=" + hsl[0] + ",s=" + hsl[1] + ",l=" + hsl[2] + ",alpha=" + alpha + "]";

		return toString;
	}

	/**
	 * Convert a RGB Color to it corresponding HSL values.
	 *
	 * @return an array containing the 3 HSL values.
	 */
	public static float[] fromRGB(XColor color) {
		// Get RGB values in the range 0 - 1

		float[] rgb = color.getRGBColorComponents(null);
		float r = rgb[0];
		float g = rgb[1];
		float b = rgb[2];

		// Minimum and Maximum RGB values are used in the HSL calculations

		float min = Math.min(r, Math.min(g, b));
		float max = Math.max(r, Math.max(g, b));

		// Calculate the Hue

		float h = 0;

		if (max == min)
			h = 0;
		else if (max == r)
			h = ((60 * (g - b) / (max - min)) + 360) % 360;
		else if (max == g)
			h = (60 * (b - r) / (max - min)) + 120;
		else if (max == b)
			h = (60 * (r - g) / (max - min)) + 240;

		// Calculate the Luminance

		float l = (max + min) / 2;

		// Calculate the Saturation

		float s = 0;

		if (max == min)
			s = 0;
		else if (l <= .5f)
			s = (max - min) / (max + min);
		else
			s = (max - min) / (2 - max - min);

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

	/**
	 * Convert HSL values to a RGB Color with a default alpha value of 1. H (Hue) is
	 * specified as degrees in the range 0 - 360. S (Saturation) is specified as a
	 * percentage in the range 1 - 100. L (Lumanance) is specified as a percentage
	 * in the range 1 - 100.
	 *
	 * @param hsl an array containing the 3 HSL values
	 *
	 * @return the RGB Color object
	 */
	public static XColor toRGB(float[] hsl) {
		return toRGB(hsl, 1.0f);
	}

	/**
	 * Convert HSL values to a RGB Color. H (Hue) is specified as degrees in the
	 * range 0 - 360. S (Saturation) is specified as a percentage in the range 1 -
	 * 100. L (Lumanance) is specified as a percentage in the range 1 - 100.
	 *
	 * @param hsl   an array containing the 3 HSL values
	 * @param alpha the alpha value between 0 - 1
	 *
	 * @return the RGB Color object
	 */
	public static XColor toRGB(float[] hsl, float alpha) {
		return toRGB(hsl[0], hsl[1], hsl[2], alpha);
	}

	/**
	 * Convert HSL values to a RGB Color with a default alpha value of 1.
	 *
	 * @param h Hue is specified as degrees in the range 0 - 360.
	 * @param s Saturation is specified as a percentage in the range 1 - 100.
	 * @param l Lumanance is specified as a percentage in the range 1 - 100.
	 *
	 * @return the RGB Color object
	 */
	public static XColor toRGB(float h, float s, float l) {
		return toRGB(h, s, l, 1.0f);
	}

	/**
	 * Convert HSL values to a RGB Color.
	 *
	 * @param h     Hue is specified as degrees in the range 0 - 360.
	 * @param s     Saturation is specified as a percentage in the range 1 - 100.
	 * @param l     Lumanance is specified as a percentage in the range 1 - 100.
	 * @param alpha the alpha value between 0 - 1
	 *
	 * @return the RGB Color object
	 */
	public static XColor toRGB(float h, float s, float l, float alpha) {

		if (s < 0)
			s = 0;
		if (s > 100)
			s = 100;
		if (l < 0)
			l = 0;
		if (l > 100)
			l = 100;
		if (alpha < 0)
			alpha = 0;
		if (alpha > 1)
			alpha = 1;

		// Formula needs all values between 0 - 1.

		h = h % 360.0f;
		h /= 360f;
		s /= 100f;
		l /= 100f;

		float q = 0;

		if (l < 0.5)
			q = l * (1 + s);
		else
			q = (l + s) - (s * l);

		float p = 2 * l - q;

		float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f)));
		float g = Math.max(0, HueToRGB(p, q, h));
		float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f)));

		r = Math.min(r, 1.0f);
		g = Math.min(g, 1.0f);
		b = Math.min(b, 1.0f);

		return XColor.fromFloat(r, g, b, alpha);
	}

	private static float HueToRGB(float p, float q, float h) {
		if (h < 0)
			h += 1;

		if (h > 1)
			h -= 1;

		if (6 * h < 1)
			return p + ((q - p) * 6 * h);

		if (2 * h < 1)
			return q;

		if (3 * h < 2)
			return p + ((q - p) * 6 * ((2.0f / 3.0f) - h));

		return p;
	}
}
