// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::f32::consts::TAU;

use bincode::ErrorKind;
use glam::Vec2;

use super::{Direction, Angle, COUNT, MASK};

fn rand_dir() -> Direction {
	Direction(rand::random::<u16>() & MASK)
}

#[test]
fn dir_sub() {
	fn test(a: u16, b: u16, c: f32) {
		assert_eq!(Direction(a) - Direction(b), Angle(c));
	}

	test(1000, 200, 800.0);
	test(2000, 200, 1800.0);
	test(10, 20, -10.0);
	test(123, 456, -333.0);
	test(1, COUNT - 1, 2.0);
	test(COUNT - 1, 1, -2.0);
}

#[test]
fn dir_sub_opposite() {
	assert_eq!((Direction(COUNT / 2) - Direction(0)).0, COUNT as f32 / 2.0);
	for i in 1..10 {
		assert_eq!((Direction(COUNT / 2 + i) - Direction(0)).0, -(COUNT as f32) / 2.0 + i as f32);
	}
	assert_eq!((Direction(0) - Direction(COUNT / 2)).0, -(COUNT as f32) / 2.0);
}

#[test]
fn dir_sub_anticommutativity() {
	for _ in 0..10000 {
		let (a, b) = (rand_dir(), rand_dir());
		assert_eq!((a - b).0, -(b - a).0);
	}
}

#[test]
fn angle_add() {
	assert_eq!(Direction(5) + Angle(2.0), Direction(7));
	assert_eq!(Direction(5) + Angle(2.2), Direction(7));
	assert_eq!(Direction(5) + Angle(2.7), Direction(8));
	assert_eq!(Direction(0) + Angle(COUNT as f32 - 1.0), Direction(COUNT - 1));
	assert_eq!(Direction(1) + Angle(COUNT as f32 - 1.0), Direction(0));
	assert_eq!(Direction(1) + Angle(-1.0), Direction(0));
	assert_eq!(Direction(1) + Angle(-2.0), Direction(COUNT - 1));
	assert_eq!(Direction(1) + Angle(-69.0), Direction(COUNT - 68));
	assert_eq!(Direction(0) + Angle(-1234.0), Direction(COUNT - 1234));
	assert_eq!(Direction(COUNT - 1) + Angle(1.0), Direction(0));
	assert_eq!(Direction(COUNT - 1) + Angle(5.0), Direction(4));
}

#[test]
fn dir_sub_angle_add_undoes() {
	for _ in 0..10000 {
		let (a, b) = (rand_dir(), rand_dir());
		assert_eq!(b + (a - b), a);
	}
}

fn to_vec2_slow(dir: Direction) -> Vec2 {
	let angle = dir.0 as f32 / COUNT as f32 * TAU;
	Vec2::from_angle(angle)
}

#[test]
fn cache_works() {
	for i in 0..COUNT {
		let dir = Direction(i);
		let dist2 = Vec2::from(dir).distance_squared(to_vec2_slow(dir));
		assert!(dist2 < 1e-12); // Close enough
	}
}

#[test]
fn de_fails() {
	let invalid = Direction(COUNT);
	let data = bincode::serialize(&invalid).unwrap();
	let err = bincode::deserialize::<Direction>(&data).unwrap_err();
	assert!(matches!(*err, ErrorKind::Custom(_)));
}
