//! Reproducible test-signal generators — the "conjured noises" the measurement
//! harness drives AEC/analysis with. Everything is deterministic: the noise
//! sources take an explicit seed so a run is byte-for-byte repeatable, which is
//! what makes A/B comparisons (old build vs new, tuning X vs Y) meaningful.
//!
//! All generators emit `f32` samples nominally in `[-amp, amp]` at the given
//! sample rate. Convert to `i16` at the I/O edge.

use std::f64::consts::PI;

/// A tiny deterministic PRNG (xorshift64*) so the noise generators need no
/// external crate and produce identical output for a given seed across runs.
struct XorShift64 {
    state: u64,
}

impl XorShift64 {
    fn new(seed: u64) -> Self {
        // Avoid the all-zero fixed point.
        XorShift64 {
            state: seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1,
        }
    }

    fn next_u64(&mut self) -> u64 {
        let mut x = self.state;
        x ^= x >> 12;
        x ^= x << 25;
        x ^= x >> 27;
        self.state = x;
        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
    }

    /// Uniform `f32` in `[-1.0, 1.0)`.
    fn next_bipolar(&mut self) -> f32 {
        // Top 24 bits -> [0,1), then map to [-1,1).
        let u = (self.next_u64() >> 40) as f32 / (1u32 << 24) as f32;
        u * 2.0 - 1.0
    }
}

/// `len` samples of silence.
pub fn silence(len: usize) -> Vec<f32> {
    vec![0.0; len]
}

/// A pure sine tone at `freq` Hz.
pub fn sine(freq: f32, amp: f32, len: usize, sample_rate: u32) -> Vec<f32> {
    (0..len)
        .map(|i| amp * (2.0 * PI * freq as f64 * i as f64 / sample_rate as f64).sin() as f32)
        .collect()
}

/// A unit impulse: `amp` at sample 0, silence after. Its flat spectrum makes it
/// the natural probe for an echo path's impulse response.
pub fn impulse(amp: f32, len: usize) -> Vec<f32> {
    let mut v = vec![0.0; len];
    if len > 0 {
        v[0] = amp;
    }
    v
}

/// A logarithmic (exponential) sine sweep from `f0` to `f1` Hz over the buffer —
/// the standard excitation for measuring a system's frequency response, since it
/// spends equal time per octave.
pub fn log_sweep(f0: f32, f1: f32, amp: f32, len: usize, sample_rate: u32) -> Vec<f32> {
    if len == 0 {
        return Vec::new();
    }
    let t_total = len as f64 / sample_rate as f64;
    let (f0, f1) = (f0 as f64, f1 as f64);
    let k = (f1 / f0).ln();
    (0..len)
        .map(|i| {
            let t = i as f64 / sample_rate as f64;
            // Instantaneous-phase integral of an exponential chirp.
            let phase = 2.0 * PI * f0 * t_total / k * ((k * t / t_total).exp() - 1.0);
            amp * phase.sin() as f32
        })
        .collect()
}

/// Uniform white noise in `[-amp, amp]`, seeded for reproducibility.
pub fn white_noise(amp: f32, len: usize, seed: u64) -> Vec<f32> {
    let mut rng = XorShift64::new(seed);
    (0..len).map(|_| amp * rng.next_bipolar()).collect()
}

/// Pink (1/f) noise via the Voss-McCartney algorithm — perceptually flatter than
/// white noise and a closer stand-in for room/voice spectra. Seeded.
pub fn pink_noise(amp: f32, len: usize, seed: u64) -> Vec<f32> {
    let mut rng = XorShift64::new(seed);
    const ROWS: usize = 16;
    let mut rows = [0.0f32; ROWS];
    let mut running = 0.0f32;
    let mut out = Vec::with_capacity(len);
    for i in 0..len {
        // Each row updates half as often as the previous: the row to refresh is
        // the index of the lowest set bit of the sample counter.
        let n = i + 1;
        let row = (n & n.wrapping_neg()).trailing_zeros() as usize;
        if row < ROWS {
            running -= rows[row];
            rows[row] = rng.next_bipolar();
            running += rows[row];
        }
        // Normalize: sum of ROWS unit-bipolar rows spans roughly [-ROWS, ROWS].
        out.push(amp * running / ROWS as f32);
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::dsp::metrics::rms;

    #[test]
    fn sine_has_expected_length_and_range() {
        let s = sine(1000.0, 0.5, 480, 48_000);
        assert_eq!(s.len(), 480);
        assert!(s.iter().all(|&x| x.abs() <= 0.5001));
    }

    #[test]
    fn impulse_is_single_spike() {
        let s = impulse(1.0, 10);
        assert_eq!(s[0], 1.0);
        assert!(s[1..].iter().all(|&x| x == 0.0));
    }

    #[test]
    fn white_noise_is_deterministic_for_a_seed() {
        let a = white_noise(1.0, 256, 42);
        let b = white_noise(1.0, 256, 42);
        let c = white_noise(1.0, 256, 43);
        assert_eq!(a, b, "same seed -> identical output");
        assert_ne!(a, c, "different seed -> different output");
    }

    #[test]
    fn noise_energy_is_nonzero_and_bounded() {
        let w = white_noise(0.5, 4096, 7);
        let r = rms(&w);
        assert!(r > 0.0 && r < 0.5, "rms {r} should be in (0, amp)");
    }

    #[test]
    fn log_sweep_spans_the_buffer() {
        let s = log_sweep(100.0, 8000.0, 1.0, 4800, 48_000);
        assert_eq!(s.len(), 4800);
        // Non-trivial energy present.
        assert!(rms(&s) > 0.1);
    }
}
