#region Copyright & License Information
/*
 * Copyright (c) The OpenRA Developers and Contributors
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version. For more
 * information, see COPYING.
 */
#endregion

using System;
using System.Globalization;
using OpenRA.Scripting;

namespace OpenRA.Primitives
{
	public readonly struct Color : IEquatable<Color>, IScriptBindable
	{
		readonly uint argb;

		public static Color FromArgb(int red, int green, int blue)
		{
			return FromArgb(byte.MaxValue, red, green, blue);
		}

		public static Color FromArgb(int alpha, int red, int green, int blue)
		{
			return new Color((uint)(((byte)alpha << 24) + ((byte)red << 16) + ((byte)green << 8) + (byte)blue));
		}

		public static Color FromAhsl(int alpha, float h, float s, float l)
		{
			// Convert HSL to HSV
			var v = l + s * Math.Min(l, 1 - l);
			var sv = v > 0 ? 2 * (1 - l / v) : 0;

			return FromAhsv(alpha, h, sv, v);
		}

		public static Color FromAhsv(int alpha, float h, float s, float v)
		{
			var (r, g, b) = HsvToRgb(h, s, v);
			return FromArgb(alpha, (byte)Math.Round(255 * r), (byte)Math.Round(255 * g), (byte)Math.Round(255 * b));
		}

		public static Color FromAhsv(float h, float s, float v)
		{
			return FromAhsv(255, h, s, v);
		}

		public (int A, float H, float S, float V) ToAhsv()
		{
			var (h, s, v) = RgbToHsv(R, G, B);
			return (A, h, s, v);
		}

		Color(uint argb)
		{
			this.argb = argb;
		}

		public uint ToArgb()
		{
			return argb;
		}

		public static Color FromArgb(int alpha, Color baseColor)
		{
			return FromArgb(alpha, baseColor.R, baseColor.G, baseColor.B);
		}

		public static Color FromArgb(uint argb)
		{
			return new Color(argb);
		}

		static float SrgbToLinear(float c)
		{
			// Standard gamma conversion equation: see e.g. https://entropymine.com/imageworsener/srgbformula/
			return c <= 0.04045f ? c / 12.92f : (float)Math.Pow((c + 0.055f) / 1.055f, 2.4f);
		}

		static float LinearToSrgb(float c)
		{
			// Standard gamma conversion equation: see e.g. https://entropymine.com/imageworsener/srgbformula/
			return c <= 0.0031308f ? c * 12.92f : 1.055f * (float)Math.Pow(c, 1.0f / 2.4f) - 0.055f;
		}

		public (float R, float G, float B) ToLinear()
		{
			// Undo pre-multiplied alpha and gamma correction
			var r = SrgbToLinear((float)R / A);
			var g = SrgbToLinear((float)G / A);
			var b = SrgbToLinear((float)B / A);

			return (r, g, b);
		}

		public static Color FromLinear(byte a, float r, float g, float b)
		{
			// Apply gamma correction and pre-multiplied alpha
			return FromArgb(a,
				(byte)Math.Round(LinearToSrgb(r) * a),
				(byte)Math.Round(LinearToSrgb(g) * a),
				(byte)Math.Round(LinearToSrgb(b) * a));
		}

		public static (float R, float G, float B) HsvToRgb(float h, float s, float v)
		{
			// Based on maths explained in http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
			var px = Math.Abs(h * 6f - 3);
			var py = Math.Abs((h + 2f / 3) % 1 * 6f - 3);
			var pz = Math.Abs((h + 1f / 3) % 1 * 6f - 3);

			var r = v * float2.Lerp(1f, (px - 1).Clamp(0, 1), s);
			var g = v * float2.Lerp(1f, (py - 1).Clamp(0, 1), s);
			var b = v * float2.Lerp(1f, (pz - 1).Clamp(0, 1), s);

			return (r, g, b);
		}

		public static (float H, float S, float V) RgbToHsv(byte r, byte g, byte b)
		{
			return RgbToHsv(r / 255f, g / 255f, b / 255f);
		}

		public static (float H, float S, float V) RgbToHsv(float r, float g, float b)
		{
			// Based on maths explained in http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
			var rgbMax = Math.Max(r, Math.Max(g, b));
			var rgbMin = Math.Min(r, Math.Min(g, b));
			var delta = rgbMax - rgbMin;
			var v = rgbMax;

			// Greyscale colors are defined to have hue and saturation 0
			if (delta == 0.0f)
				return (0, 0, v);

			float hue;
			if (r == rgbMax)
				hue = (g - b) / (6 * delta);
			else if (g == rgbMax)
				hue = (b - r) / (6 * delta) + 1 / 3f;
			else
				hue = (r - g) / (6 * delta) + 2 / 3f;

			var h = hue - (int)hue;

			// Wrap negative values into [0-1)
			if (h < 0)
				h++;

			var s = delta / rgbMax;
			return (h, s, v);
		}

		public static bool TryParse(string value, out Color color)
		{
			color = default;
			value = value.Trim();
			if (value.Length != 6 && value.Length != 8)
				return false;

			byte alpha = 255;
			if (!byte.TryParse(value.AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var red)
				|| !byte.TryParse(value.AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var green)
				|| !byte.TryParse(value.AsSpan(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var blue))
				return false;

			if (value.Length == 8
				&& !byte.TryParse(value.AsSpan(6, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out alpha))
				return false;

			color = FromArgb(alpha, red, green, blue);
			return true;
		}

		public static bool operator ==(Color left, Color right)
		{
			return left.argb == right.argb;
		}

		public static bool operator !=(Color left, Color right)
		{
			return !(left == right);
		}

		public float GetBrightness()
		{
			var min = Math.Min(R, Math.Min(G, B));
			var max = Math.Max(R, Math.Max(G, B));
			return (max + min) / 510f;
		}

		public byte A => (byte)(argb >> 24);
		public byte R => (byte)(argb >> 16);
		public byte G => (byte)(argb >> 8);
		public byte B => (byte)argb;

		public bool Equals(Color other)
		{
			return this == other;
		}

		public override bool Equals(object obj)
		{
			if (obj is not Color)
				return false;

			return this == (Color)obj;
		}

		public override int GetHashCode()
		{
			return (int)(argb ^ argb >> 32);
		}

		public override string ToString()
		{
			if (A == 255)
				return CryptoUtil.ToHex(stackalloc byte[3] { R, G, B });

			return CryptoUtil.ToHex(stackalloc byte[4] { R, G, B, A });
		}

		public static Color Transparent => FromArgb(0x00FFFFFF);
		public static Color AliceBlue => FromArgb(0xFFF0F8FF);
		public static Color AntiqueWhite => FromArgb(0xFFFAEBD7);
		public static Color Aqua => FromArgb(0xFF00FFFF);
		public static Color Aquamarine => FromArgb(0xFF7FFFD4);
		public static Color Azure => FromArgb(0xFFF0FFFF);
		public static Color Beige => FromArgb(0xFFF5F5DC);
		public static Color Bisque => FromArgb(0xFFFFE4C4);
		public static Color Black => FromArgb(0xFF000000);
		public static Color BlanchedAlmond => FromArgb(0xFFFFEBCD);
		public static Color Blue => FromArgb(0xFF0000FF);
		public static Color BlueViolet => FromArgb(0xFF8A2BE2);
		public static Color Brown => FromArgb(0xFFA52A2A);
		public static Color BurlyWood => FromArgb(0xFFDEB887);
		public static Color CadetBlue => FromArgb(0xFF5F9EA0);
		public static Color Chartreuse => FromArgb(0xFF7FFF00);
		public static Color Chocolate => FromArgb(0xFFD2691E);
		public static Color Coral => FromArgb(0xFFFF7F50);
		public static Color CornflowerBlue => FromArgb(0xFF6495ED);
		public static Color Cornsilk => FromArgb(0xFFFFF8DC);
		public static Color Crimson => FromArgb(0xFFDC143C);
		public static Color Cyan => FromArgb(0xFF00FFFF);
		public static Color DarkBlue => FromArgb(0xFF00008B);
		public static Color DarkCyan => FromArgb(0xFF008B8B);
		public static Color DarkGoldenrod => FromArgb(0xFFB8860B);
		public static Color DarkGray => FromArgb(0xFFA9A9A9);
		public static Color DarkGreen => FromArgb(0xFF006400);
		public static Color DarkKhaki => FromArgb(0xFFBDB76B);
		public static Color DarkMagenta => FromArgb(0xFF8B008B);
		public static Color DarkOliveGreen => FromArgb(0xFF556B2F);
		public static Color DarkOrange => FromArgb(0xFFFF8C00);
		public static Color DarkOrchid => FromArgb(0xFF9932CC);
		public static Color DarkRed => FromArgb(0xFF8B0000);
		public static Color DarkSalmon => FromArgb(0xFFE9967A);
		public static Color DarkSeaGreen => FromArgb(0xFF8FBC8B);
		public static Color DarkSlateBlue => FromArgb(0xFF483D8B);
		public static Color DarkSlateGray => FromArgb(0xFF2F4F4F);
		public static Color DarkTurquoise => FromArgb(0xFF00CED1);
		public static Color DarkViolet => FromArgb(0xFF9400D3);
		public static Color DeepPink => FromArgb(0xFFFF1493);
		public static Color DeepSkyBlue => FromArgb(0xFF00BFFF);
		public static Color DimGray => FromArgb(0xFF696969);
		public static Color DodgerBlue => FromArgb(0xFF1E90FF);
		public static Color Firebrick => FromArgb(0xFFB22222);
		public static Color FloralWhite => FromArgb(0xFFFFFAF0);
		public static Color ForestGreen => FromArgb(0xFF228B22);
		public static Color Fuchsia => FromArgb(0xFFFF00FF);
		public static Color Gainsboro => FromArgb(0xFFDCDCDC);
		public static Color GhostWhite => FromArgb(0xFFF8F8FF);
		public static Color Gold => FromArgb(0xFFFFD700);
		public static Color Goldenrod => FromArgb(0xFFDAA520);
		public static Color Gray => FromArgb(0xFF808080);
		public static Color Green => FromArgb(0xFF008000);
		public static Color GreenYellow => FromArgb(0xFFADFF2F);
		public static Color Honeydew => FromArgb(0xFFF0FFF0);
		public static Color HotPink => FromArgb(0xFFFF69B4);
		public static Color IndianRed => FromArgb(0xFFCD5C5C);
		public static Color Indigo => FromArgb(0xFF4B0082);
		public static Color Ivory => FromArgb(0xFFFFFFF0);
		public static Color Khaki => FromArgb(0xFFF0E68C);
		public static Color Lavender => FromArgb(0xFFE6E6FA);
		public static Color LavenderBlush => FromArgb(0xFFFFF0F5);
		public static Color LawnGreen => FromArgb(0xFF7CFC00);
		public static Color LemonChiffon => FromArgb(0xFFFFFACD);
		public static Color LightBlue => FromArgb(0xFFADD8E6);
		public static Color LightCoral => FromArgb(0xFFF08080);
		public static Color LightCyan => FromArgb(0xFFE0FFFF);
		public static Color LightGoldenrodYellow => FromArgb(0xFFFAFAD2);
		public static Color LightGray => FromArgb(0xFFD3D3D3);
		public static Color LightGreen => FromArgb(0xFF90EE90);
		public static Color LightPink => FromArgb(0xFFFFB6C1);
		public static Color LightSalmon => FromArgb(0xFFFFA07A);
		public static Color LightSeaGreen => FromArgb(0xFF20B2AA);
		public static Color LightSkyBlue => FromArgb(0xFF87CEFA);
		public static Color LightSlateGray => FromArgb(0xFF778899);
		public static Color LightSteelBlue => FromArgb(0xFFB0C4DE);
		public static Color LightYellow => FromArgb(0xFFFFFFE0);
		public static Color Lime => FromArgb(0xFF00FF00);
		public static Color LimeGreen => FromArgb(0xFF32CD32);
		public static Color Linen => FromArgb(0xFFFAF0E6);
		public static Color Magenta => FromArgb(0xFFFF00FF);
		public static Color Maroon => FromArgb(0xFF800000);
		public static Color MediumAquamarine => FromArgb(0xFF66CDAA);
		public static Color MediumBlue => FromArgb(0xFF0000CD);
		public static Color MediumOrchid => FromArgb(0xFFBA55D3);
		public static Color MediumPurple => FromArgb(0xFF9370DB);
		public static Color MediumSeaGreen => FromArgb(0xFF3CB371);
		public static Color MediumSlateBlue => FromArgb(0xFF7B68EE);
		public static Color MediumSpringGreen => FromArgb(0xFF00FA9A);
		public static Color MediumTurquoise => FromArgb(0xFF48D1CC);
		public static Color MediumVioletRed => FromArgb(0xFFC71585);
		public static Color MidnightBlue => FromArgb(0xFF191970);
		public static Color MintCream => FromArgb(0xFFF5FFFA);
		public static Color MistyRose => FromArgb(0xFFFFE4E1);
		public static Color Moccasin => FromArgb(0xFFFFE4B5);
		public static Color NavajoWhite => FromArgb(0xFFFFDEAD);
		public static Color Navy => FromArgb(0xFF000080);
		public static Color OldLace => FromArgb(0xFFFDF5E6);
		public static Color Olive => FromArgb(0xFF808000);
		public static Color OliveDrab => FromArgb(0xFF6B8E23);
		public static Color Orange => FromArgb(0xFFFFA500);
		public static Color OrangeRed => FromArgb(0xFFFF4500);
		public static Color Orchid => FromArgb(0xFFDA70D6);
		public static Color PaleGoldenrod => FromArgb(0xFFEEE8AA);
		public static Color PaleGreen => FromArgb(0xFF98FB98);
		public static Color PaleTurquoise => FromArgb(0xFFAFEEEE);
		public static Color PaleVioletRed => FromArgb(0xFFDB7093);
		public static Color PapayaWhip => FromArgb(0xFFFFEFD5);
		public static Color PeachPuff => FromArgb(0xFFFFDAB9);
		public static Color Peru => FromArgb(0xFFCD853F);
		public static Color Pink => FromArgb(0xFFFFC0CB);
		public static Color Plum => FromArgb(0xFFDDA0DD);
		public static Color PowderBlue => FromArgb(0xFFB0E0E6);
		public static Color Purple => FromArgb(0xFF800080);
		public static Color Red => FromArgb(0xFFFF0000);
		public static Color RosyBrown => FromArgb(0xFFBC8F8F);
		public static Color RoyalBlue => FromArgb(0xFF4169E1);
		public static Color SaddleBrown => FromArgb(0xFF8B4513);
		public static Color Salmon => FromArgb(0xFFFA8072);
		public static Color SandyBrown => FromArgb(0xFFF4A460);
		public static Color SeaGreen => FromArgb(0xFF2E8B57);
		public static Color SeaShell => FromArgb(0xFFFFF5EE);
		public static Color Sienna => FromArgb(0xFFA0522D);
		public static Color Silver => FromArgb(0xFFC0C0C0);
		public static Color SkyBlue => FromArgb(0xFF87CEEB);
		public static Color SlateBlue => FromArgb(0xFF6A5ACD);
		public static Color SlateGray => FromArgb(0xFF708090);
		public static Color Snow => FromArgb(0xFFFFFAFA);
		public static Color SpringGreen => FromArgb(0xFF00FF7F);
		public static Color SteelBlue => FromArgb(0xFF4682B4);
		public static Color Tan => FromArgb(0xFFD2B48C);
		public static Color Teal => FromArgb(0xFF008080);
		public static Color Thistle => FromArgb(0xFFD8BFD8);
		public static Color Tomato => FromArgb(0xFFFF6347);
		public static Color Turquoise => FromArgb(0xFF40E0D0);
		public static Color Violet => FromArgb(0xFFEE82EE);
		public static Color Wheat => FromArgb(0xFFF5DEB3);
		public static Color White => FromArgb(0xFFFFFFFF);
		public static Color WhiteSmoke => FromArgb(0xFFF5F5F5);
		public static Color Yellow => FromArgb(0xFFFFFF00);
		public static Color YellowGreen => FromArgb(0xFF9ACD32);
	}
}
