// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
pub mod glam_fix;
#[cfg(feature = "client")] pub mod decay;

use std::mem;
#[cfg(feature = "client")] use std::f32::consts::TAU;

use glam::Vec2;
use serde::Deserialize;

/**
 * Adds `rotation` to `angle`, wrapping around if outside [0, 2π]. This prevents
 * loss of floating-point precision if the angle ever gets large enough
 * (unlikely for this to ever happen but possible).
 *
 * **Doesn't check if the angle decreases below zero.**
 */
#[cfg(feature = "client")]
pub fn add_angle(mut angle: f32, rotation: f32) -> f32 {
	angle += rotation;
	if angle >= TAU {
		angle -= (angle / TAU).floor() * TAU;
	}
	angle
}

/**
 * Linearly interpolates between `p0` and `p1` by `t`. Note that lerp(p0, p1,
 * 1) isn't guaranteed to be p1 due to possible floating-point error.
 */
pub fn lerp(p0: f32, p1: f32, t: f32) -> f32 {
	p0 + (p1 - p0) * t
}

pub fn bilerp(p00: f32, p10: f32, p01: f32, p11: f32, x: f32, y: f32) -> f32 {
	lerp(lerp(p00, p10, x), lerp(p01, p11, x), y)
}

pub fn smoothstep(t: f32) -> f32 {
	t * t * (3.0 - 2.0 * t)
}

#[cfg(feature = "client")]
pub fn limit_dt(dt: f32) -> f32 {
	dt.max(1.0 / 500.0) // Prevents division by zero
}

pub trait Rect {
	// Requires p0 ≤ p1
	fn get_p0(&self) -> Vec2;
	fn get_p1(&self) -> Vec2;
}

pub trait Circle {
	fn get_pos(&self) -> Vec2;
	fn get_radius(&self) -> f32;
}

impl Circle for (Vec2, f32) {
	fn get_pos(&self) -> Vec2 { self.0 }
	fn get_radius(&self) -> f32 { self.1 }
}

pub trait CollidingRect {
	fn colliding_rect(&self, other: &impl Rect) -> bool;
}

impl CollidingRect for RectCorners {
//impl<T> CollidingRect for T where T: Rect { // Would like to do this but the compiler complains
	fn colliding_rect(&self, other: &impl Rect) -> bool {
		let (p0, p1) = (self.get_p0(), self.get_p1());
		let (other_p0, other_p1) = (other.get_p0(), other.get_p1());
		p1.x > other_p0.x && p0.x < other_p1.x && p1.y > other_p0.y && p0.y < other_p1.y
	}
}

impl<T> CollidingRect for T where T: Circle {
	/*
	 * Rectangle-circle collision detection algorithm. This works by checking if
	 * the circle's centre is inside the rectangle, or is outside it but within
	 * distance radius of it.
	 */
	fn colliding_rect(&self, other: &impl Rect) -> bool {
		// Using a wrapper to avoid unnecessary code size with monomorphisation
		fn colliding(pos: Vec2, radius: f32, p0: Vec2, p1: Vec2) -> bool {
			// Checks the first two rectangles
			let (suf_right, suf_left) = (pos.x > p0.x, pos.x < p1.x); // If sufficiently right or left
			if suf_right && suf_left && pos.y > p0.y - radius && pos.y < p1.y + radius {
				return true;
			}

			let (suf_up, suf_down) = (pos.y > p0.y, pos.y < p1.y);
			if suf_up && suf_down && pos.x > p0.x - radius && pos.x < p1.x + radius {
				return true;
			}

			// Now checking a corner
			// Using the previous results to avoid searching all four
			let corner = match (suf_right, suf_up) {
				(false, false) => p0,
				( true, false) => Vec2::new(p1.x, p0.y),
				(false,  true) => Vec2::new(p0.x, p1.y),
				( true,  true) => p1,
			};

			pos.distance_squared(corner) < radius * radius
		}

		colliding(self.get_pos(), self.get_radius(), other.get_p0(), other.get_p1())
	}
}

pub trait CollidingCircle {
	fn colliding_circle(&self, other: &impl Circle) -> bool;
}

impl<T> CollidingCircle for T where T: Circle {
	fn colliding_circle(&self, other: &impl Circle) -> bool {
		let sum_r = self.get_radius() + other.get_radius();
		self.get_pos().distance_squared(other.get_pos()) < sum_r * sum_r
	}
}

pub trait ContainingRect {
	/**
	 * Returns whether `self` fully contains the `other` rect. In other words,
	 * all points inside `other` are inside `self`.
	 */
	fn contains_rect(&self, other: &impl Rect) -> bool;
}

impl ContainingRect for RectCorners {
	fn contains_rect(&self, other: &impl Rect) -> bool {
		let (p0, p1) = (self.get_p0(), self.get_p1());
		let (other_p0, other_p1) = (other.get_p0(), other.get_p1());
		other_p0.x >= p0.x && other_p1.x <= p1.x && other_p0.y >= p0.y && other_p1.y <= p1.y
	}
}

#[derive(Clone, Copy, Deserialize)]
#[serde(from = "RectCornersUnchecked")]
pub struct RectCorners(Vec2, Vec2);

impl RectCorners {
	pub fn new(mut p0: Vec2, mut p1: Vec2) -> RectCorners {
		for i in 0..2 {
			if p0[i] > p1[i] {
				mem::swap(&mut p0[i], &mut p1[i]);
			}
		}

		RectCorners(p0, p1)
	}

	pub fn new_unchecked(p0: Vec2, p1: Vec2) -> RectCorners {
		RectCorners(p0, p1)
	}

	pub fn inner(&self) -> (Vec2, Vec2) {
		(self.0, self.1)
	}
}

impl Rect for RectCorners {
	fn get_p0(&self) -> Vec2 { self.0 }
	fn get_p1(&self) -> Vec2 { self.1 }
}

#[derive(Deserialize)]
struct RectCornersUnchecked(Vec2, Vec2);

impl From<RectCornersUnchecked> for RectCorners {
	fn from(rect: RectCornersUnchecked) -> RectCorners {
		RectCorners::new(rect.0, rect.1)
	}
}
