/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*	Tiny Look and Feel                                                         *
*                                                                              *
*  (C) Copyright 2003 - 2007 Hans Bickel                                       *
*                                                                              *
*   For licensing information and credits, please refer to the                 *
*   comment in file de.muntjak.tinylookandfeel.TinyLookAndFeel                 *
*                                                                              *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package de.muntjak.tinylookandfeel.controlpanel;

import java.awt.*;

/**
 * ColorRoutines
 * 
 * @version 1.0
 * @author Hans Bickel
 */
public class ColorRoutines {
	private static final int RGB = 1;
	private static final int RBG = 2;
	private static final int GBR = 3;
	private static final int GRB = 4;
	private static final int BRG = 5;
	private static final int BGR = 6;
	private boolean preserveGrey;
	private int chue, csat, cbri;
	private int fr, fg, fb;
	int hi, lo, md;
	boolean hiIsR, hiIsG, hiIsB;
	boolean mdIsR, mdIsG, mdIsB;
	boolean loIsR, loIsG, loIsB;

	public ColorRoutines(Color c) {
		setHSB(c.getRed(), c.getGreen(), c.getBlue());
	}

	public ColorRoutines(int hue, int sat, int bri, boolean preserveGrey) {
		chue = hue;
		csat = sat;
		cbri = bri;
		this.preserveGrey = preserveGrey;

		Color c = Color.getHSBColor((float) ((double)chue / 360.0), 1.0f, 1.0f);
		fr = c.getRed();
		fg = c.getGreen();
		fb = c.getBlue();

		// sort colors - 6 options
		if (fr >= fg && fg >= fb) {
			hi = fr;
			md = fg;
			lo = fb;
			hiIsR = true;
			mdIsG = true;
			loIsB = true;
		}
		else if (fr >= fb && fb >= fg) {
			hi = fr;
			md = fb;
			lo = fg;
			hiIsR = true;
			mdIsB = true;
			loIsG = true;
		}
		else if (fg >= fr && fr >= fb) {
			hi = fg;
			md = fr;
			lo = fb;
			hiIsG = true;
			mdIsR = true;
			loIsB = true;
		}
		else if (fg >= fb && fb >= fr) {
			hi = fg;
			md = fb;
			lo = fr;
			hiIsG = true;
			mdIsB = true;
			loIsR = true;
		}
		else if (fb >= fg && fg >= fr) {
			hi = fb;
			md = fg;
			lo = fr;
			hiIsB = true;
			mdIsG = true;
			loIsR = true;
		}
		else if (fb >= fr && fr >= fg) {
			hi = fb;
			md = fr;
			lo = fg;
			hiIsB = true;
			mdIsR = true;
			loIsG = true;
		}
	}

	private void setHSB(int r, int g, int b) {
		chue = getHue(r, g, b);
		csat = getSaturation(r, g, b);
		cbri = getBrightness(r, g, b);
	}

	public static Color getAverage(Color c1, Color c2) {
		int r = (int)Math.round((c1.getRed() + c2.getRed()) / 2.0);
		int g = (int)Math.round((c1.getGreen() + c2.getGreen()) / 2.0);
		int b = (int)Math.round((c1.getBlue() + c2.getBlue()) / 2.0);

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

	// i >= 0 <= d
	// c1 ist Einblendfarbe
	// c2 ist Hintergrundfarbe
	public static Color getGradient(Color c1, Color c2, int d, int i) {
		if (i == 0)
			return c1;
		if (i == d)
			return c2;

		double d2 = i * 1.1 / d;
		double d1 = 1.0 - d2;

		int r = (int)Math.round(c1.getRed() * d1 + c2.getRed() * d2);
		int g = (int)Math.round(c1.getGreen() * d1 + c2.getGreen() * d2);
		int b = (int)Math.round(c1.getBlue() * d1 + c2.getBlue() * d2);

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

	public static Color getMaxSaturation(Color c, int memH) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

		if (r == g && r == b)
			return c;

		int ta = 0, tb = 0, tc = 0;
		int mapping = RGB;

		if (r >= g && r >= b) {
			tc = r;

			if (g == b) {
				ta = g;
				tb = b;
				mapping = RGB;
			}
			else if (g > b) {
				ta = g;
				tb = b;
				mapping = RGB;
			}
			else {
				tb = g;
				ta = b;
				mapping = RBG;
			}
		}
		else if (g >= r && g >= b) {
			tc = g;

			if (r == b) {
				ta = r;
				tb = b;
				mapping = GRB;
			}
			else if (r > b) {
				ta = r;
				tb = b;
				mapping = GRB;
			}
			else {
				tb = r;
				ta = b;
				mapping = GBR;
			}
		}
		else if (b >= r && b >= g) {
			tc = b;

			if (r == g) {
				ta = r;
				tb = g;
				mapping = BRG;
			}
			else if (r > g) {
				ta = r;
				tb = g;
				mapping = BRG;
			}
			else {
				tb = r;
				ta = g;
				mapping = BGR;
			}
		}

		if (tb == 0) {
			return c;
		}

		int nc = Math.min(255, tc + tb);
		int nb = Math.max(0, tc + tb - 255);
		int na = ta;
		int h = 0, mh = 0;
		int ba = 0, delta = 360;
		Color rc = null;

		switch (mapping) {
			case RGB :
				h = getHue(nc, na, nb);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(nc, ++na, nb);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(nc, na, nb);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(nc, na, nb);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(nc, --na, nb);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(nc, na, nb);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(nc, na, nb);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}
				if (na == 256 | na == -1) {
					na = ba;
				}
				rc = new Color(nc, na, nb);
				break;
			case RBG :
				h = getHue(nc, nb, na);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(nc, nb, ++na);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(nc, nb, na);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(nc, nb, na);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(nc, nb, --na);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(nc, nb, na);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(nc, nb, na);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}
				if (na == 256 | na == -1) {
					na = ba;
				}
				rc = new Color(nc, nb, na);
				break;
			case GBR :
				h = getHue(nb, nc, na);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(nb, nc, ++na);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(nb, nc, na);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(nb, nc, na);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(nb, nc, --na);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(nb, nc, na);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(nb, nc, na);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}

