//! Logarithmic, exponential, and root functions.
//!
//! - `ln`   — natural logarithm (base e)
//! - `log`  — common logarithm (base 10)
//! - `log2` — binary logarithm (base 2)
//! - `exp`  — e raised to a power
//! - `pow`  — base raised to an exponent (2 args)
//! - `sqrt` — square root
//! - `cbrt` — cube root

use super::{FunctionError, FunctionRegistry};

fn ln_fn(args: &[f64]) -> Result<f64, FunctionError> {
    let x = args[0];
    if x <= 0.0 {
        return Err(FunctionError::new(
            "Argument out of domain for ln (must be positive)",
        ));
    }
    Ok(x.ln())
}

fn log_fn(args: &[f64]) -> Result<f64, FunctionError> {
    let x = args[0];
    if x <= 0.0 {
        return Err(FunctionError::new(
            "Argument out of domain for log (must be positive)",
        ));
    }
    Ok(x.log10())
}

fn log2_fn(args: &[f64]) -> Result<f64, FunctionError> {
    let x = args[0];
    if x <= 0.0 {
        return Err(FunctionError::new(
            "Argument out of domain for log2 (must be positive)",
        ));
    }
    Ok(x.log2())
}

fn exp_fn(args: &[f64]) -> Result<f64, FunctionError> {
    Ok(args[0].exp())
}

fn pow_fn(args: &[f64]) -> Result<f64, FunctionError> {
    Ok(args[0].powf(args[1]))
}

fn sqrt_fn(args: &[f64]) -> Result<f64, FunctionError> {
    let x = args[0];
    if x < 0.0 {
        return Err(FunctionError::new(
            "Argument out of domain for sqrt (must be non-negative)",
        ));
    }
    Ok(x.sqrt())
}

fn cbrt_fn(args: &[f64]) -> Result<f64, FunctionError> {
    Ok(args[0].cbrt())
}

/// Register all logarithmic/exponential/root functions.
pub fn register(reg: &mut FunctionRegistry) {
    reg.register_fixed("ln", 1, ln_fn);
    reg.register_fixed("log", 1, log_fn);
    reg.register_fixed("log2", 1, log2_fn);
    reg.register_fixed("exp", 1, exp_fn);
    reg.register_fixed("pow", 2, pow_fn);
    reg.register_fixed("sqrt", 1, sqrt_fn);
    reg.register_fixed("cbrt", 1, cbrt_fn);
}

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

    fn reg() -> FunctionRegistry {
        FunctionRegistry::new()
    }

    // --- ln ---

    #[test]
    fn ln_one_is_zero() {
        let v = reg().call("ln", &[1.0]).unwrap();
        assert!(v.abs() < 1e-10);
    }

    #[test]
    fn ln_e_is_one() {
        let v = reg().call("ln", &[std::f64::consts::E]).unwrap();
        assert!((v - 1.0).abs() < 1e-10);
    }

    #[test]
    fn ln_zero_domain_error() {
        let err = reg().call("ln", &[0.0]).unwrap_err();
        assert!(err.message.contains("out of domain"));
    }

    #[test]
    fn ln_negative_domain_error() {
        let err = reg().call("ln", &[-1.0]).unwrap_err();
        assert!(err.message.contains("out of domain"));
    }

    // --- log (base 10) ---

    #[test]
    fn log_100_is_2() {
        let v = reg().call("log", &[100.0]).unwrap();
        assert!((v - 2.0).abs() < 1e-10);
    }

    #[test]
    fn log_1000_is_3() {
        let v = reg().call("log", &[1000.0]).unwrap();
        assert!((v - 3.0).abs() < 1e-10);
    }

    #[test]
    fn log_negative_domain_error() {
        let err = reg().call("log", &[-1.0]).unwrap_err();
        assert!(err.message.contains("out of domain"));
    }

    // --- log2 ---

    #[test]
    fn log2_256_is_8() {
        let v = reg().call("log2", &[256.0]).unwrap();
        assert!((v - 8.0).abs() < 1e-10);
    }

    #[test]
    fn log2_one_is_zero() {
        let v = reg().call("log2", &[1.0]).unwrap();
        assert!(v.abs() < 1e-10);
    }

    #[test]
    fn log2_negative_domain_error() {
        let err = reg().call("log2", &[-5.0]).unwrap_err();
        assert!(err.message.contains("out of domain"));
    }

    // --- exp ---

    #[test]
    fn exp_zero_is_one() {
        let v = reg().call("exp", &[0.0]).unwrap();
        assert!((v - 1.0).abs() < 1e-10);
    }

    #[test]
    fn exp_one_is_e() {
        let v = reg().call("exp", &[1.0]).unwrap();
        assert!((v - std::f64::consts::E).abs() < 1e-10);
    }

    // --- pow ---

    #[test]
    fn pow_2_10_is_1024() {
        let v = reg().call("pow", &[2.0, 10.0]).unwrap();
        assert!((v - 1024.0).abs() < 1e-10);
    }

    #[test]
    fn pow_3_0_is_1() {
        let v = reg().call("pow", &[3.0, 0.0]).unwrap();
        assert!((v - 1.0).abs() < 1e-10);
    }

    // --- sqrt ---

    #[test]
    fn sqrt_144_is_12() {
        let v = reg().call("sqrt", &[144.0]).unwrap();
        assert!((v - 12.0).abs() < 1e-10);
    }

    #[test]
    fn sqrt_2_approx() {
        let v = reg().call("sqrt", &[2.0]).unwrap();
        assert!((v - std::f64::consts::SQRT_2).abs() < 1e-10);
    }

    #[test]
    fn sqrt_zero_is_zero() {
        let v = reg().call("sqrt", &[0.0]).unwrap();
        assert!(v.abs() < 1e-10);
    }

    #[test]
    fn sqrt_negative_domain_error() {
        let err = reg().call("sqrt", &[-4.0]).unwrap_err();
        assert!(err.message.contains("out of domain"));
    }

    // --- cbrt ---

    #[test]
    fn cbrt_27_is_3() {
        let v = reg().call("cbrt", &[27.0]).unwrap();
        assert!((v - 3.0).abs() < 1e-10);
    }

    #[test]
    fn cbrt_8_is_2() {
        let v = reg().call("cbrt", &[8.0]).unwrap();
        assert!((v - 2.0).abs() < 1e-10);
    }

    #[test]
    fn cbrt_neg_8_is_neg_2() {
        let v = reg().call("cbrt", &[-8.0]).unwrap();
        assert!((v - (-2.0)).abs() < 1e-10);
    }

    // --- composition ---

    #[test]
    fn ln_exp_roundtrip() {
        let r = reg();
        let inner = r.call("exp", &[5.0]).unwrap();
        let v = r.call("ln", &[inner]).unwrap();
        assert!((v - 5.0).abs() < 1e-10);
    }

    #[test]
    fn exp_ln_roundtrip() {
        let r = reg();
        let inner = r.call("ln", &[10.0]).unwrap();
        let v = r.call("exp", &[inner]).unwrap();
        assert!((v - 10.0).abs() < 1e-10);
    }

    #[test]
    fn sqrt_pow_roundtrip() {
        let r = reg();
        let inner = r.call("pow", &[3.0, 2.0]).unwrap();
        let v = r.call("sqrt", &[inner]).unwrap();
        assert!((v - 3.0).abs() < 1e-10);
    }
}
