use std::{
    collections::VecDeque,
    num::NonZeroUsize,
};

use getset::CopyGetters;
use num::Float;

use crate::View;

/// Lags a value such that it appears n ticks later.
#[derive(Debug, Clone, CopyGetters)]
pub struct Lag<T, V> {
    view: V,
    buffer: VecDeque<T>,
    /// The sliding window length.
    #[getset(get_copy = "pub")]
    window_len: NonZeroUsize,
    out: Option<T>,
}

impl<T, V> Lag<T, V>
where
    V: View<T>,
    T: Float,
{
    /// Create a new instance with a `view` and a `window_len`.
    pub fn new(view: V, window_len: NonZeroUsize) -> Self {
        Self {
            view,
            buffer: VecDeque::with_capacity(window_len.get()),
            window_len,
            out: None,
        }
    }
}

impl<T, V> View<T> for Lag<T, V>
where
    V: View<T>,
    T: Float,
{
    fn update(&mut self, val: T) {
        debug_assert!(val.is_finite(), "value must be finite");
        self.view.update(val);
        let Some(val) = self.view.last() else { return };
        debug_assert!(val.is_finite(), "value must be finite");

        self.buffer.push_back(val);
        if self.buffer.len() >= self.window_len().get() {
            self.out = self.buffer.pop_front();
        }
    }

    fn last(&self) -> Option<T> {
        self.out
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::pure_functions::Echo;

    #[test]
    fn lag() {
        let mut lag = Lag::new(Echo::new(), NonZeroUsize::new(3).unwrap());

        lag.update(1.0);
        assert_eq!(lag.last(), None);
        lag.update(2.0);
        assert_eq!(lag.last(), None);
        lag.update(3.0);
        assert_eq!(lag.last(), Some(1.0));
        lag.update(4.0);
        assert_eq!(lag.last(), Some(2.0));
        lag.update(5.0);
        assert_eq!(lag.last(), Some(3.0));
    }
}
