downsample 0.0.5

keep downsampled history of data over long period of time
Documentation
use crate::{
    Reducer,
    fixed_frequency::storage::{FixedStorage, FixedWriter, LevelMetadata},
};
use alloc::vec::Vec;
use core::num::NonZeroUsize;

#[derive(Clone, Copy)]
struct LevelInfo<T> {
    capacity: NonZeroUsize,
    aggregation_window: NonZeroUsize,
    reducer: Reducer<T>,
}

pub struct Builder<T> {
    raw_capacity: NonZeroUsize,
    levels:       Vec<LevelInfo<T>>,
}

impl<T> Builder<T> {
    /// raw_capacity: capacity of raw T data before any aggregation is done.
    pub fn new(raw_capacity: NonZeroUsize) -> Self {
        Self {
            raw_capacity,
            levels: Vec::new(),
        }
    }

    pub fn level(mut self, capacity: NonZeroUsize, aggregation_window: NonZeroUsize, reducer: Reducer<T>) -> Self {
        self.levels.push(LevelInfo {
            capacity,
            aggregation_window,
            reducer,
        });
        self
    }

    pub fn build(self) -> FixedWriter<T>
    where
        T: Default,
    {
        let mut levels = Vec::with_capacity(self.levels.len() + 1);
        let mut reducers = Vec::with_capacity(self.levels.len());
        let mut start = 0;
        let mut capacity = self.raw_capacity.get();

        for info in self.levels {
            let aggregation_window = info.aggregation_window.get();
            let storage_capacity = capacity + aggregation_window;
            let end = start + storage_capacity;
            levels.push(LevelMetadata::new(start, end, aggregation_window));
            reducers.push(info.reducer);
            start = end;
            capacity = info.capacity.get();
        }

        let end = start + capacity;
        levels.push(LevelMetadata::new(start, end, 0));

        let mut data = Vec::with_capacity(end);
        for _ in 0..end {
            data.push(T::default());
        }

        FixedWriter {
            storage: FixedStorage { data, levels },
            reducers,
        }
    }
}

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

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

    #[test]
    fn construct_simple() {
        // 0s - 1s : 100Hz
        // 1s - 1m : 1Hz
        // 1m - 1h : 1/60 Hz
        // 1h - 1d : 1/3600 Hz
        let s: FixedWriter<f32> = Builder::new(nz(100))
            .level(nz(59), nz(100), reducers::average_f32())
            .level(nz(59), nz(60), reducers::average_f32())
            .level(nz(23), nz(60), reducers::average_f32())
            .build();

        assert_eq!(s.storage.level_count(), 4);

        let raw = s.storage.level_metadata(0).unwrap();
        assert_eq!(raw.capacity(), 100);
        assert_eq!(raw.aggregation_window(), 100);
        assert_eq!(raw.storage_capacity(), 200);

        let seconds = s.storage.level_metadata(1).unwrap();
        assert_eq!(seconds.capacity(), 59);
        assert_eq!(seconds.aggregation_window(), 60);
        assert_eq!(seconds.storage_capacity(), 119);

        let minutes = s.storage.level_metadata(2).unwrap();
        assert_eq!(minutes.capacity(), 59);
        assert_eq!(minutes.aggregation_window(), 60);
        assert_eq!(minutes.storage_capacity(), 119);

        let hours = s.storage.level_metadata(3).unwrap();
        assert_eq!(hours.capacity(), 23);
        assert_eq!(hours.aggregation_window(), 0);
        assert_eq!(hours.storage_capacity(), 23);
    }

    #[test]
    fn construct_use_simple() {
        // 0s - 1s : 100Hz
        // 1s - 1m : 1Hz
        // 1m - 1h : 1/60 Hz
        // 1h - 1d : 1/3600 Hz
        let mut s: FixedWriter<f32> = Builder::new(nz(100))
            .level(nz(59), nz(100), reducers::average_f32())
            .level(nz(59), nz(60), reducers::average_f32())
            .level(nz(23), nz(60), reducers::average_f32())
            .build();

        for _ in 0..1000 {
            s.push(0.0);
        }
    }
}