//! Cache miss measurement

use std::time::Instant;

use super::super::error::ProfilingError;

/// Cache miss result
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CacheMissResult {
    pub l1_miss_rate: f64,
    pub l2_miss_rate: f64,
    pub l3_miss_rate: f64,
}

/// Measure cache miss rates by comparing access latencies across different working set sizes.
/// Note: This is an estimation based on memory access latency patterns.
/// Actual cache miss rates require hardware performance counters (perf on Linux).
pub fn measure_cache_misses() -> Result<CacheMissResult, ProfilingError> {
    super::super::logging::log_info("Measuring cache miss rates");

    // Measure latencies for different working set sizes
    // L1 cache: ~32-64KB on most CPUs
    let l1_latency = measure_sequential_latency(8 * 1024); // 8KB - fits in L1

    // L2 cache: ~256KB-1MB on most CPUs
    let l2_latency = measure_sequential_latency(64 * 1024); // 64KB - likely L1 miss, L2 hit

    // L3 cache: ~4-32MB on most CPUs
    let l3_latency = measure_sequential_latency(512 * 1024); // 512KB - likely L2 miss, L3 hit

    // RAM: beyond all caches
    let ram_latency = measure_sequential_latency(16 * 1024 * 1024); // 16MB - likely L3 miss

    // Calculate miss rates based on latency ratios between cache levels
    // Higher ratio = more misses at that level
    let l1_miss_rate = estimate_miss_rate_ratio(l1_latency, l2_latency);
    let l2_miss_rate = estimate_miss_rate_ratio(l2_latency, l3_latency);
    let l3_miss_rate = estimate_miss_rate_ratio(l3_latency, ram_latency);

    Ok(CacheMissResult {
        l1_miss_rate,
        l2_miss_rate,
        l3_miss_rate,
    })
}

/// Measure average access latency for sequential access pattern
fn measure_sequential_latency(size: usize) -> f64 {
    let iterations = 100;
    let elements = size / std::mem::size_of::<u64>();
    let stride = 64 / std::mem::size_of::<u64>(); // Cache line size stride
    let mut data: Vec<u64> = vec![1u64; elements];

    // Warm up - touch all memory
    for i in 0..elements {
        data[i] = i as u64;
    }
    std::hint::black_box(&data);

    // Measure access latency with stride to defeat prefetcher
    let start = Instant::now();
    let mut sum = 0u64;
    for _ in 0..iterations {
        for i in (0..elements).step_by(stride.max(1)) {
            sum = sum.wrapping_add(data[i]);
        }
    }
    std::hint::black_box(sum);
    let duration = start.elapsed();

    let accesses = iterations * (elements / stride.max(1));
    duration.as_nanos() as f64 / accesses as f64
}

/// Estimate miss rate based on latency ratio between cache levels
/// If next level is much slower, current level has low miss rate (data stays in cache)
/// If next level is similar speed, current level has high miss rate (data not cached)
fn estimate_miss_rate_ratio(current_latency: f64, next_level_latency: f64) -> f64 {
    if next_level_latency <= current_latency || current_latency <= 0.0 {
        return 0.01; // Already at slowest level or invalid measurement
    }

    let ratio = next_level_latency / current_latency;

    // Ratio interpretation:
    // ratio > 3.0 = significant latency jump = low miss rate (data cached well)
    // ratio ~ 1.0 = no latency jump = high miss rate (not benefiting from cache)
    //
    // Map ratio to miss rate: higher ratio = lower miss rate
    let miss_rate = if ratio >= 3.0 {
        0.01 + (1.0 / ratio) * 0.10 // Very low miss rate (1-4%)
    } else if ratio >= 1.5 {
        0.05 + (3.0 - ratio) / 1.5 * 0.25 // Moderate miss rate (5-30%)
    } else {
        0.30 + (1.5 - ratio) / 0.5 * 0.30 // High miss rate (30-60%)
    };

    miss_rate.clamp(0.01, 0.60)
}
