// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::ops::Add;
use std::fmt::{Display, Formatter, Result as FmtResult};

use serde::{Serialize, Deserialize};

/**
 * A newtype struct representing the amount of ammo the player has.
 *
 * The purpose of this struct is to allow the player to have infinite ammo,
 * which is represented as u32::MAX. All other finite amounts are represented
 * by their value.
 */
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct AmmoCount(u32);

const MAX: u32 = u32::MAX - 1;
const INFINITY: u32 = u32::MAX;

impl AmmoCount {
	/**
	 * Returns a new ammo count.
	 *
	 * If amount = u32::MAX, the amount of ammo the player has is u32::MAX - 1,
	 * not an infinite amount.
	 */
	pub const fn new(amount: u32) -> AmmoCount {
		AmmoCount(if amount != INFINITY { amount } else { MAX })
	}

	/**
	 * Returns an ammo count representing an infinite amount of bullets.
	 */
	pub const fn new_infinite() -> AmmoCount {
		AmmoCount(INFINITY)
	}

	/**
	 * Decreases the ammo count by one if finite, does nothing if infinite.
	 *
	 * Panics if the ammo count is zero.
	 */
	pub fn decrease(&mut self) {
		self.0 = match self.0 {
			INFINITY => INFINITY,
			0 => panic!("cannot decrease ammo count when it's zero"),
			x => x - 1,
		}
	}

	/**
	 * Returns whether the ammo count is positive.
	 */
	pub fn is_positive(self) -> bool { self.0 > 0 }
}

impl From<AmmoCount> for f32 {
	fn from(count: AmmoCount) -> f32 {
		if count.0 == INFINITY { f32::INFINITY } else { count.0 as f32 }
	}
}

impl Add<u32> for AmmoCount {
	type Output = AmmoCount;

	fn add(self, rhs: u32) -> AmmoCount {
		if self.0 != INFINITY {
			AmmoCount((self.0 as u64 + rhs as u64).min(MAX as u64) as u32)
		} else {
			self
		}
	}
}

impl Display for AmmoCount {
	/**
	 * Returns a string representation of the ammo count that is guaranteed to
	 * be at most four characters long.
	 */
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		if self.0 < 10000 { write!(fmt, "{}", self.0) }
		else if self.0 == INFINITY { write!(fmt, "∞") }
		else {
			// Returns a number like XXeY
			// For example, 690,000 ≤ 69e4 < 700,000
			let (mut significand, mut exponent) = (self.0 / 1000, 3);
			while significand >= 100 {
				significand /= 10;
				exponent += 1;
			}
			write!(fmt, "{significand}e{exponent}")
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	fn a(count: u32) -> String { AmmoCount::new(count).to_string() }

	#[test]
	fn small() {
		assert_eq!(a(0), "0"); assert_eq!(a(1), "1"); assert_eq!(a(2), "2");
		assert_eq!(a(123), "123");
		assert_eq!(a(456), "456");
		assert_eq!(a(789), "789");
		assert_eq!(a(9999), "9999");
	}

	#[test]
	fn infinity() {
		assert_eq!(AmmoCount::new_infinite().to_string(), "∞");
	}

	#[test]
	fn large() {
		assert_eq!(a(10_000), "10e3"); assert_eq!(a(10_001), "10e3"); assert_eq!(a(10_999), "10e3");
		assert_eq!(a(11_000), "11e3");
		assert_eq!(a(12_345), "12e3");
		assert_eq!(a(99_999), "99e3");
		assert_eq!(a(100_000), "10e4");
		assert_eq!(a(690_000), "69e4");
		assert_eq!(a(696_969_696), "69e7");
	}
}
