//! Hurst Exponent estimator functions for Rust
//!
//! Originally based on evrom/hurst (GPL-3.0), forked to MIT for license compatibility.
//! This provides core R/S (Rescaled Range) analysis for Hurst exponent estimation.
//!
//! # License Resolution
//!
//! Issue #96 Task #149/150: GPL-3.0 license conflict resolution.
//! The external evrom/hurst crate (v0.1.0) was GPL-3.0 licensed, blocking PyPI distribution.
//! This internal fork enables MIT-licensed Hurst calculations without GPL restrictions.
//!
//! # Examples
//!
//! ```
//! # use opendeviationbar_hurst::rssimple;
//! let prices = vec![100.0, 101.0, 99.5, 102.0, 101.5];
//! let hurst = rssimple(&prices);
//! assert!(hurst >= 0.0 && hurst <= 1.0);
//! ```

use linreg::linear_regression;

pub mod dfa;
pub mod fbm;
pub mod ghe;
pub mod higuchi;
pub mod iaaft;
pub(crate) mod utils;
pub mod wavelet;

pub use dfa::{dfa, dfa_order};
pub use fbm::{generate_fbm, generate_fbm_davies_harte};
pub use ghe::{ghe, ghe_q1, ghe_q2};
pub use higuchi::hurst_higuchi;
pub use iaaft::iaaft_surrogate;
pub use wavelet::hurst_wavelet;

use utils::*;

/// Simple R/S Hurst estimation
///
/// Computes the Hurst exponent using basic Rescaled Range analysis.
/// Issue #96: Optimized from 5 passes + 2 Vec allocations to 2 passes + 0 allocations.
///
/// # Arguments
///
/// * `x` - Input time series data (prices or returns)
///
/// # Returns
///
/// Hurst exponent value, typically in range [0, 1]
pub fn rssimple(x: &[f64]) -> f64 {
    let n = x.len();
    if n == 0 {
        return 0.0;
    }
    let n_f64 = n as f64;
    let inv_n = 1.0 / n_f64;

    // Pass 1: mean
    let x_mean: f64 = x.iter().sum::<f64>() * inv_n;

    // Pass 2: fused cumsum-minmax + variance (zero allocations)
    // Simultaneously tracks:
    // - Running cumulative sum of deviations (for R/S range)
    // - Min/max of cumulative sum (for rescaled range)
    // - Sum of squared deviations (for standard deviation)
    let mut cumsum = 0.0_f64;
    let mut cum_min = 0.0_f64;
    let mut cum_max = 0.0_f64;
    let mut sum_sq = 0.0_f64;

    for &val in x {
        let d = val - x_mean;
        cumsum += d;
        cum_min = cum_min.min(cumsum);
        cum_max = cum_max.max(cumsum);
        sum_sq += d * d;
    }

    let std_dev = (sum_sq * inv_n).sqrt();
    if std_dev < f64::EPSILON {
        return 0.5; // Constant series
    }

    let rs = (cum_max - cum_min) / std_dev;
    if rs < f64::EPSILON {
        return 0.5;
    }

    rs.log2() / n_f64.log2()
}

