// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use serde::{Serialize, Deserialize};

#[cfg(feature = "client")] use glam::{Vec3, Vec4};
#[cfg(feature = "client")] use egui::Color32;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Colour([u8; 3]);

const fn hex(n: u32) -> Colour {
	Colour([(n >> 16) as u8, (n >> 8) as u8, n as u8])
}

impl From<[u8; 3]> for Colour {
	fn from(colour: [u8; 3]) -> Colour {
		Colour(colour)
	}
}

impl TryFrom<&str> for Colour {
	type Error = &'static str;

	fn try_from(s: &str) -> Result<Colour, &'static str> {
		Ok(match s {
			"red" => Colour::RED,
			"orange" => hex(0xff6f1f),
			"yellow" => hex(0xffff1f),
			"green" => hex(0x3fff1f),
			"cyan" => hex(0x1fffff),
			"blue" => hex(0x3f6fff),
			"purple" => hex(0x7f1fff),
			"magenta" => hex(0xff1fff),
			"pink" => hex(0xff1fbf),

			"white" => hex(0xffffff),
			"lightgrey" => hex(0xbfbfbf),
			"grey" => hex(0x7f7f7f),
			"darkgrey" => hex(0x3f3f3f),
			"black" => hex(0x000000),

			_ => {
				let hex = s.strip_prefix("#").ok_or("unknown colour")?;
				if hex.len() != 6 {
					return Err("expected six hexadecimal digits");
				}

				let mut colour = [0; 3];
				for (i, x) in colour.iter_mut().enumerate() {
					let i = i * 2;
					*x = hex.get(i..i + 2).and_then(|hex| u8::from_str_radix(hex, 16).ok()).ok_or("invalid hexadecimal")?;
				}

				Colour(colour)
			},
		})
	}
}

impl Default for Colour {
	fn default() -> Colour {
		Colour::RED
	}
}

impl From<Colour> for [u8; 3] {
	fn from(colour: Colour) -> [u8; 3] {
		colour.0
	}
}

impl From<Colour> for [f32; 3] {
	fn from(colour: Colour) -> [f32; 3] {
		colour.0.map(|x| x as f32 / 255.0)
	}
}

#[cfg(feature = "client")]
impl From<Colour> for Vec3 {
	fn from(colour: Colour) -> Vec3 {
		Vec3::from_array(colour.into())
	}
}

impl Colour {
	pub const RED: Colour = hex(0xff3f1f);
	pub const WHITE: Colour = hex(0xffffff);

	pub fn op(self, f: impl Fn(u8) -> u8) -> Colour {
		Colour(self.0.map(f))
	}

	pub fn with_alpha(self, alpha: u8) -> ColourAlpha {
		ColourAlpha([self.0[0], self.0[1], self.0[2], alpha])
	}

	#[cfg(feature = "client")]
	pub fn min_intensity(self, min_int: f32) -> Color32 {
		let colour = self.min_intensity_vec3(min_int);
		Color32::from_rgb((colour.x * 255.0) as u8, (colour.y * 255.0) as u8, (colour.z * 255.0) as u8)
	}

	#[cfg(feature = "client")]
	pub fn name_colour_dark(self) -> Color32 {
		self.min_intensity(0.5)
	}

	#[cfg(feature = "client")]
	pub fn name_colour_less_dark(dark: Color32) -> Color32 {
		Color32::from_rgb(((dark.r() as u32 * 7) / 8) as u8 + 0x20, ((dark.g() as u32 * 7) / 8) as u8 + 0x20, ((dark.b() as u32 * 7) / 8) as u8 + 0x20)
	}

	#[cfg(feature = "client")]
	pub fn name_colour_normal(dark: Color32) -> Color32 {
		Color32::from_rgb(dark.r() / 2 + 0x80, dark.g() / 2 + 0x80, dark.b() / 2 + 0x80)
	}

	/**
	 * The scores and chat messages are displayed with the player's colour. This
	 * function makes sure that the colours are bright enough to be easily
	 * readable on the usually dark background.
	 *
	 * This function takes the input colour and returns a colour whose perceived
	 * intensity is at least the minimum provided.
	 *
	 * Note that the perceived intensity here is important as #00ff00 appears
	 * much brighter than #0000ff as the human eye is much more sensitive to
	 * green light than blue light.
	 */
	#[cfg(feature = "client")]
	fn min_intensity_vec3(self, min_int: f32) -> Vec3 {
		fn intensity(colour: Vec3) -> f32 {
			// Constants from https://stackoverflow.com/a/596243
			const X: f32 = 0.299;
			const Y: f32 = 0.587;
			const Z: f32 = 0.114;

			X * colour.x + Y * colour.y + Z * colour.z
		}

		// 1. If the intensity is already sufficient, return the original colour
		let mut colour = Vec3::from(self);
		let mut int = intensity(colour);
		if int >= min_int { return colour; }

		// 2. Increase brightness to reach minimum
		if int > 0.0 { // Avoiding division by zero
			let k = min_int / int; // Multiplier to reach sufficient intensity

			colour *= k;
			int *= k;

			let max = colour.max_element();
			if max > 1.0 {
				colour /= max;
				int /= max;
			} else {
				return colour; // Threshold met and within bounds
			}
		}

		// 3. Decrease saturation to reach minimum
		let t = (min_int - int) / (1.0 - int); // Required to get an intensity of `min_int`
		colour.lerp(Vec3::ONE, t)
	}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ColourAlpha([u8; 4]);

impl From<[u8; 4]> for ColourAlpha {
	fn from(colour: [u8; 4]) -> ColourAlpha {
		ColourAlpha(colour)
	}
}

impl From<ColourAlpha> for [f32; 4] {
	fn from(colour: ColourAlpha) -> [f32; 4] {
		colour.0.map(|x| x as f32 / 255.0)
	}
}

impl From<Colour> for ColourAlpha {
	fn from(colour: Colour) -> ColourAlpha {
		colour.with_alpha(0xff)
	}
}

#[cfg(feature = "client")]
impl From<ColourAlpha> for Vec4 {
	fn from(colour: ColourAlpha) -> Vec4 {
		Vec4::from_array(colour.into())
	}
}

impl ColourAlpha {
	pub fn fully_transparent(self) -> bool {
		self.0[3] == 0
	}
}
