// Copyright 2026 Gabriel Bjørnager Jensen.
//
// This file is part of LOOKLOOK.
//
// LOOKLOOK is free software: you can redistribute
// it and/or modify it under the terms of the GNU
// Affero General Public License as published by
// the Free Software Foundation, either version 3
// of the License, or (at your option) any later
// version.
//
// LOOKLOOK is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Af-
// fero General Public License along with LOOKLOOK.
// If not, see <https://www.gnu.org/licenses/>.

//! The [`FrameRate`] type.

use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter};
use std::hint::assert_unchecked;
use std::mem::transmute;

/// A frame rate.
#[repr(transparent)]
#[derive(Copy)]
#[derive_const(Clone)]
pub struct FrameRate(u32);

impl FrameRate {
	/// Constructs a new frame rate.
	///
	/// The provided value is measured in hertz.
	///
	/// If the provided value is zero, greater than
	/// `8589934592`, or greater than [`usize::MAX`],
	/// this constructor returns [`None`].
	#[inline]
	#[must_use]
	pub const fn new(value: u64) -> Option<Self> {
		if value >= 1 && value <= 2_u64.pow(u32::BITS) && value.bit_width() <= usize::BITS {
			let value = unsafe { Self::new_unchecked(value) };

			Some(value)
		} else {
			None
		}
	}

	/// Unsafely constructs a new frame rate.
	///
	/// # Safety
	///
	/// The provided value must be non-zero and less
	/// than or equal to `8589934592` and less than
	/// [`usize::MAX`].
	#[inline(always)]
	#[must_use]
	#[track_caller]
	pub const unsafe fn new_unchecked(mut value: u64) -> Self {
		unsafe { assert_unchecked(value <= 2_u64.pow(u32::BITS)) };

		// SAFETY: The only case where this would underflow
		// is where `value == 0`, which the caller specifi-
		// cally guarantees is not the case.
		value = unsafe { value.unchecked_sub(1) };

		// NOTE: Optimiser should see `value <= u32::MAX`.
		let value = value.truncate();

		unsafe { transmute::<u32, Self>(value) }
	}

	/// Retrieves the frame rate as [`u64`], meaasured
	/// in hertz.
	#[inline]
	#[must_use]
	pub const fn as_u64(self) -> u64 {
		u64::from(self.0) + 1
	}

	/// Retrieves the frame rate as [`usize`], meaasured
	/// in hertz.
	#[inline]
	#[must_use]
	pub const fn as_usize(self) -> usize {
		// NOTE: We always guarantee that the value is less
		// than `usize::MAX`.
		self.as_u64() as usize
	}
}

impl Debug for FrameRate {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(&self.as_u64(), f)
	}
}

const impl Default for FrameRate {
	#[inline]
	fn default() -> Self {
		const { Self::new(25).unwrap() }
	}
}

impl Display for FrameRate {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Display::fmt(&self.as_u64(), f)?;
		f.write_str("Hz")?;

		Ok(())
	}
}

const impl Eq for FrameRate {}

const impl Ord for FrameRate {
	#[inline]
	fn cmp(&self, other: &Self) -> Ordering {
		self.as_u64().cmp(&other.as_u64())
	}
}

const impl PartialEq for FrameRate {
	#[inline]
	fn eq(&self, other: &Self) -> bool {
		self.as_u64().eq(&other.as_u64())
	}
}

const impl PartialOrd for FrameRate {
	#[inline]
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		self.as_u64().partial_cmp(&other.as_u64())
	}
}
