// Copyright 2025 Gabriel Bjørnager Jensen.

mod seal {
	pub trait Float { }
}

use crate::{Array, Uint};

use core::cmp::Ordering;
use core::fmt::Debug;
use core::ops::{
	Add,
	AddAssign,
	Div,
	DivAssign,
	Mul,
	MulAssign,
	Neg,
	Rem,
	RemAssign,
	Sub,
	SubAssign,
};

pub(crate) use seal::Float as SealedFloat;

/// A generic, floating-point real.
///
/// This trait abstracts the interfaces defined by [`f16`], [`f32`], [`f64`], and [`f128`] so that these may be used as a single type.
///
#[cfg_attr(feature = "std", doc = "See also [`StdFloat`](crate::StdFloat).\n")]
///
/// # Safety
///
/// [`Bits`](Self::Bits) must be an unsigned, integral type of the same width as `Self` so that transmutations between the two is always valid.
///
/// Furthermore, all items must behave exactly as their standard counterparts.
pub unsafe trait Float
where
	Self:
		SealedFloat
		+ Copy
		+ Debug
		+ Default
		//+ Display  // Not for `f128`.
		//+ LowerExp // Not for `f128`.
		+ PartialEq
		+ PartialOrd
		//+ Product  // Not for `f128`.
		//+ Sum      // Not for `f128`.
		//+ UpperExp // Not for `f128`.
		// Operators:
		+ Add<Output = Self>
		+ Div<Output = Self>
		+ Mul<Output = Self>
		+ Neg<Output = Self>
		+ Rem<Output = Self>
		+ Sub<Output = Self>
		// Assignement operators:
		+ AddAssign
		+ DivAssign
		+ MulAssign
		+ RemAssign
		+ SubAssign,
{
	/// The array type that represents the bytes of this type.
	type Bytes: Array<Scalar = u8>;

	/// The scalar type that represents the bits of this type.
	type Bits: Uint;

	/// See [`f64::RADIX`].
	const RADIX:           u32;

	/// See [`f64::MANTISSA_DIGITS`].
	const MANTISSA_DIGITS: u32;

	/// See [`f64::DIGITS`].
	const DIGITS:          u32;

	/// See [`f64::EPSILON`].
	const EPSILON:         Self;

	/// See [`f64::MIN`].
	const MIN:             Self;

	/// See [`f64::MIN_POSITIVE`].
	const MIN_POSITIVE:    Self;

	/// See [`f64::MAX`].
	const MAX:             Self;

	/// See [`f64::INFINITY`].
	const INFINITY:        Self;

	/// See [`f64::NEG_INFINITY`].
	const NEG_INFINITY:    Self;

	/// See [`f64::NAN`].
	const NAN:             Self;

	/// See [`f64::E`].
	const E:               Self;

	/// See [`f64::PI`].
	const PI:              Self;

	/// See [`f64::TAU`].
	const TAU:             Self;

	/// See [`f64::LOG2_10`].
	const LOG2_10:         Self;

	/// See [`f64::LOG2_E`].
	const LOG2_E:          Self;

	/// See [`f64::LN_2`].
	const LN_2:            Self;

	/// See [`f64::LN_10`].
	const LN_10:           Self;

	/// See [`f64::LOG10_2`].
	const LOG10_2:         Self;

	/// See [`f64::LOG10_E`].
	const LOG10_E:         Self;

	/// See [`f64::SQRT_2`].
	const SQRT_2:          Self;

	/// See [`f64::FRAC_1_PI`].
	const FRAC_1_PI:       Self;

	/// See [`f64::FRAC_1_SQRT_2`].
	const FRAC_1_SQRT_2:   Self;

	/// See [`f64::FRAC_2_PI`].
	const FRAC_2_PI:       Self;

	/// See [`f64::FRAC_2_SQRT_PI`].
	const FRAC_2_SQRT_PI:  Self;

	/// See [`f64::FRAC_PI_2`].
	const FRAC_PI_2:       Self;

	/// See [`f64::FRAC_PI_3`].
	const FRAC_PI_3:       Self;

	/// See [`f64::FRAC_PI_4`].
	const FRAC_PI_4:       Self;

	/// See [`f64::FRAC_PI_6`].
	const FRAC_PI_6:       Self;

	/// See [`f64::FRAC_PI_8`].
	const FRAC_PI_8:       Self;

	/// Constructs a floating-point number from an [`f16`] value.
	#[cfg(feature = "f16")]
	#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "f16")))]
	#[must_use]
	fn from_f16(value: f16) -> Self;

	/// Lossily constructs a floating-point number from an [`f64`] value.
	#[must_use]
	fn from_f64_lossy(value: f64) -> Self;

	/// Lossily constructs a floating-point number from an [`f128`] value.
	#[cfg(feature = "f128")]
	#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "f128")))]
	#[must_use]
	fn from_f128_lossy(value: f128) -> Self;

	/// See [`f64::from_ne_bytes`].
	#[must_use]
	fn from_ne_bytes(bytes: Self::Bytes) -> Self;

	/// See [`f64::from_le_bytes`].
	#[must_use]
	fn from_le_bytes(bytes: Self::Bytes) -> Self;

	/// See [`f64::from_be_bytes`].
	#[must_use]
	fn from_be_bytes(bytes: Self::Bytes) -> Self;

	/// See [`f64::from_bits`].
	#[must_use]
	fn from_bits(bits: Self::Bits) -> Self;

	/// See [`f64::mul_add`].
	#[must_use]
	fn mul_add(self, a: Self, b: Self) -> Self;

	/// See [`f64::recip`].
	#[must_use]
	fn recip(self) -> Self;

	/// See [`f64::round`].
	#[must_use]
	fn round(self) -> Self;

	/// See [`f64::round_ties_even`].
	#[must_use]
	fn round_ties_even(self) -> Self;

	/// See [`f64::floor`].
	#[must_use]
	fn floor(self) -> Self;

	/// See [`f64::ceil`].
	#[must_use]
	fn ceil(self) -> Self;

	/// See [`f64::trunc`].
	#[must_use]
	fn trunc(self) -> Self;

	/// See [`f64::fract`].
	#[must_use]
	fn fract(self) -> Self;

	/// See [`f64::to_radians`].
	#[must_use]
	fn to_radians(self) -> Self;

	/// See [`f64::to_degrees`].
	#[must_use]
	fn to_degrees(self) -> Self;

	/// See [`f64::next_down`].
	#[must_use]
	fn next_down(self) -> Self;

	/// See [`f64::next_up`].
	#[must_use]
	fn next_up(self) -> Self;

	/// See [`f64::is_nan`].
	#[must_use]
	fn is_nan(self) -> bool;

	/// See [`f64::is_infinite`].
	#[must_use]
	fn is_infinite(self) -> bool;

	/// See [`f64::is_finite`].
	#[must_use]
	fn is_finite(self) -> bool;

	/// See [`f64::is_normal`].
	#[must_use]
	fn is_normal(self) -> bool;

	/// See [`f64::is_subnormal`].
	#[must_use]
	fn is_subnormal(self) -> bool;

	/// See [`f64::is_sign_negative`].
	#[must_use]
	fn is_sign_negative(self) -> bool;

	/// See [`f64::is_sign_positive`].
	#[must_use]
	fn is_sign_positive(self) -> bool;

	/// See [`f64::total_cmp`].
	#[must_use]
	fn total_cmp(&self, rhs: &Self) -> Ordering;

	/// See [`f64::to_ne_bytes`].
	#[must_use]
	fn to_ne_bytes(self) -> Self::Bytes;

	/// See [`f64::to_le_bytes`].
	#[must_use]
	fn to_le_bytes(self) -> Self::Bytes;

	/// See [`f64::to_be_bytes`].
	#[must_use]
	fn to_be_bytes(self) -> Self::Bytes;

	/// See [`f64::to_bits`].
	#[must_use]
	fn to_bits(self) -> Self::Bits;
}

