package speiger.src.coreengine.math.misc;

import java.awt.Color;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import speiger.src.collections.floats.lists.FloatList;

public class ColorObject
{
	static final float DEVIDER = 1F / 255F;
	public static final int R = 0xFF << 16;
	public static final int G = 0xFF << 8;
	public static final int B = 0xFF;
	public static final int A = 0xFF << 24;
	public static final long SIGN = 0x00000000FFFFFFFFL;
	static final int ALL = 0xFFFFFFFF;
	
	public static final ColorObject WHITE = new ColorObject(255, 255, 255);
	public static final ColorObject LIGHT_GRAY = new ColorObject(192, 192, 192);
	public static final ColorObject GRAY = new ColorObject(128, 128, 128);
	public static final ColorObject DARK_GRAY = new ColorObject(64, 64, 64);
	public static final ColorObject BLACK = new ColorObject(0, 0, 0);
	public static final ColorObject RED = new ColorObject(255, 0, 0);
	public static final ColorObject PINK = new ColorObject(255, 175, 175);
	public static final ColorObject PURPLE = new ColorObject(106, 13, 173);
	public static final ColorObject ORANGE = new ColorObject(255, 200, 0);
	public static final ColorObject YELLOW = new ColorObject(255, 255, 0);
	public static final ColorObject GREEN = new ColorObject(0, 255, 0);
	public static final ColorObject DARK_GREEN = new ColorObject(7, 161, 0);
	public static final ColorObject MAGENTA = new ColorObject(255, 0, 255);
	public static final ColorObject CYAN = new ColorObject(0, 255, 255);
	public static final ColorObject BLUE = new ColorObject(0, 0, 255);
	public static final ColorObject LIGHT_BLUE = new ColorObject(0, 150, 255);

	//Specialized Components that get reused
	public static final ColorObject INVISIBLE = new ColorObject(0, 0, 0, 0);
	public static final ColorObject TEXT_DEFAULT_BACKGROUND = new ColorObject(80, 80, 80, 144);
	public static final ColorObject WINDOW_DEFAULT_BACKGROUND = new ColorObject(64, 64, 64, 128);
	public static final ColorObject POPUP_DEFAULT_BACKGROUND = new ColorObject(85, 85, 85);
	public static final ColorObject DESTRUCTION = new ColorObject(255, 0, 0, 128);
	
	int rgba;
	
	public ColorObject(int rgba)
	{
		this.rgba = rgba;
	}
	
	public ColorObject(int r, int g, int b)
	{
		rgba = A | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
	}
	
	public ColorObject(int r, int g, int b, int a)
	{
		rgba = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
	}
	
	public static ColorObject rgb(int rgb)
	{
		return new ColorObject(rgb | (255 << 24));
	}
	
	public ColorObject copy()
	{
		return new ColorObject(rgba);
	}
	
	public ColorObject setRGB(int rgba)
	{
		this.rgba = rgba;
		return this;
	}
	
	public ColorObject setRed(int r)
	{
		rgba = rgba & ~R | ((r & 0xFF) << 16);
		return this;
	}
	
	public ColorObject setGreen(int g)
	{
		rgba = rgba & ~G | ((g & 0xFF) << 8);
		return this;
	}
	
	public ColorObject setBlue(int b)
	{
		rgba = rgba & ~B | (b & 0xFF);
		return this;
	}
	
	public ColorObject setAlpha(int a)
	{
		rgba = rgba & ~A | ((a & 0xFF) << 24);
		return this;
	}
	
	public ColorObject setRed(float r)
	{
		return setRed((int)(r * 255F + 0.5F));
	}
	
	public ColorObject setGreen(float g)
	{
		return setGreen((int)(g * 255F + 0.5F));
	}
	
	public ColorObject setBlue(float b)
	{
		return setBlue((int)(b * 255F + 0.5F));
	}
	
	public ColorObject setAlpha(float a)
	{
		return setAlpha((int)(a * 255F + 0.5F));
	}
	
