use crate::output;
use crate::output::{
    fallback_to_anonymous_on_invalid_label, ComparedStdout, LabeledOutput, Output, SimpleStdout,
};
use std::time::{Duration, Instant};
pub fn run_timed<T, F: FnMut() -> T>(mut closure: F) -> Duration {
    let start = Instant::now();
    (closure)();
    Instant::now().duration_since(start)
}
pub fn run_timed_times<T, F: FnMut() -> T>(iterations: usize, mut closure: F) -> TimingData {
    let mut elapsed = Duration::ZERO;
    let mut min_nanos = u128::MAX;
    let mut max_nanos = 0;
    for _ in 0..iterations {
        let start = Instant::now();
        closure();
        let run_elapsed = Instant::now().duration_since(start);
        let run_elapsed_nanos = run_elapsed.as_nanos();
        if run_elapsed_nanos < min_nanos {
            min_nanos = run_elapsed_nanos;
        }
        if run_elapsed_nanos > max_nanos {
            max_nanos = run_elapsed_nanos;
        }
        elapsed += run_elapsed;
    }
    TimingData {
        iterations: iterations as u128,
        min_nanos,
        max_nanos,
        elapsed: elapsed.as_nanos(),
    }
}
pub fn run_timed_from_iterator<T, R, F: FnMut(R) -> T, It>(
    iterator: It,
    mut closure: F,
) -> TimingData
where
    It: Iterator<Item = R>,
{
    let mut elapsed = Duration::ZERO;
    let mut min_nanos = u128::MAX;
    let mut max_nanos = 0;
    let mut iterations = 0;
    for v in iterator {
        let start = Instant::now();
        closure(v);
        let run_elapsed = Instant::now().duration_since(start);
        let run_elapsed_nanos = run_elapsed.as_nanos();
        if run_elapsed_nanos < min_nanos {
            min_nanos = run_elapsed_nanos;
        }
        if run_elapsed_nanos > max_nanos {
            max_nanos = run_elapsed_nanos;
        }
        elapsed += run_elapsed;
        iterations += 1;
    }
    TimingData {
        iterations,
        min_nanos,
        max_nanos,
        elapsed: elapsed.as_nanos(),
    }
}
#[derive(Copy, Clone, Debug)]
#[cfg(feature = "timer")]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct TimingData {
    pub min_nanos: u128,
    pub max_nanos: u128,
    pub elapsed: u128,
    pub iterations: u128,
}
#[cfg(feature = "timer")]
impl TimingData {
    pub fn pretty_print(&self) {
        output::print_timer_header("anonymous", self);
        output::timer_print_elapsed(
            self.min_nanos as f64,
            self.elapsed as f64 / self.iterations as f64,
            self.max_nanos as f64,
        );
    }
}
pub trait Timeable<It, T>: Sized
where
    It: Iterator<Item = T>,
{
    fn timed(self) -> TimedIterator<It, T, SimpleStdout> {
        self.timed_labeled("anonymous")
    }
    fn timed_labeled(self, label: &'static str) -> TimedIterator<It, T, SimpleStdout>;
    fn timed_persisted(self) -> TimedIterator<It, T, ComparedStdout> {
        self.timed_persisted_labeled("anonymous")
    }
    fn timed_persisted_labeled(self, label: &'static str) -> TimedIterator<It, T, ComparedStdout>;
}
impl<It, T> Timeable<It, T> for It
where
    It: Iterator<Item = T>,
{
    fn timed_labeled(self, label: &'static str) -> TimedIterator<It, T, SimpleStdout> {
        TimedIterator::new(
            self,
            LabeledOutput::new(fallback_to_anonymous_on_invalid_label(label), SimpleStdout),
        )
    }
    fn timed_persisted_labeled(self, label: &'static str) -> TimedIterator<It, T, ComparedStdout> {
        TimedIterator::new(
            self,
            LabeledOutput::new(
                fallback_to_anonymous_on_invalid_label(label),
                ComparedStdout,
            ),
        )
    }
}
pub struct TimedIterator<It, T, O>
where
    It: Iterator<Item = T>,
{
    inner: It,
    iterations: u128,
    min_nanos: u128,
    max_nanos: u128,
    elapsed: Duration,
    out: LabeledOutput<O>,
}
impl<It, T, O> TimedIterator<It, T, O>
where
    It: Iterator<Item = T>,
{
    fn new(inner: It, out: LabeledOutput<O>) -> Self {
        TimedIterator {
            inner,
            iterations: 0,
            min_nanos: u128::MAX,
            max_nanos: 0,
            elapsed: Duration::ZERO,
            out,
        }
    }
}
impl<It, T, O> Iterator for TimedIterator<It, T, O>
where
    It: Iterator<Item = T>,
    O: Output,
{
    type Item = T;
    fn next(&mut self) -> Option<Self::Item> {
        let start = Instant::now();
        let maybe_item = self.inner.next();
        let run_elapsed = Instant::now().duration_since(start);
        if let Some(item) = maybe_item {
            let run_elapsed_nanos = run_elapsed.as_nanos();
            if run_elapsed_nanos < self.min_nanos {
                self.min_nanos = run_elapsed_nanos;
            }
            if run_elapsed_nanos > self.max_nanos {
                self.max_nanos = run_elapsed_nanos;
            }
            self.elapsed += run_elapsed;
            self.iterations += 1;
            Some(item)
        } else {
            self.out.dump(TimingData {
                min_nanos: self.min_nanos,
                max_nanos: self.max_nanos,
                elapsed: self.elapsed.as_nanos(),
                iterations: self.iterations,
            });
            None
        }
    }
}
#[cfg(test)]
#[cfg(feature = "timer")]
mod tests {
    use crate::timing::Timeable;
    #[test]
    fn time_iterator() {
        let _v: Vec<i32> = (0..100).timed().chain(0..10_000).timed().collect();
    }
    #[test]
    fn time_persisted_iterator() {
        for _ in 0..2 {
            let _v: Vec<i32> = (0..1_000_000).timed_persisted().collect();
        }
    }
    #[test]
    fn time_persisted_labled() {
        for _ in 0..2 {
            let _v: Vec<i32> = (0..1_000_000).timed_persisted_labeled("my_test").collect();
        }
    }
}