macro_rules! impl_float {
	{
		$($ty:ty {
			Bytes = $bytes:ty,
			Bits  = $bits:ty,

			consts = $consts:ident$(,)?
		} $(= $feature:literal)?),*$(,)?
	} => {
		$(
			$(
				#[cfg(feature = $feature)]
			)?
			impl ::multitype::SealedFloat for $ty { }

			$(
				#[cfg(feature = $feature)]
				#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = $feature)))]
			)?
			unsafe impl ::multitype::Float for $ty {
				type Bytes = $bytes;

				type Bits = $bits;

				const RADIX:           ::core::primitive::u32 = Self::RADIX;

				const MANTISSA_DIGITS: ::core::primitive::u32 = Self::MANTISSA_DIGITS;
				const DIGITS:          ::core::primitive::u32 = Self::DIGITS;

				const EPSILON:         Self = Self::EPSILON;

				const MIN:             Self = Self::MIN;
				const MIN_POSITIVE:    Self = Self::MIN_POSITIVE;
				const MAX:             Self = Self::MAX;

				const INFINITY:        Self = Self::INFINITY;
				const NEG_INFINITY:    Self = Self::NEG_INFINITY;

				const NAN:             Self = Self::NAN;

				const E:               Self = ::core::$consts::consts::E;
				const PI:              Self = ::core::$consts::consts::PI;
				const TAU:             Self = ::core::$consts::consts::TAU;

				const LOG2_10:         Self = ::core::$consts::consts::LOG2_10;
				const LOG2_E:          Self = ::core::$consts::consts::LOG2_E;
				const LN_2:            Self = ::core::$consts::consts::LN_2;
				const LN_10:           Self = ::core::$consts::consts::LN_10;
				const LOG10_2:         Self = ::core::$consts::consts::LOG10_2;
				const LOG10_E:         Self = ::core::$consts::consts::LOG10_E;

				const SQRT_2:          Self = ::core::$consts::consts::SQRT_2;

				const FRAC_1_PI:       Self = ::core::$consts::consts::FRAC_1_PI;
				const FRAC_1_SQRT_2:   Self = ::core::$consts::consts::FRAC_1_SQRT_2;
				const FRAC_2_PI:       Self = ::core::$consts::consts::FRAC_2_PI;
				const FRAC_2_SQRT_PI:  Self = ::core::$consts::consts::FRAC_2_SQRT_PI;
				const FRAC_PI_2:       Self = ::core::$consts::consts::FRAC_PI_2;
				const FRAC_PI_3:       Self = ::core::$consts::consts::FRAC_PI_3;
				const FRAC_PI_4:       Self = ::core::$consts::consts::FRAC_PI_4;
				const FRAC_PI_6:       Self = ::core::$consts::consts::FRAC_PI_6;
				const FRAC_PI_8:       Self = ::core::$consts::consts::FRAC_PI_8;

				#[cfg(feature = "f16")]
				#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "f16")))]
				#[inline(always)]
				#[track_caller]
				fn from_f16(value: f16) -> Self {
					value as Self
				}

				#[inline(always)]
				#[track_caller]
				fn from_f64_lossy(value: f64) -> Self {
					value as Self
				}

				#[cfg(feature = "f128")]
				#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "f128")))]
				#[inline(always)]
				#[track_caller]
				fn from_f128_lossy(value: f128) -> Self {
					value as Self
				}

				#[inline(always)]
				#[track_caller]
				fn from_ne_bytes(bytes: Self::Bytes) -> Self {
					Self::from_ne_bytes(bytes)
				}

				#[inline(always)]
				#[track_caller]
				fn from_le_bytes(bytes: Self::Bytes) -> Self {
					Self::from_le_bytes(bytes)
				}

				#[inline(always)]
				#[track_caller]
				fn from_be_bytes(bytes: Self::Bytes) -> Self {
					Self::from_be_bytes(bytes)
				}

				#[inline(always)]
				#[track_caller]
				fn from_bits(bits: Self::Bits) -> Self {
					Self::from_bits(bits)
				}

				#[inline(always)]
				#[track_caller]
				fn mul_add(self, a: Self, b: Self) -> Self {
					self.mul_add(a, b)
				}

				#[inline(always)]
				#[track_caller]
				fn recip(self) -> Self {
					self.recip()
				}

				#[inline(always)]
				fn round(self) -> Self {
					self.round()
				}

				#[inline(always)]
				fn round_ties_even(self) -> Self {
					self.round_ties_even()
				}

				#[inline(always)]
				fn floor(self) -> Self {
					self.floor()
				}

				#[inline(always)]
				fn ceil(self) -> Self {
					self.ceil()
				}

				#[inline(always)]
				fn trunc(self) -> Self {
					self.trunc()
				}

				#[inline(always)]
				fn fract(self) -> Self {
					self.fract()
				}

				#[inline(always)]
				#[track_caller]
				fn to_radians(self) -> Self {
					self.to_radians()
				}

				#[inline(always)]
				#[track_caller]
				fn to_degrees(self) -> Self {
					self.to_radians()
				}

				#[inline(always)]
				#[track_caller]
				fn next_down(self) -> Self {
					self.next_up()
				}

				#[inline(always)]
				#[track_caller]
				fn next_up(self) -> Self {
					self.next_up()
				}

				#[inline(always)]
				#[track_caller]
				fn is_nan(self) -> bool {
					self.is_nan()
				}

				#[inline(always)]
				#[track_caller]
				fn is_infinite(self) -> bool {
					self.is_infinite()
				}

				#[inline(always)]
				#[track_caller]
				fn is_finite(self) -> bool {
					self.is_finite()
				}

				#[inline(always)]
				#[track_caller]
				fn is_normal(self) -> bool {
					self.is_normal()
				}

				#[inline(always)]
				#[track_caller]
				fn is_subnormal(self) -> bool {
					self.is_subnormal()
				}

				#[inline(always)]
				#[track_caller]
				fn is_sign_negative(self) -> bool {
					self.is_sign_negative()
				}

				#[inline(always)]
				#[track_caller]
				fn is_sign_positive(self) -> bool {
					self.is_sign_positive()
				}

				#[inline(always)]
				#[track_caller]
				fn total_cmp(&self, rhs: &Self) -> ::core::cmp::Ordering {
					self.total_cmp(&rhs)
				}

				#[inline(always)]
				#[track_caller]
				fn to_ne_bytes(self) -> Self::Bytes {
					self.to_ne_bytes()
				}

				#[inline(always)]
				#[track_caller]
				fn to_le_bytes(self) -> Self::Bytes {
					self.to_le_bytes()
				}

				#[inline(always)]
				#[track_caller]
				fn to_be_bytes(self) -> Self::Bytes {
					self.to_be_bytes()
				}

				#[inline(always)]
				#[track_caller]
				fn to_bits(self) -> Self::Bits {
					self.to_bits()
				}
			}
		)*
	};
}

impl_float! {
	f16  { Bytes = [u8; 0x02], Bits = u16,  consts = f16  } = "f16",
	f32  { Bytes = [u8; 0x04], Bits = u32,  consts = f32  },
	f64  { Bytes = [u8; 0x08], Bits = u64,  consts = f64  },
	f128 { Bytes = [u8; 0x10], Bits = u128, consts = f128 } = "f128",
}
