#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-echo-killer -- Acoustic Echo Cancellation (NLMS Adaptive Filter)
#[derive(Debug)] pub enum EchoError { InvalidConfig(String) }
impl std::fmt::Display for EchoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidConfig(e) => write!(f, "{e}") } } }
impl std::error::Error for EchoError {}
pub struct NlmsFilter { weights: Vec<f64>, mu: f64 }
impl NlmsFilter {
    pub fn new(taps: usize, step_size: f64) -> Result<Self, EchoError> {
        if taps == 0 { return Err(EchoError::InvalidConfig("taps=0".into())); }
        Ok(Self { weights: vec![0.0; taps], mu: step_size })
    }
    pub fn predict(&self, x: &[f64]) -> f64 {
        self.weights.iter().zip(x.iter()).map(|(w, xi)| w * xi).sum()
    }
    pub fn update(&mut self, x: &[f64], error: f64) {
        let power: f64 = x.iter().map(|xi| xi * xi).sum::<f64>() + 1e-10;
        let norm_step = self.mu / power;
        for (w, xi) in self.weights.iter_mut().zip(x.iter()) { *w += norm_step * error * xi; }
    }
}
pub struct EchoCanceller { filter: NlmsFilter, buffer: Vec<f64> }
impl EchoCanceller {
    pub fn new(filter_length: usize, step_size: f64) -> Result<Self, EchoError> {
        Ok(Self { filter: NlmsFilter::new(filter_length, step_size)?, buffer: vec![0.0; filter_length] })
    }
    pub fn process_sample(&mut self, far_end: f64, near_end: f64) -> f64 {
        self.buffer.insert(0, far_end);
        self.buffer.truncate(self.filter.weights.len());
        let echo_estimate = self.filter.predict(&self.buffer);
        let error = near_end - echo_estimate;
        self.filter.update(&self.buffer, error);
        error // output = near_end minus estimated echo
    }
    pub fn process_block(&mut self, far_end: &[f64], near_end: &[f64]) -> Vec<f64> {
        far_end.iter().zip(near_end.iter()).map(|(&f, &n)| self.process_sample(f, n)).collect()
    }
}
pub fn cross_correlate(a: &[f64], b: &[f64], max_lag: usize) -> usize {
    let mut best_lag = 0; let mut best_corr = f64::MIN;
    for lag in 0..max_lag.min(a.len()) {
        let corr: f64 = a.iter().skip(lag).zip(b.iter()).map(|(x, y)| x * y).sum();
        if corr > best_corr { best_corr = corr; best_lag = lag; }
    }
    best_lag
}
#[cfg(test)] mod tests {
    use super::*;
    #[test] fn test_nlms_convergence() {
        let mut aec = EchoCanceller::new(8, 0.5).unwrap();
        for i in 0..100 { let far = (i as f64 * 0.1).sin(); let echo = far * 0.6; let out = aec.process_sample(far, echo); let _ = out; }
        let far = 1.0; let echo = far * 0.6; let residual = aec.process_sample(far, echo);
        assert!(residual.abs() < 0.5); // echo should be reduced
    }
    #[test] fn test_cross_corr() { let a = vec![0.0, 0.0, 1.0, 0.0]; let b = vec![1.0, 0.0, 0.0, 0.0]; let lag = cross_correlate(&a, &b, 4); assert_eq!(lag, 2); }
}