	public ColorObject setRGB(float hue, float brightness, float saturation, int alpha)
	{
		return setRGB(Color.HSBtoRGB(hue, saturation, brightness) | alpha << 24);
	}
	
	public int getRGB()
	{
		return rgba;
	}
	
	public int getRed()
	{
		return (rgba >> 16) & 0xFF;
	}
	
	public int getGreen()
	{
		return (rgba >> 8) & 0xFF;
	}
	
	public int getBlue()
	{
		return rgba & 0xFF;
	}
	
	public int getAlpha()
	{
		return (rgba >> 24) & 0xFF;
	}
	
	public float getRedFloat()
	{
		return ((rgba >> 16) & 0xFF) * DEVIDER;
	}
	
	public float getGreenFloat()
	{
		return ((rgba >> 8) & 0xFF) * DEVIDER;
	}
	
	public float getBlueFloat()
	{
		return (rgba & 0xFF) * DEVIDER;
	}
	
	public float getAlphaFloat()
	{
		return ((rgba >> 24) & 0xFF) * DEVIDER;
	}
	
	public String getHexCode(boolean alpha)
	{
		return "0x"+(alpha ? Long.toHexString(1 << 32 | rgba & SIGN) : Integer.toHexString((1 << 24) | (rgba & ~A))).substring(1);
	}
	
	public String getHTMLCode(boolean alpha)
	{
		return "#"+(alpha ? Long.toHexString(1 << 32 | rgba & SIGN) : Integer.toHexString((1 << 24) | (rgba & ~A))).substring(1);
	}
	
	public boolean needsDarkColor()
	{
		return getBrightness() >= 130;
	}
	
	public int getBrightness()
	{
		return getBrightness(rgba);
	}
	
	public Color toColor()
	{
		return new Color(rgba);
	}
	
	public float[] toHue()
	{
		return Color.RGBtoHSB(getRed(), getGreen(), getBlue(), new float[3]);
	}
	
	public ColorObject brighter()
	{
		return brighter(0.7F);
	}
	
	public ColorObject brighter(float factor)
	{
		rgba = brighter(rgba, factor);
		return this;
	}
	
	public ColorObject darker()
	{
		return darker(0.7F);
	}
	
	public ColorObject darker(float factor)
	{
		rgba = darker(rgba, factor);
		return this;
	}
	
	public ColorObject mix(ColorObject to, float factor)
	{
		rgba = mix(rgba, to.rgba, factor);
		return this;
	}
	
	public ColorObject mix(ColorObject from, ColorObject to, float factor)
	{
		rgba = mix(from.rgba, to.rgba, factor);
		return this;
	}
	
	public ColorObject write(FloatBuffer buffer, boolean alpha)
	{
		buffer.put(getRedFloat()).put(getGreenFloat()).put(getBlueFloat());
		if(alpha)
		{
			buffer.put(getAlphaFloat());
		}
		return this;
	}
	
	public ColorObject read(FloatBuffer buffer, boolean alpha)
	{
		setRed(buffer.get()).setGreen(buffer.get()).setBlue(buffer.get());
		if(alpha)
		{
			setAlpha(buffer.get());
		}
		return this;
	}
	
	public ColorObject write(ByteBuffer buffer, boolean alpha)
	{
		pack(rgba, alpha, buffer);
		return this;
	}
	
	public ColorObject read(ByteBuffer buffer, boolean alpha)
	{
		setRed(buffer.get()).setGreen(buffer.get()).setBlue(buffer.get());
		if(alpha)
		{
			setAlpha(buffer.get());
		}
		return this;
	}
	
	public ColorObject write(FloatList buffer, boolean alpha)
	{
		buffer.add(getRedFloat());
		buffer.add(getGreenFloat());
		buffer.add(getBlueFloat());
		if(alpha)
		{
			buffer.add(getAlphaFloat());
		}
		return this;
	}
	
	public ColorObject writeFloat(ByteBuffer buffer, boolean alpha)
	{
		buffer.putFloat(getRedFloat()).putFloat(getGreenFloat()).putFloat(getBlueFloat());
		if(alpha)
		{
			buffer.putFloat(getAlphaFloat());
		}
		return this;
	}
	
