use std::{
    fmt::{Debug, Display},
    iter::Sum,
    marker::PhantomData,
    ops::Div,
};

#[derive(Default)]
pub(crate) struct ObservationLog<T, DivType = usize>(Vec<T>, PhantomData<DivType>)
where
    T: Clone + Ord + Sum<T> + Div<DivType, Output = T> + Default + Debug,
    DivType: TryFrom<usize>,
    <DivType as TryFrom<usize>>::Error: std::fmt::Debug;

#[derive(Debug)]
pub(crate) struct Aggregation<T: Debug> {
    count: usize,
    min: T,
    max: T,
    avg: T,
    q1: T,
    median: T,
    q3: T,
    total: T,
}

impl<T, DivType> ObservationLog<T, DivType>
where
    T: Clone + Ord + Sum<T> + Div<DivType, Output = T> + Default + Debug,
    DivType: TryFrom<usize>,
    <DivType as TryFrom<usize>>::Error: std::fmt::Debug,
{
    pub(crate) fn push(&mut self, observation: T) {
        self.0.push(observation);
    }

    pub(crate) fn aggregate(&mut self) -> Option<Aggregation<T>> {
        let count = self.0.len();
        if count > 0 {
            self.0.sort();
            let min = self.0.first().cloned().unwrap();
            let max = self.0.last().cloned().unwrap();
            let q1 = self.0[self.0.len() / 4].clone();
            let median = self.0[self.0.len() / 2].clone();
            let q3 = self.0[3 * self.0.len() / 4].clone();
            let total: T = self.0.iter().cloned().sum();
            let avg = total.clone()
                / DivType::try_from(count).expect("failed to normalize the total value");
            Some(Aggregation::<T> {
                count,
                min,
                max,
                q1,
                median,
                q3,
                avg,
                total,
            })
        } else {
            None
        }
    }
}

impl<T: std::fmt::Debug> Display for Aggregation<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "- count: {}", self.count)?;
        writeln!(f, "- min: {:?}", self.min)?;
        writeln!(f, "- q1: {:?}", self.q1)?;
        writeln!(f, "- median: {:?}", self.median)?;
        writeln!(f, "- q3: {:?}", self.q3)?;
        writeln!(f, "- max: {:?}", self.max)?;
        writeln!(f, "- avg: {:?}", self.avg)?;
        write!(f, "- total: {:?}", self.total)?;
        Ok(())
    }
}
