#![deny(rustdoc::broken_intra_doc_links)]

use std::{cmp::Ordering, num::FpCategory};

/// Trait for clamping a value between minimum and maximum bounds.
///
/// This trait provides functionality to restrict a value to lie within a specified range.
/// If the value is less than the minimum, it returns the minimum. If greater than the
/// maximum, it returns the maximum. Otherwise, it returns the value unchanged.
///
/// # Examples
///
/// ```
/// use num_valid::functions::Clamp;
///
/// let result = Clamp::clamp(5.0f64, &0.0, &10.0);
/// assert_eq!(result, 5.0);
///
/// let result = Clamp::clamp(-5.0f64, &0.0, &10.0);
/// assert_eq!(result, 0.0);
///
/// let result = Clamp::clamp(15.0f64, &0.0, &10.0);
/// assert_eq!(result, 10.0);
/// ```
pub trait Clamp {
    /// Clamp the value within the specified bounds.
    ///
    /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`.
    /// Otherwise this returns `self`.
    ///
    /// Note that this function returns `NaN` if the initial value was `NaN` as well.
    ///
    /// # Panics
    /// Panics if `min` > `max`, `min` is `NaN`, or `max` is `NaN`.
    /// ```
    /// use num_valid::functions::Clamp;
    ///
    /// assert!(Clamp::clamp(-3.0f64, &-2.0, &1.0) == -2.0);
    /// assert!(Clamp::clamp(0.0f64, &-2.0, &1.0) == 0.0);
    /// assert!(Clamp::clamp(2.0f64, &-2.0, &1.0) == 1.0);
    /// assert!(Clamp::clamp(f64::NAN, &-2.0, &1.0).is_nan());
    /// ```
    fn clamp(self, min: &Self, max: &Self) -> Self;
}

/// Trait for classifying floating-point values into categories.
///
/// This trait provides functionality to determine the category of a floating-point number
/// according to the IEEE 754 standard (normal, subnormal, zero, infinite, or NaN).
///
/// # Examples
///
/// ```
/// use num_valid::functions::Classify;
/// use std::num::FpCategory;
///
/// let normal = 42.0f64;
/// assert_eq!(Classify::classify(&normal), FpCategory::Normal);
///
/// let infinity = f64::INFINITY;
/// assert_eq!(Classify::classify(&infinity), FpCategory::Infinite);
///
/// let zero = 0.0f64;
/// assert_eq!(Classify::classify(&zero), FpCategory::Zero);
/// ```
pub trait Classify {
    /// Returns the floating point category of the number. If only one property is going to be tested,
    /// it is generally faster to use the specific predicate instead.
    /// ```
    /// use num_valid::functions::Classify;
    /// use std::num::FpCategory;
    ///
    /// let num = 12.4_f64;
    /// let inf = f64::INFINITY;
    ///
    /// assert_eq!(Classify::classify(&num), FpCategory::Normal);
    /// assert_eq!(Classify::classify(&inf), FpCategory::Infinite);
    /// ```
    fn classify(&self) -> FpCategory;
}

/// Trait for computing `exp(x) - 1` with high precision for small values.
///
/// This trait provides the `exp_m1` function, which computes `e^x - 1` more accurately
/// than `exp(x) - 1` when `x` is close to zero. This avoids catastrophic cancellation
/// that occurs when subtracting two nearly equal numbers.
///
/// # Mathematical Background
///
/// For small values of `x`, computing `exp(x) - 1` directly can lose precision because
/// `exp(x)` is close to 1. The `exp_m1` function uses alternative algorithms (such as
/// Taylor series) that maintain accuracy in this regime.
///
/// # Examples
///
/// ```
/// use num_valid::functions::ExpM1;
///
/// let x = 1e-10f64;
/// let result = ExpM1::exp_m1(x);
/// // More accurate than: x.exp() - 1.0
/// assert!((result - x).abs() < 1e-15);
/// ```
pub trait ExpM1 {
    /// Returns `e^(self) - 1`` in a way that is accurate even if the number is close to zero.
    fn exp_m1(self) -> Self;
}

/// Trait for computing the Euclidean distance (hypotenuse) between two values.
///
/// This trait provides the `hypot` function, which computes `sqrt(x² + y²)` in a way
/// that avoids overflow and underflow for intermediate calculations. This is the length
/// of the hypotenuse of a right triangle with sides of length `|x|` and `|y|`.
///
/// # Mathematical Background
///
/// Computing `sqrt(x² + y²)` directly can overflow if `x` or `y` are very large,
/// or underflow if they are very small. The `hypot` function uses scaling techniques
/// to avoid these issues while maintaining numerical accuracy.
///
/// # Examples
///
/// ```
/// use num_valid::functions::Hypot;
///
/// let x = 3.0f64;
/// let y = 4.0f64;
/// let distance = Hypot::hypot(x, &y);
/// assert_eq!(distance, 5.0);
/// ```
pub trait Hypot {
    /// Compute the distance between the origin and a point (`self`, `other`) on the Euclidean plane.
    /// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `self.abs()` and `other.abs()`.
    fn hypot(self, other: &Self) -> Self;
}

/// Trait for computing `ln(1 + x)` with high precision for small values.
///
/// This trait provides the `ln_1p` function, which computes `ln(1 + x)` more accurately
/// than `ln(1.0 + x)` when `x` is close to zero. This avoids precision loss from adding
/// a small number to 1 and then taking the logarithm.
///
/// # Mathematical Background
///
/// For small values of `x`, computing `ln(1 + x)` directly loses precision because
/// `1 + x` rounds to 1 in floating-point arithmetic. The `ln_1p` function uses
/// alternative algorithms (such as Taylor series) that maintain accuracy for small `x`.
///
/// # Examples
///
/// ```
/// use num_valid::functions::Ln1p;
///
/// let x = 1e-10f64;
/// let result = Ln1p::ln_1p(x);
/// // More accurate than: (1.0 + x).ln()
/// assert!((result - x).abs() < 1e-15);
/// ```
pub trait Ln1p {
    /// Returns `ln(1. + self)` (natural logarithm) more accurately than if the operations were performed separately.
    fn ln_1p(self) -> Self;
}

/// Trait for total ordering comparison of floating-point values.
///
/// This trait provides a total ordering for floating-point numbers, including special
/// values like NaN and signed zeros. Unlike the standard `PartialOrd` trait, this
/// comparison always returns a definite ordering.
///
/// # Ordering Rules
///
/// The total ordering is defined as:
/// - Negative NaN < negative infinity < negative numbers < -0.0 < +0.0 < positive numbers < positive infinity < positive NaN
/// - NaN values are ordered by their bit representation
/// - -0.0 is ordered as less than +0.0
///
/// This matches the IEEE 754-2008 `totalOrder` predicate.
///
/// # Examples
///
/// ```
/// use num_valid::functions::TotalCmp;
/// use std::cmp::Ordering;
///
/// let a = -0.0f64;
/// let b = 0.0f64;
/// assert_eq!(TotalCmp::total_cmp(&a, &b), Ordering::Less);
///
/// let nan = f64::NAN;
/// let inf = f64::INFINITY;
/// assert_eq!(TotalCmp::total_cmp(&inf, &nan), Ordering::Less);
/// ```
pub trait TotalCmp {
    fn total_cmp(&self, other: &Self) -> Ordering;
}