/// Corrected R over S Hurst exponent
///
/// Computes Hurst exponent with interval averaging correction for improved stability.
///
/// # Arguments
///
/// * `x` - Input time series data
///
/// # Returns
///
/// Corrected Hurst exponent value, typically in range [0, 1]
pub fn rs_corrected(x: Vec<f64>) -> f64 {
    let mut cap_x: Vec<f64> = vec![x.len() as f64];
    let mut cap_y: Vec<f64> = vec![rscalc(&x)];
    let mut n: Vec<u64> = vec![0, x.len() as u64 / 2, x.len() as u64];

    // Issue #96: Pre-allocate loop Vecs outside the while loop (3-8 iterations = 6-16 allocations saved)
    let mut xl: Vec<f64> = Vec::with_capacity(16);
    let mut yl: Vec<f64> = Vec::with_capacity(16);

    // compute averaged R/S for halved intervals
    while n[1] >= 8 {
        xl.clear();
        yl.clear();
        for i in 1..n.len() {
            let rs: f64 = rscalc(&x[((n[i - 1] + 1) as usize)..(n[i] as usize)]);
            xl.push((n[i] - n[i - 1]) as f64);
            yl.push(rs);
        }
        cap_x.push(mean(&xl));
        cap_y.push(mean(&yl));
        // next step
        n = half(&n, x.len() as u64);
    }

    // Memory v2: In-place log transform (cap_x/cap_y not used after)
    for v in &mut cap_x {
        *v = v.ln();
    }
    for v in &mut cap_y {
        *v = v.ln();
    }
    let (slope, _): (f64, f64) = linear_regression(&cap_x, &cap_y).unwrap();
    slope
}

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

    #[test]
    fn test_rssimple_trending_series() {
        // Monotonically increasing → H > 0.5 (trending)
        let prices: Vec<f64> = (0..100).map(|i| 100.0 + i as f64 * 0.5).collect();
        let h = rssimple(&prices);
        assert!(h.is_finite(), "Hurst must be finite: {}", h);
        assert!(h > 0.4, "Trending series should have H > 0.4: {}", h);
    }

    #[test]
    fn test_rssimple_constant_series() {
        let prices = vec![100.0; 50];
        let h = rssimple(&prices);
        assert_eq!(h, 0.5, "Constant series → H = 0.5");
    }

    #[test]
    fn test_rssimple_empty() {
        let h = rssimple(&[]);
        assert_eq!(h, 0.0, "Empty series → 0.0");
    }

    #[test]
    fn test_rssimple_single_element() {
        let h = rssimple(&[100.0]);
        // Single element: std_dev = 0 → returns 0.5
        assert_eq!(h, 0.5, "Single element → H = 0.5");
    }

    #[test]
    fn test_rssimple_alternating_series() {
        // Mean-reverting: H < 0.5
        let prices: Vec<f64> = (0..200)
            .map(|i| if i % 2 == 0 { 100.0 } else { 101.0 })
            .collect();
        let h = rssimple(&prices);
        assert!(h.is_finite(), "Hurst must be finite: {}", h);
        assert!(h < 0.6, "Alternating series should have low H: {}", h);
    }

    #[test]
    fn test_rssimple_five_elements_doc_example() {
        let prices = vec![100.0, 101.0, 99.5, 102.0, 101.5];
        let h = rssimple(&prices);
        assert!(
            h >= 0.0 && h <= 1.5,
            "Hurst should be in reasonable range: {}",
            h
        );
    }

    #[test]
    fn test_rssimple_two_elements() {
        let h = rssimple(&[100.0, 200.0]);
        assert!(h.is_finite(), "Two-element series must be finite: {}", h);
    }

    #[test]
    fn test_rssimple_nan_in_series() {
        // NaN values should not cause panic, but result may be NaN/non-finite
        let prices = vec![100.0, f64::NAN, 102.0, 101.0, 99.0];
        let h = rssimple(&prices);
        // Just ensure no panic — NaN propagation is acceptable
        let _ = h;
    }

    /// Issue #96: Bit-exact oracle — rs_corrected() must produce identical output after Vec pre-allocation.
    /// Ground truth captured BEFORE refactoring and hardcoded as f64::to_bits() values.
    #[test]
    fn test_rs_corrected_bit_exact_oracle() {
        // Input 1: 64 linearly spaced (trending)
        let input64: Vec<f64> = (0..64).map(|i| 100.0 + i as f64 * 0.5).collect();
        let h64 = rs_corrected(input64);
        assert_eq!(
            h64.to_bits(),
            0x3ff1_1399_b240_ce05_u64,
            "DIVERGENCE rs_corrected(64 linear): got bits={:#018x} value={h64}",
            h64.to_bits()
        );

        // Input 2: 256 trending
        let input256: Vec<f64> = (0..256).map(|i| 50.0 + i as f64 * 0.1).collect();
        let h256 = rs_corrected(input256);
        assert_eq!(
            h256.to_bits(),
            0x3ff0_9a08_1fca_7747_u64,
            "DIVERGENCE rs_corrected(256 trend): got bits={:#018x} value={h256}",
            h256.to_bits()
        );

        // Input 3: 128 alternating (mean-reverting)
        let input128: Vec<f64> = (0..128)
            .map(|i| if i % 2 == 0 { 100.0 } else { 101.0 })
            .collect();
        let h128 = rs_corrected(input128);
        assert_eq!(
            h128.to_bits(),
            0xbfc3_5905_2b48_ed40_u64,
            "DIVERGENCE rs_corrected(128 alt): got bits={:#018x} value={h128}",
            h128.to_bits()
        );
    }

    #[test]
    fn test_rssimple_negative_prices() {
        // Negative prices (e.g., futures spread) should work
        let prices: Vec<f64> = (0..50).map(|i| -10.0 + i as f64 * 0.1).collect();
        let h = rssimple(&prices);
        assert!(
            h.is_finite(),
            "Negative prices must produce finite H: {}",
            h
        );
    }
}