	@Override
	public int hashCode()
	{
		return rgba;
	}
	
	@Override
	public boolean equals(Object obj)
	{
		if(obj instanceof ColorObject)
		{
			return ((ColorObject)obj).rgba == rgba;
		}
		return false;
	}
	
	@Override
	public String toString()
	{
		return "Color[r=" + getRed() + ", g=" + getGreen() + ", b=" + getBlue() + ", a=" + getAlpha() + "]";
	}
	
	public String getFloatColor()
	{
		return "Color[r=" + getRedFloat() + ", g=" + getGreenFloat() + ", b=" + getBlueFloat() + ", a=" + getAlphaFloat() + "]";
	}
	
	public static byte[] toByteArray(int color, boolean alpha)
	{
		byte[] data = new byte[alpha ? 4 : 3];
		data[0] = (byte)((color >> 16) & 0xFF);
		data[1] = (byte)((color >> 8) & 0xFF);
		data[2] = (byte)(color & 0xFF);
		if(alpha) data[3] = (byte)((color >> 24) & 0xFF);
		return data;
	}
	
	public static void pack(int color, boolean alpha, ByteBuffer buffer)
	{
		buffer.put((byte)((color >> 16) & 0xFF)).put((byte)((color >> 8) & 0xFF)).put((byte)(color & 0xFF));
		if(alpha)
		{
			buffer.put((byte)((color >> 24) & 0xFF));
		}
	}
	
	public static void packFloat(int color, boolean alpha, FloatBuffer buffer)
	{
		buffer.put(((color >> 16) & 0xFF) * DEVIDER).put(((color >> 8) & 0xFF) * DEVIDER).put((color & 0xFF) * DEVIDER);
		if(alpha)
		{
			buffer.put(((color >> 24) & 0xFF) * DEVIDER);
		}
	}
	
	public static int getBrightness(int rgba)
	{
		return getBrightness((rgba >> 16) & 0xFF, (rgba >> 8) & 0xFF, rgba & 0xFF);
	}
	
	public static int getBrightness(int r, int g, int b)
	{
		return (int)Math.sqrt((r * r * 0.241F) + (g * g * 0.691F) + (b * b * 0.068F));
	}
	
	public static int mix(int from, int to, float factor)
	{
	    float weight0 = (1F - factor);
	    float weight1 = factor;
	    int r = (int)((((from >> 16) & 0xFF) * weight0) + (((to >> 16) & 0xFF) * weight1));
	    int g = (int)((((from >> 8) & 0xFF) * weight0) + (((to >> 8) & 0xFF) * weight1));
	    int b = (int)(((from & 0xFF) * weight0) + ((to & 0xFF) * weight1));
	    int a = (int)((((from >> 24) & 0xFF) * weight0) + (((to >> 24) & 0xFF) * weight1));
	    return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | b & 0xFF;
	}
	
	public static int darker(int color, float factor)
	{
		int r = Math.max(0, (int)(((color >> 16) & 0xFF) * factor));
		int g = Math.max(0, (int)(((color >> 8) & 0xFF) * factor));
		int b = Math.max(0, (int)((color & 0xFF) * factor));
		return (color & A) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
	}
	
	public static int brighter(int color, float factor)
	{
		int r = (color >> 16) & 0xFF;
		int g = (color >> 8) & 0xFF;
		int b = color & 0xFF;
		int i = (int)(1.0 / (1.0 - factor));
		if(r == 0 && g == 0 && b == 0)
		{
			return (color & A) | ((i & 0xFF) << 16) | ((i & 0xFF) << 8) | (i & 0xFF);
		}
		if(r > 0 && r < i) r = i;
		if(g > 0 && g < i) g = i;
		if(b > 0 && b < i) b = i;
		return (color & A) | Math.min(255, (int)(r / factor)) << 16 | Math.min(255, (int)(g / factor)) << 8 | Math.min(255, (int)(b / factor));
	}
}
