// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod table;
#[cfg(test)]
mod tests;

use std::f32::consts::TAU;
use std::ops::{Add, Sub, Mul, Div, AddAssign, Neg};

use serde::{Serialize, Deserialize, Serializer, Deserializer, de::Error as DeError};
use glam::Vec2;

#[cfg(feature = "client")] use crate::utils::maths::decay::Decay;

// INVARIANT: self.0 < COUNT
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(remote = "Self")] // Useful: https://github.com/serde-rs/serde/issues/1220
pub struct Direction(u16);

#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct Angle(f32);

pub(super) const BITS: u16 = 12;

const COUNT: u16 = 1 << BITS;
const MASK: u16 = COUNT - 1;
const FLIP_MASK: u16 = 1 << (BITS - 1); // XOR this to flip the direction

impl Direction {
	#[cfg(feature = "client")]
	pub(super) fn to_bits(self) -> u16 {
		self.0
	}

	pub(super) fn from_bits(data: u16) -> Result<Direction, String> {
		if data < COUNT {
			Ok(Direction(data))
		} else {
			Err(String::from("invalid direction"))
		}
	}

	pub fn from_degrees(degrees: f32) -> Direction {
		let angle = degrees / 360.0;
		let dir = ((angle - angle.floor()) * COUNT as f32).round() as u16;
		Direction(dir & MASK)
	}
}

impl Serialize for Direction {
	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
		Direction::serialize(self, s)
	}
}

impl<'de> Deserialize<'de> for Direction {
	fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Direction, D::Error> {
		let dir = Direction::deserialize(d)?;
		if dir.0 < COUNT {
			Ok(dir)
		} else {
			Err(DeError::custom("invalid direction"))
		}
	}
}

impl From<Vec2> for Direction {
	fn from(dir: Vec2) -> Direction {
		let angle = libm::atan2f(dir.y, dir.x);
		Direction((((angle / TAU + 1.0) * COUNT as f32).round() as u16) & MASK)
	}
}

impl Sub for Direction {
	type Output = Angle;

	fn sub(self, other: Direction) -> Angle {
		let angle = self.0 as f32 - other.0 as f32;
		if angle > COUNT as f32 / 2.0 {
			Angle(angle - COUNT as f32)
		} else if angle < -(COUNT as f32) / 2.0 {
			Angle(angle + COUNT as f32)
		} else {
			Angle(angle)
		}
	}
}

impl Neg for Direction {
	type Output = Direction;

	fn neg(self) -> Direction {
		Direction(self.0 ^ FLIP_MASK)
	}
}

impl Add<Angle> for Direction {
	type Output = Direction;

	fn add(self, angle: Angle) -> Direction {
		let dir_angle = self.0 as i32 + angle.0.round() as i32;
		Direction((dir_angle & MASK as i32) as u16)
	}
}

impl AddAssign for Angle {
	fn add_assign(&mut self, other: Angle) {
		self.0 += other.0;
	}
}

impl Add for Angle { type Output = Angle; fn add(self, other: Angle) -> Angle { Angle(self.0 + other.0) } }
impl Sub for Angle { type Output = Angle; fn sub(self, other: Angle) -> Angle { Angle(self.0 - other.0) } }
impl Mul<f32> for Angle { type Output = Angle; fn mul(self, scale: f32) -> Angle { Angle(self.0 * scale) } }
impl Div<f32> for Angle { type Output = Angle; fn div(self, scale: f32) -> Angle { Angle(self.0 / scale) } }

#[cfg(feature = "client")]
impl Decay for Angle {}
