downsample 0.0.5

keep downsampled history of data over long period of time
Documentation
use crate::fixed_frequency::storage::LevelMetadata;

pub struct LevelView<'a, T> {
    data:  &'a [T],
    level: &'a LevelMetadata,
}

impl<'a, T> LevelView<'a, T> {
    pub(crate) fn new(data: &'a [T], level: &'a LevelMetadata) -> Self { Self { data, level } }

    pub fn len(&self) -> usize { self.level.sample_capacity() }

    pub fn is_empty(&self) -> bool { self.len() == 0 }

    pub fn get(&self, index: usize) -> Option<&'a T> {
        if index >= self.len() {
            return None;
        }

        Some(&self.data[self.level.readable_position(index)])
    }

    pub fn iter_oldest_first(&self) -> LevelIter<'a, T> {
        LevelIter {
            data:  self.data,
            level: self.level,
            index: 0,
        }
    }
}

pub struct LevelIter<'a, T> {
    data:  &'a [T],
    level: &'a LevelMetadata,
    index: usize,
}

impl<'a, T> Iterator for LevelIter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.level.sample_capacity() {
            return None;
        }

        let pos = self.level.readable_position(self.index);
        self.index += 1;
        Some(&self.data[pos])
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.level.sample_capacity() - self.index;
        (remaining, Some(remaining))
    }
}

impl<T> ExactSizeIterator for LevelIter<'_, T> {}

#[cfg(test)]
mod tests {
    use crate::{FixedFrequencyBuilder, FixedWriter, reducers};
    use alloc::vec::Vec;
    use core::num::NonZeroUsize;

    const fn nz(value: usize) -> NonZeroUsize {
        match NonZeroUsize::new(value) {
            Some(value) => value,
            None => panic!("expected non-zero value"),
        }
    }

    fn storage() -> FixedWriter<f32> {
        FixedFrequencyBuilder::new(nz(3))
            .level(nz(2), nz(2), reducers::average_f32())
            .build()
    }

    #[test]
    fn view_get_skips_aggregation_window() {
        let mut s = storage();
        for value in 1..=9 {
            s.push(value as f32);
        }

        let view = s.storage().level(0).unwrap();
        assert_eq!(view.len(), 3);
        assert!(!view.is_empty());
        assert_eq!(view.get(0), Some(&7.0));
        assert_eq!(view.get(1), Some(&8.0));
        assert_eq!(view.get(2), Some(&9.0));
        assert_eq!(view.get(3), None);
    }

    #[test]
    fn iter_yields_oldest_first_after_wraparound() {
        let mut s = storage();
        for value in 1..=9 {
            s.push(value as f32);
        }

        let values: Vec<_> = s.storage().level(0).unwrap().iter_oldest_first().copied().collect();
        assert_eq!(values, [7.0, 8.0, 9.0]);
    }

    #[test]
    fn final_level_view_reads_all_slots() {
        let mut s = storage();
        for value in 1..=9 {
            s.push(value as f32);
        }

        let values: Vec<_> = s.storage().level(1).unwrap().iter_oldest_first().copied().collect();
        assert_eq!(values, [3.5, 5.5]);
    }

    #[test]
    fn iter_size_hint_tracks_remaining_items() {
        let mut s = storage();
        for value in 1..=9 {
            s.push(value as f32);
        }

        let view = s.storage().level(0).unwrap();
        let mut iter = view.iter_oldest_first();
        assert_eq!(iter.size_hint(), (3, Some(3)));
        assert_eq!(iter.next(), Some(&7.0));
        assert_eq!(iter.size_hint(), (2, Some(2)));
        assert_eq!(iter.next(), Some(&8.0));
        assert_eq!(iter.next(), Some(&9.0));
        assert_eq!(iter.size_hint(), (0, Some(0)));
        assert_eq!(iter.next(), None);
    }
}