				if (na == 256 | na == -1) {
					na = ba;
				}

				rc = new Color(nb, nc, na);
				break;
			case GRB :
				h = getHue(na, nc, nb);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(++na, nc, nb);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(na, nc, nb);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(na, nc, nb);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(--na, nc, nb);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(na, nc, nb);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(na, nc, nb);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}
				if (na == 256 | na == -1) {
					na = ba;
				}
				rc = new Color(na, nc, nb);
				break;
			case BRG :
				h = getHue(na, nb, nc);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(++na, nb, nc);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(na, nb, nc);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(na, nb, nc);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(--na, nb, nc);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(na, nb, nc);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(na, nb, nc);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}
				if (na == 256 | na == -1) {
					na = ba;
				}
				rc = new Color(na, nb, nc);
				break;
			case BGR :
				h = getHue(nb, na, nc);
				mh = h;
				while (h != memH && na < 256) {
					h = getHue(nb, ++na, nc);
					if (na == 256)
						break;

					if (h == memH) {
						return new Color(nb, na, nc);
					}
					else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
						return new Color(nb, na, nc);
					}
					else if (Math.abs(h - memH) < delta) {
						delta = Math.abs(h - memH);
						ba = na;
					}
					mh = h;
				}

				if (h != memH) {
					h = getHue(nc, na, nb);
					mh = h;
					na = ta;
					while (h != memH && na >= 0) {
						h = getHue(nb, --na, nc);
						if (na == -1)
							break;

						if (h == memH) {
							return new Color(nb, na, nc);
						}
						else if ((mh < memH && h > memH) || (mh > memH && h < memH)) {
							return new Color(nb, na, nc);
						}
						else if (Math.abs(h - memH) < delta) {
							delta = Math.abs(h - memH);
							ba = na;
						}
						mh = h;
					}
				}
				if (na == 256 | na == -1) {
					na = ba;
				}
				rc = new Color(nb, na, nc);
				break;
		}

		return rc;
	}

	public static float getGreyValue(Color c) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();
		int ta = 0, tb = 0, tc = 0;

		if (r >= g && r >= b) {
			if (r == 0)
				return 0; // black

			tc = r;

			if (g >= b) {
				ta = g;
				tb = b;
			}
			else {
				tb = g;
				ta = b;
			}
		}
		else if (g >= r && g >= b) {
			tc = g;

			if (r >= b) {
				ta = r;
				tb = b;
			}
			else {
				tb = r;
				ta = b;
			}
		}
		else if (b >= r && b >= g) {
			tc = b;

			if (r >= g) {
				ta = r;
				tb = g;
			}
			else {
				tb = r;
				ta = g;
			}
		}

		return (float) ((tc + tb) / 2.0);
	}

	public static int getBrightness(Color c) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

		return getBrightness(r, g, b);
	}

	public static int getBrightness(int r, int g, int b) {
		if (r >= g && r >= b) {
			return (int)Math.round(100 * r / 255.0);
		}
		else if (g >= r && g >= b) {
			return (int)Math.round(100 * g / 255.0);
		}
		else if (b >= r && b >= g) {
			return (int)Math.round(100 * b / 255.0);
		}

		return -1;
	}

	public static int getSaturation(Color c) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

		return getSaturation(r, g, b);
	}

	public static int getSaturation(int r, int g, int b) {
		int ta = 0, tb = 0, tc = 0;

		if (r >= g && r >= b) {
			if (r == 0)
				return 0; // black

			tc = r;

			if (g >= b) {
				ta = g;
				tb = b;
			}
			else {
				tb = g;
				ta = b;
			}
		}
		else if (g >= r && g >= b) {
			tc = g;

			if (r >= b) {
				ta = r;
				tb = b;
			}
			else {
				tb = r;
				ta = b;
			}
		}
		else if (b >= r && b >= g) {
			tc = b;

			if (r >= g) {
				ta = r;
				tb = g;
			}
			else {
				tb = r;
				ta = g;
			}
		}

		return 100 - (int)Math.round(100.0 * tb / tc);
	}

	public static int getHue(Color c) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

		return getHue(r, g, b);
	}

	public static int calculateHue(Color c) {
		float f[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
		return (int)Math.round(360.0 * f[0]);
	}

	public static int getHue(int r, int g, int b) {
		int ta = 0, tb = 0, tc = 0;
		int mapping = RGB;

		if (r >= g && r >= b) {
			tc = r;

			if (g == b) {
				return 0;
			}
			else if (g > b) {
				ta = g;
				tb = b;
				mapping = RGB;
			}
			else {
				tb = g;
				ta = b;
				mapping = RBG;
			}
		}
		else if (g >= r && g >= b) {
			tc = g;

			if (r == b) {
				return 120;
			}
			else if (r > b) {
				ta = r;
				tb = b;
				mapping = GRB;
			}
			else {
				tb = r;
				ta = b;
				mapping = GBR;
			}
		}
		else if (b >= r && b >= g) {
			tc = b;

			if (r == g) {
				return 240;
			}
			else if (r > g) {
				ta = r;
				tb = g;
				mapping = BRG;
			}
			else {
				tb = r;
				ta = g;
				mapping = BGR;
			}
		}

		// normalize
		double na = (ta * 255.0 / tc);
		double nb = (tb * 255.0 / tc);

		double val = ((na - nb) * 255.0 / (255 - nb));

		int w = (int)Math.round(60 * val / 255.0);

		switch (mapping) {
			case RGB :
				return w; // 0 - 60
			case RBG :
				return 360 - w; // 300 - 360
			case GBR :
				return 120 + w; // 120 - 180
			case GRB :
				return 120 - w; // 60 - 120
			case BRG :
				return 240 + w; // 240 - 300
			case BGR :
				return 240 - w; // 180 - 240
			default :
				return -1;
		}
	}

	public static Color getHSB(int h, int s, int b) {
		double cr = 0, cg = 0, cb = 0;
		int mapping = RGB;

		if (h == 360)
			h = 0;

		// compute hue
		int winkel = h / 60;
		int amount = h % 60;

		switch (winkel) {
			case 0 : // 0 - 60
				cr = 255;
				cg = (255 * amount / 60.0);
				mapping = RGB;
				break;
			case 1 : // 60 - 120
				cg = 255;
				cr = 255 - (255 * amount / 60.0);
				mapping = GBR;
				break;
			case 2 : // 120 - 180
				cg = 255;
				cb = (255 * amount / 60.0);
				mapping = GBR;
				break;
			case 3 : // 180 - 240
				cb = 255;
				cg = 255 - (255 * amount / 60.0);
				mapping = BRG;
				break;
			case 4 : // 240 - 300
				cb = 255;
				cr = (255 * amount / 60.0);
				mapping = BRG;
				break;
			case 5 : // 300 - 360
				cr = 255;
				cb = 255 - (255 * amount / 60.0);
				mapping = RGB;
				break;
		}

		// compute brightness
		cr = (cr * b / 100.0);
		cg = (cg * b / 100.0);
		cb = (cb * b / 100.0);

		// compute saturation
		int d = 100 - s;

		switch (mapping) {
			case RGB :
				cg += ((cr - cg) * d / 100.0);
				cb += ((cr - cb) * d / 100.0);
				break;
			case GBR :
				cr += ((cg - cr) * d / 100.0);
				cb += ((cg - cb) * d / 100.0);
				break;
			case BRG :
				cr += ((cb - cr) * d / 100.0);
				cg += ((cb - cg) * d / 100.0);
				break;
		}

		return new Color((int)Math.round(cr), (int)Math.round(cg), (int)Math.round(cb));
	}

	// Parameter: the original icon pixel
	public int colorize(int r, int g, int b, int a) {
		if (cbri == 100) {
			return colorToInt(255, 255, 255, a);
		}
		else if (cbri == -100) {
			return colorToInt(0, 0, 0, a);
		}

		// first calculate the grey value			
		int hi1 = r;
		if (g >= r && g >= b)
			hi1 = g;
		else if (b >= r && b >= g)
			hi1 = b;

		int lo1 = r;
		if (g <= r && g <= b)
			lo1 = g;
		else if (b <= r && b <= g)
			lo1 = b;

		int grey = (hi1 + lo1) / 2; // floor

		// compute with cbri
		if (cbri < 0) {
			grey += grey * cbri / 100;
		}
		else if (cbri > 0) {
			grey += (255 - grey) * cbri / 100;
		}

		// if in-colors are equal and preserveGrey is true,
		// return grey values
		if (preserveGrey) {
			if (r == g && r == b) {
				return colorToInt(grey, grey, grey, a);
			}
		}

		// now calculate the output colors
		// csat = 0 => output = grey
		// grey = 127 => output = full saturation
		int hr = 0;
		int hg = 0;
		int hb = 0;
		int diff = 0;

		if (grey >= 127) {
			diff = 255 - grey;
		}
		else {
			diff = grey;
		}
		// hi value is always 255
		if (hiIsR) {
			hr = grey + diff * csat / 100;
		}
		else if (hiIsG) {
			hg = grey + diff * csat / 100;
		}
		else if (hiIsB) {
			hb = grey + diff * csat / 100;
		}

		// md value is between 0 and 255
		if (mdIsR) {
			if (grey >= 127) {
				diff = fr + (255 - fr) * (grey - 127) / 128 - grey;
			}
			else {
				diff = fr * grey / 127 - grey;
			}
			hr = grey + diff * csat / 100;
		}
		else if (mdIsG) {
			if (grey >= 127) {
				diff = fg + (255 - fg) * (grey - 127) / 128 - grey;
			}
			else {
				diff = fg * grey / 127 - grey;
			}
			hg = grey + diff * csat / 100;
		}
		else if (mdIsB) {
			if (grey >= 127) {
				diff = fb + (255 - fb) * (grey - 127) / 128 - grey;
			}
			else {
				diff = fb * grey / 127 - grey;
			}
			hb = grey + diff * csat / 100;
		}

		diff = grey - (255 - grey);
		if (diff < 0)
			diff = 0;
		diff = grey - diff;

		// lo value = 0
		if (loIsR) {
			hr = grey - diff * csat / 100;
		}
		else if (loIsG) {
			hg = grey - diff * csat / 100;
		}
		else if (loIsB) {
			hb = grey - diff * csat / 100;
		}

		return colorToInt(hr, hg, hb, a);
	}

	public static Color getInverseColor(Color c) {
		int r = 255 - c.getRed();
		int g = 255 - c.getGreen();
		int b = 255 - c.getBlue();

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

	public static Color getRandomColor() {
		int r = (int) (Math.random() * 255);
		int g = (int) (Math.random() * 255);
		int b = (int) (Math.random() * 255);

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

	public static Color getAlphaColor(Color c, int a) {
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

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

	protected static int colorToInt(Color c, int a) {
		return c.getBlue() + c.getGreen() * 256 + c.getRed() * (256 * 256) + a * (256 * 256 * 256);
	}

	protected static int colorToInt(int r, int g, int b, int a) {
		return b + g * 256 + r * (256 * 256) + a * (256 * 256 * 256);
	}

	public static Color lighten(Color c, int amount) {
		if (amount < 0) return c;
		
		if (amount > 100) amount = 100;

		int dr = (int)Math.round((255 - c.getRed()) * amount / 100.0);
		int dg = (int)Math.round((255 - c.getGreen()) * amount / 100.0);
		int db = (int)Math.round((255 - c.getBlue()) * amount / 100.0);

		return new Color(c.getRed() + dr, c.getGreen() + dg, c.getBlue() + db, c.getAlpha());
	}

	public static Color darken(Color c, int amount) {
		if (amount < 0 || amount > 100) return c;

		int r = (int)Math.round(c.getRed() * (100 - amount) / 100.0);
		int g = (int)Math.round(c.getGreen() * (100 - amount) / 100.0);
		int b = (int)Math.round(c.getBlue() * (100 - amount) / 100.0);

		return new Color(r, g, b, c.getAlpha());
	}

	public static Color lighten(int grey, int amount) {
		if (amount < 0 || amount > 100) return new Color(grey, grey, grey);

		int val = (255 - grey) * amount / 100 + grey;

		return new Color(val, val, val);
	}

	public static Color darken(int grey, int amount) {
		if (amount < 0 || amount > 100) return new Color(grey, grey, grey);

		int val = grey * (100 - amount) / 100;

		return new Color(val, val, val);
	}

}
