#![deny(rustdoc::broken_intra_doc_links)]

use crate::{functions::FunctionErrors, kernels::RawScalarTrait, validation::StrictFinitePolicy};
use duplicate::duplicate_item;
use num::Complex;
use thiserror::Error;
use try_create::ValidationPolicy;

//------------------------------------------------------------------------------------------------
/// Errors that can occur during the input validation phase when attempting to compute
/// the exponential of a number.
///
/// This enum is used as a source for the [`Input`](FunctionErrors::Input) variant of [`ExpErrors`].
/// It is generic over `RawScalar: RawScalarTrait`, where `RawScalar` is the type of the
/// number for which the exponential is being computed. The `source` field in the
/// [`InvalidExponent`](ExpInputErrors::InvalidExponent) variant will be of type
/// `<RawScalar as RawScalarTrait>::ValidationErrors`.
#[derive(Debug, Error)]
pub enum ExpInputErrors<RawScalar: RawScalarTrait> {
    /// The input exponent failed validation according to the active policy.
    ///
    /// This error typically occurs if the input value for the exponential computation
    /// (the exponent) failed initial validation checks. For example, using
    /// [`StrictFinitePolicy`], this would
    /// trigger if the exponent is NaN, Infinity, or (for `f64`) subnormal.
    #[error("the input exponent is invalid according to validation policy")]
    // More descriptive
    InvalidExponent {
        /// The underlying validation error from the input type.
        ///
        /// This provides more specific details about why the input exponent
        /// was considered invalid by the validation policy. The type of this field
        /// is `<RawScalar as RawScalarTrait>::ValidationErrors`.
        #[source]
        #[backtrace]
        source: <RawScalar as RawScalarTrait>::ValidationErrors,
    },
}

/// Errors that can occur during the computation of the exponential of a real or complex number.
///
/// This type represents the possible failures when calling [`Exp::try_exp()`].
/// It is generic over `RawScalar: RawScalarTrait`. This type alias wraps [`FunctionErrors`],
/// where the input error source is [`ExpInputErrors<RawScalar>`] and the output
/// error source is `<RawScalar as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// - `Input`: Indicates that the input exponent was invalid for the exponential computation.
///   This could be due to failing initial validation (e.g., containing NaN or Infinity).
///   The `source` field provides more specific details via [`ExpInputErrors`].
///
/// - `Output`: Indicates that the computed exponential value itself failed validation.
///   This typically means the result of the `exp` operation yielded a non-finite value
///   (NaN or Infinity), or overflowed. The `source` field provides details,
///   usually an instance of [`ErrorsValidationRawReal`](crate::validation::ErrorsValidationRawReal)
///   or [`ErrorsValidationRawComplex`](crate::validation::ErrorsValidationRawComplex).
pub type ExpErrors<RawScalar> =
    FunctionErrors<ExpInputErrors<RawScalar>, <RawScalar as RawScalarTrait>::ValidationErrors>;

/// A trait for computing the exponential function (`e^x`).
///
/// This trait provides an interface for calculating the exponential of a number,
/// which can be real or complex. It includes both a fallible version (`try_exp`)
/// that performs validation and an infallible version (`exp`) that may panic
/// in debug builds if validation fails.
///
/// # Implementors
///
/// This trait is implemented for:
/// - `f64`
/// - `Complex<f64>`
/// - `RealRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
/// - `ComplexRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
///
/// The implementations use a [`StrictFinitePolicy`] for validating inputs and outputs,
/// meaning that NaN, Infinity, and subnormal numbers (for `f64`) will typically result
/// in an error or panic.
pub trait Exp: Sized {
    /// The error type that can be returned by the `try_exp` method.
    ///
    /// This is typically an instantiation of [`ExpErrors`].
    type Error: std::error::Error;

    /// Attempts to compute the exponential of `self` (`e^self`), returning a `Result`.
    fn try_exp(self) -> Result<Self, <Self as Exp>::Error>;

    /// Computes and returns the *exponential* of `self`.
    fn exp(self) -> Self;
}

#[duplicate_item(
    T;
    [f64];
    [Complex::<f64>];
)]
impl Exp for T {
    type Error = ExpErrors<Self>;

