//! CPU benchmark workloads

use std::time::Instant;

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

/// Instruction throughput result
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InstructionResult {
    pub integer_ops_per_sec: f64,
    pub float_ops_per_sec: f64,
    pub simd_ops_per_sec: Option<f64>,
}

/// Test instruction throughput (integer, float, SIMD)
pub fn test_instruction_throughput() -> Result<InstructionResult, ProfilingError> {
    super::super::logging::log_info("Testing instruction throughput");

    let iterations = 1_000_000;

    // Integer operations (prime sieve)
    let start = Instant::now();
    let integer_ops = run_prime_sieve(iterations);
    let integer_duration = start.elapsed();
    let integer_ops_per_sec = integer_ops as f64 / integer_duration.as_secs_f64();

    // Floating-point operations (matrix multiplication)
    let start = Instant::now();
    let float_ops = run_matrix_ops(100);
    let float_duration = start.elapsed();
    let float_ops_per_sec = float_ops as f64 / float_duration.as_secs_f64();

    // SIMD operations (vectorized arithmetic)
    let simd_ops_per_sec = match run_simd_ops() {
        Ok(ops) => Some(ops),
        Err(e) => {
            super::super::logging::log_warn(&format!(
                "SIMD operations not supported on this platform: {}",
                e
            ));
            None
        }
    };

    Ok(InstructionResult {
        integer_ops_per_sec,
        float_ops_per_sec,
        simd_ops_per_sec,
    })
}

pub(super) fn run_prime_sieve(limit: u64) -> u64 {
    // Use larger sieve size to ensure meaningful work per thread
    // 1M elements takes ~10ms in release, reducing thread overhead impact
    let sieve_size = (limit as usize).min(1_000_000);
    let mut sieve = vec![true; sieve_size];
    let mut count = 0u64;

    for i in 2..sieve.len() {
        if sieve[i] {
            count += 1;
            let mut j = i * 2;
            while j < sieve.len() {
                sieve[j] = false;
                j += i;
            }
        }
    }

    count
}

pub(super) fn run_matrix_ops(size: usize) -> u64 {
    let mut a = vec![0.0f64; size * size];
    let mut b = vec![0.0f64; size * size];
    let mut c = vec![0.0f64; size * size];

    for i in 0..size {
        for j in 0..size {
            a[i * size + j] = (i + j) as f64;
            b[i * size + j] = (i * j) as f64;
        }
    }

    let mut ops = 0u64;
    for i in 0..size {
        for j in 0..size {
            for k in 0..size {
                c[i * size + j] += a[i * size + k] * b[k * size + j];
                ops += 1;
            }
        }
    }

    ops
}

/// Run SIMD operations using platform-specific vectorization
fn run_simd_ops() -> Result<f64, ProfilingError> {
    #[cfg(target_arch = "x86_64")]
    {
        use std::arch::x86_64::*;

        if is_x86_feature_detected!("avx2") {
            return run_avx2_ops();
        } else if is_x86_feature_detected!("sse2") {
            return run_sse2_ops();
        }
    }

    #[cfg(target_arch = "aarch64")]
    {
        #[allow(unused_imports)]
        use std::arch::aarch64::*;
        return run_neon_ops();
    }

    // Fallback to scalar operations
    #[allow(unreachable_code)]
    run_scalar_simd_ops()
}

#[cfg(target_arch = "x86_64")]
fn run_avx2_ops() -> Result<f64, ProfilingError> {
    use std::arch::x86_64::*;

    let size = 10000;
    let a = vec![1.0f32; size];
    let b = vec![2.0f32; size];
    let mut result = vec![0.0f32; size];

    let start = std::time::Instant::now();

    unsafe {
        for i in (0..size).step_by(8) {
            let va = _mm256_loadu_ps(a.as_ptr().add(i));
            let vb = _mm256_loadu_ps(b.as_ptr().add(i));
            let vr = _mm256_add_ps(va, vb);
            _mm256_storeu_ps(result.as_mut_ptr().add(i), vr);
        }
    }

    let duration = start.elapsed();
    let ops = size as f64 / duration.as_secs_f64();

    Ok(ops)
}

#[cfg(target_arch = "x86_64")]
fn run_sse2_ops() -> Result<f64, ProfilingError> {
    use std::arch::x86_64::*;

    let size = 10000;
    let a = vec![1.0f32; size];
    let b = vec![2.0f32; size];
    let mut result = vec![0.0f32; size];

    let start = std::time::Instant::now();

    unsafe {
        for i in (0..size).step_by(4) {
            let va = _mm_loadu_ps(a.as_ptr().add(i));
            let vb = _mm_loadu_ps(b.as_ptr().add(i));
            let vr = _mm_add_ps(va, vb);
            _mm_storeu_ps(result.as_mut_ptr().add(i), vr);
        }
    }

    let duration = start.elapsed();
    let ops = size as f64 / duration.as_secs_f64();

    Ok(ops)
}

#[cfg(target_arch = "aarch64")]
fn run_neon_ops() -> Result<f64, ProfilingError> {
    use std::arch::aarch64::*;

    let size = 10000;
    let a = vec![1.0f32; size];
    let b = vec![2.0f32; size];
    let mut result = vec![0.0f32; size];

    let start = std::time::Instant::now();

    unsafe {
        for i in (0..size).step_by(4) {
            let va = vld1q_f32(a.as_ptr().add(i));
            let vb = vld1q_f32(b.as_ptr().add(i));
            let vr = vaddq_f32(va, vb);
            vst1q_f32(result.as_mut_ptr().add(i), vr);
        }
    }

    let duration = start.elapsed();
    let ops = size as f64 / duration.as_secs_f64();

    Ok(ops)
}

fn run_scalar_simd_ops() -> Result<f64, ProfilingError> {
    let size = 10000;
    let a = vec![1.0f32; size];
    let b = vec![2.0f32; size];
    let mut result = vec![0.0f32; size];

    let start = std::time::Instant::now();

    for i in 0..size {
        result[i] = a[i] + b[i];
    }

    let duration = start.elapsed();
    let ops = size as f64 / duration.as_secs_f64();

    Ok(ops)
}
