//! Higuchi Fractal Dimension method for Hurst exponent estimation
//!
//! Reference: Higuchi (1988), "Approach to an irregular time series on the basis
//! of the fractal theory"
//!
//! Computes fractal dimension D from curve length scaling, then converts to
//! Hurst exponent: H = 2 - D.
//!
//! Well-studied for short time series (works with n ≥ 10).

use linreg::linear_regression;

/// Estimate Hurst exponent via Higuchi fractal dimension.
///
/// H = 2 - D, where D is the fractal dimension from curve length scaling.
///
/// # Arguments
/// * `x` - Input time series
///
/// # Returns
/// Hurst exponent estimate
pub fn hurst_higuchi(x: &[f64]) -> f64 {
    let n = x.len();
    if n < 10 {
        return 0.5;
    }

    let k_max = (n / 4).min(16).max(2);

    let mut log_k_inv = Vec::with_capacity(k_max);
    let mut log_l = Vec::with_capacity(k_max);

    for k in 1..=k_max {
        // For each k, compute L(k) = mean of L_m(k) over m = 1..k
        let mut l_sum = 0.0;
        let mut valid_m = 0;

        for m in 1..=k {
            let num_segments = (n - m) / k;
            if num_segments == 0 {
                continue;
            }

            // L_m(k) = (n-1) / (floor((n-m)/k) * k) * Σ|x[m + i*k] - x[m + (i-1)*k]|
            let mut abs_sum = 0.0;
            for i in 1..=num_segments {
                let idx_curr = m + i * k - 1; // 0-indexed: m-1 + i*k
                let idx_prev = m + (i - 1) * k - 1;
                if idx_curr < n && idx_prev < n {
                    abs_sum += (x[idx_curr] - x[idx_prev]).abs();
                }
            }

            let normalization = (n - 1) as f64 / (num_segments * k) as f64;
            let l_m_k = abs_sum * normalization / k as f64;
            l_sum += l_m_k;
            valid_m += 1;
        }

        if valid_m > 0 {
            let l_k = l_sum / valid_m as f64;
            if l_k > 0.0 {
                log_k_inv.push((1.0 / k as f64).ln());
                log_l.push(l_k.ln());
            }
        }
    }

    if log_k_inv.len() < 3 {
        return 0.5;
    }

    // Linear regression of log(L(k)) vs log(1/k) → slope = D (fractal dimension)
    // H = 2 - D
    let result: Result<(f64, f64), _> = linear_regression(&log_k_inv, &log_l);
    match result {
        Ok((slope, _)) => {
            let d: f64 = slope; // fractal dimension
            let h: f64 = 2.0 - d;
            if h.is_finite() {
                h
            } else {
                0.5
            }
        }
        Err(_) => 0.5,
    }
}

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

    #[test]
    fn test_higuchi_trending() {
        let prices: Vec<f64> = (0..256).map(|i| 100.0 + i as f64 * 0.5).collect();
        let h = hurst_higuchi(&prices);
        assert!(
            h > 0.7,
            "Trending series should have high Higuchi H: {h}"
        );
    }

    #[test]
    fn test_higuchi_mean_reverting() {
        let prices: Vec<f64> = (0..256)
            .map(|i| if i % 2 == 0 { 100.0 } else { 101.0 })
            .collect();
        let h = hurst_higuchi(&prices);
        assert!(
            h < 0.5,
            "Mean-reverting series should have low Higuchi H: {h}"
        );
    }

    #[test]
    fn test_higuchi_short() {
        let h = hurst_higuchi(&[1.0, 2.0, 3.0]);
        assert_eq!(h, 0.5, "Very short series returns 0.5");
    }

    #[test]
    fn test_higuchi_works_at_n10() {
        let prices: Vec<f64> = (0..10).map(|i| 100.0 + i as f64).collect();
        let h = hurst_higuchi(&prices);
        assert!(h.is_finite(), "Should work at n=10: {h}");
    }

    #[test]
    fn test_higuchi_constant() {
        let prices = vec![100.0; 128];
        let h = hurst_higuchi(&prices);
        assert!(h.is_finite(), "Constant series should be finite: {h}");
    }
}