    /// Attempts to compute the exponential of `self` (`e^self`), returning a `Result`.
    ///
    /// This method first validates the input `self` using [`StrictFinitePolicy`].
    /// If the input is valid, it computes the exponential and then validates the result
    /// using the same policy.
    ///
    /// # Returns
    ///
    /// - `Ok(Self)`: If both the input and the computed exponential are valid (finite).
    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN, Infinity) or if the
    ///   computed exponential is invalid (e.g., NaN, Infinity, overflow).
    ///
    /// # Examples
    ///
    /// ```rust
    /// use num_valid::functions::Exp;
    /// use num::Complex;
    ///
    /// // For f64
    /// let x = 2.0_f64;
    /// match x.try_exp() {
    ///     Ok(val) => println!("e^{} = {}", x, val), // e^2.0 = 7.389...
    ///     Err(e) => println!("Error: {:?}", e),
    /// }
    ///
    /// assert!(f64::NAN.try_exp().is_err());
    ///
    /// // For Complex<f64>
    /// let z = Complex::new(1.0, std::f64::consts::PI); // e^(1 + iπ) = e * e^(iπ) = e * (-1) = -e
    /// match z.try_exp() {
    ///     Ok(val) => println!("e^({:?}) = {:?}", z, val), // e^Complex { re: 1.0, im: 3.14... } = Complex { re: -2.718..., im: tiny }
    ///     Err(e) => println!("Error: {:?}", e),
    /// }
    /// ```
    #[inline(always)]
    fn try_exp(self) -> Result<Self, Self::Error> {
        StrictFinitePolicy::<Self, 53>::validate(self)
            .map_err(|e| ExpInputErrors::InvalidExponent { source: e }.into())
            .and_then(|v| {
                StrictFinitePolicy::<Self, 53>::validate(T::exp(v))
                    .map_err(|e| Self::Error::Output { source: e })
            })
    }

    /// Computes and returns the exponential of `self` (`e^self`).
    ///
    /// # Behavior
    ///
    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_exp().unwrap()`.
    ///   It will panic if the input `self` is invalid (e.g., NaN, Infinity) or if the
    ///   computed exponential is invalid.
    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
    ///   exponential function directly (e.g., `f64::exp`, `num::Complex::exp`).
    ///   The behavior for non-finite inputs or outputs (like NaN propagation or overflow
    ///   resulting in Infinity) depends on the underlying implementation for the specific type.
    ///
    /// # Panics
    ///
    /// In debug builds, this method will panic if `try_exp()` would return an `Err`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use num_valid::functions::Exp;
    /// use num::Complex;
    ///
    /// let x = 1.0_f64;
    /// println!("e^{} = {}", x, x.exp()); // e^1.0 = 2.718...
    ///
    /// let z = Complex::new(0.0, std::f64::consts::PI / 2.0); // e^(iπ/2) = i
    /// println!("e^({:?}) = {:?}", z, z.exp()); // e^Complex { re: 0.0, im: 1.57... } = Complex { re: tiny, im: 1.0 }
    /// ```
    #[inline(always)]
    fn exp(self) -> Self {
        #[cfg(debug_assertions)]
        {
            self.try_exp().unwrap()
        }
        #[cfg(not(debug_assertions))]
        {
            T::exp(self)
        }
    }
}
//------------------------------------------------------------------------------------------------

//------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;
    use num::Complex;

    mod native64 {
        use super::*;

        mod real {
            use super::*;

            #[test]
            fn test_f64_exp_valid() {
                let value = 4.0;
                let expected_result = 54.598150033144236;
                assert_eq!(value.try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_f64_exp_negative() {
                let value = -4.0;
                let expected_result = 1.831563888873418e-02;
                assert_eq!(value.try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_f64_exp_zero() {
                let value = 0.0;
                assert_eq!(value.try_exp().unwrap(), 1.0);
                assert_eq!(value.exp(), 1.0);
            }

            #[test]
            fn test_f64_exp_nan() {
                let value = f64::NAN;
                let result = value.try_exp();
                assert!(matches!(result, Err(ExpErrors::<f64>::Input { .. })));
            }

            #[test]
            fn test_f64_exp_infinity() {
                let value = f64::INFINITY;
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<f64>::Input { .. })
                ));
            }

            #[test]
            fn test_f64_exp_subnormal() {
                let value = f64::MIN_POSITIVE / 2.0;
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<f64>::Input { .. })
                ));
            }

            #[test]
            fn test_f64_exp_output_overflow() {
                let value = 710.0; // f64::exp(710.0) is Inf
                let result = value.try_exp();
                assert!(matches!(result, Err(ExpErrors::<f64>::Output { .. })));
            }
        }

        mod complex {
            use super::*;

            #[test]
            fn test_complex_f64_exp_valid() {
                let value = Complex::new(4.0, 1.0);
                let expected_result = Complex::new(29.49950635904248, 45.94275907707917);
                assert_eq!(value.try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_complex_f64_exp_zero() {
                let value = Complex::new(0.0, 0.0);
                let expected_result = Complex::new(1.0, 0.0);
                assert_eq!(value.try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_complex_f64_exp_nan() {
                let value = Complex::new(f64::NAN, 0.0);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));

                let value = Complex::new(0.0, f64::NAN);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));
            }

            #[test]
            fn test_complex_f64_exp_infinity() {
                let value = Complex::new(f64::INFINITY, 0.0);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));

                let value = Complex::new(0.0, f64::INFINITY);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));
            }

            #[test]
            fn test_complex_f64_exp_subnormal() {
                let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));

                let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
                assert!(matches!(
                    value.try_exp(),
                    Err(ExpErrors::<Complex<f64>>::Input { .. })
                ));
            }

            #[test]
            fn test_complex_f64_exp_output_overflow_real() {
                let value = Complex::new(710.0, 0.0); // exp(value) has Inf real part
                let result = value.try_exp();
                assert!(matches!(
                    result,
                    Err(ExpErrors::<Complex<f64>>::Output { .. })
                ));
            }
        }
    }

    #[cfg(feature = "rug")]
    mod rug53 {
        use super::*;
        use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};
        use try_create::TryNew;

        mod real {
            use super::*;

            #[test]
            fn test_rug_float_exp_valid() {
                let value =
                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, -4.0)).unwrap();

                let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
                    53,
                    1.831563888873418e-2,
                ))
                .unwrap();
                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_rug_float_exp_zero() {
                let value =
                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();

                let expected_result =
                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();
                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }
        }

        mod complex {
            use super::*;

            #[test]
            fn test_complex_rug_float_exp_valid() {
                let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                    53,
                    (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 1.0)),
                ))
                .unwrap();

                let expected_result =
                    ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (
                            rug::Float::with_val(53, 29.49950635904248),
                            rug::Float::with_val(53, 45.94275907707917),
                        ),
                    ))
                    .unwrap();
                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }

            #[test]
            fn test_complex_rug_float_exp_zero() {
                let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                    53,
                    (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
                ))
                .unwrap();

                let expected_result =
                    ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
                        53,
                        (rug::Float::with_val(53, 1.), rug::Float::with_val(53, 0.)),
                    ))
                    .unwrap();
                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
                assert_eq!(value.exp(), expected_result);
            }
        }
    }
}
//------------------------------------------------------------------------------------------------
