use crate::result::{Measurement, Operation, SuiteMeasurements};
use std::collections::HashSet;
use std::io::Write;
use std::iter::FromIterator;
const COMMIT_DIGITS: usize = 7;
#[derive(Debug, Default)]
pub struct Reporter {
    results: Vec<SuiteMeasurements>,
}
impl Reporter {
    pub fn push(&mut self, result: SuiteMeasurements) {
        self.results.push(result);
    }
    pub fn write(&self, f: &mut dyn Write) -> Result<(), std::io::Error> {
                writeln!(f, "# Summaries of all benchmark runs")?;
        let hosts = self.hosts();
        for bench in self.benchmarks() {
            writeln!(f)?;
            writeln!(f, "## Benchmark {}", bench)?;
            for op in [Operation::Backup, Operation::Restore] {
                for host in hosts.iter() {
                    writeln!(f)?;
                    writeln!(f, "Table: {:?} on host {}, times in ms", op, host)?;
                    writeln!(f)?;
                    let mut want_headings = true;
                    for r in self.results_for(host) {
                                                let cols = self.durations(r, &bench, op);
                        if want_headings {
                            want_headings = false;
                            self.headings(f, &cols)?;
                        }
                        self.rows(f, r, &cols)?;
                    }
                }
            }
        }
        writeln!(f)?;
        writeln!(f, "# Individual benchmark runs")?;
        for host in self.hosts() {
            writeln!(f)?;
            writeln!(f, "## Host `{}`", host)?;
            for r in self.results_for(&host) {
                writeln!(f)?;
                writeln!(f, "### Benchmark run {}", r.timestamp())?;
                writeln!(f)?;
                writeln!(f, "* CPUs: {}", r.cpus())?;
                writeln!(f, "* RAM: {} MiB", r.ram() / 1024 / 1024)?;
                for bench in r.benchmark_names() {
                    writeln!(f)?;
                    writeln!(f, "#### Benchmark `{}`", bench)?;
                    writeln!(f)?;
                    for opm in r.ops().filter(|o| o.name() == bench) {
                        let op = opm.op();
                        match op {
                            Operation::Backup => {
                                writeln!(f, "* Backup: {} ms", opm.millis())?;
                            }
                            Operation::Restore => {
                                writeln!(f, "* Restore: {} ms", opm.millis())?;
                            }
                            _ => continue,
                        }
                        for m in opm.iter() {
                            match m {
                                Measurement::DurationMs(_) => (),
                                Measurement::TotalFiles(n) => {
                                    writeln!(f, "  * files: {}", n)?;
                                }
                                &Measurement::TotalData(n) => {
                                    writeln!(f, "  * data: {} bytes", n)?;
                                }
                            }
                        }
                    }
                    for op in [Operation::Backup, Operation::Restore] {
                        writeln!(f)?;
                        writeln!(f, "Table: {:?}", op)?;
                        writeln!(f)?;
                        let cols = self.durations(r, &bench, op);
                        self.headings(f, &cols)?;
                        self.rows(f, r, &cols)?;
                    }
                }
            }
        }
        Ok(())
    }
    fn durations(&self, r: &SuiteMeasurements, bench: &str, op: Operation) -> Vec<u128> {
        r.ops()
            .filter(|r| r.name() == bench)
            .filter(|r| r.op() == op)
            .map(|r| r.millis())
            .collect()
    }
    fn headings(&self, f: &mut dyn Write, durations: &[u128]) -> Result<(), std::io::Error> {
                let mut headings: Vec<String> = durations
            .iter()
            .enumerate()
            .map(|(i, _)| format!("step {}", i))
            .collect();
        headings.insert(0, "Commit".to_string());
        headings.insert(0, "Version".to_string());
        for h in headings.iter() {
            write!(f, "| {}", h)?;
        }
        writeln!(f, "|")?;
                for _ in headings.iter() {
            write!(f, "|----")?;
        }
        writeln!(f, "|")?;
        Ok(())
    }
    fn rows(
        &self,
        f: &mut dyn Write,
        r: &SuiteMeasurements,
        durations: &[u128],
    ) -> Result<(), std::io::Error> {
        write!(f, "| {}", pretty_version(r.obnam_version()))?;
        write!(f, "| {}", pretty_commit(r.obnam_commit()))?;
        for ms in durations.iter() {
            write!(f, "| {}", ms)?;
        }
        writeln!(f, "|")?;
        Ok(())
    }
    fn benchmarks(&self) -> Vec<String> {
        let mut names = HashSet::new();
        for r in self.results.iter() {
            for opm in r.ops() {
                names.insert(opm.name());
            }
        }
        let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect();
        names.sort();
        names
    }
    fn hosts(&self) -> Vec<String> {
        let names: HashSet<&str> = HashSet::from_iter(self.results.iter().map(|r| r.hostname()));
        let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect();
        names.sort();
        names
    }
    fn results_for<'a>(&'a self, hostname: &'a str) -> impl Iterator<Item = &'a SuiteMeasurements> {
        self.results
            .iter()
            .filter(move |r| r.hostname() == hostname)
    }
}
fn pretty_version(v: &str) -> String {
    if let Some(v) = v.strip_prefix("obnam-backup ") {
        v.to_string()
    } else {
        v.to_string()
    }
}
fn pretty_commit(c: Option<&str>) -> String {
    if let Some(c) = c {
        c[..COMMIT_DIGITS].to_string()
    } else {
        "".to_string()
    }
}