use std::time::SystemTime;

use openpgp_cert_d as cert_d;

use cert_d::CertD;
use cert_d::Tag;

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let args = std::env::args().collect::<Vec<String>>();

    let certd = if args.len() == 1 {
        CertD::new()?
    } else if args.len() == 2 {
        CertD::with_base_dir(&args[1])?
    } else {
        eprintln!("Usage: {} [CERTD]", args[0]);
        return Err("Invalid arguments".into());
    };

    let iter_fingerprint = certd.fingerprints();
    let fingerprints = iter_fingerprint.collect::<Result<Vec<_>, _>>()?;
    eprintln!("Have {} certificates", fingerprints.len());

    // Number of trials.
    const N: usize = 1000;

    let mut stat_one_trials: [u128; N] = [0; N];
    let mut stat_one_tags: [Tag; N] = [Tag(0); N];

    let mut tag_readdir_std_trials: [u128; N] = [0; N];
    let mut tag_readdir_std_tags: [Tag; N] = [Tag(0); N];

    let mut tag_probe_std_trials: [u128; N] = [0; N];
    let mut tag_probe_std_tags: [Tag; N] = [Tag(0); N];

    #[cfg(unix)]
    let mut tag_readdir_unix_trials: [u128; N] = [0; N];
    #[cfg(unix)]
    let mut tag_readdir_unix_tags: [Tag; N] = [Tag(0); N];

    #[cfg(unix)]
    let mut tag_probe_unix_trials: [u128; N] = [0; N];
    #[cfg(unix)]
    let mut tag_probe_unix_tags: [Tag; N] = [Tag(0); N];

    eprintln!("Running {} trials", N);

    for trial in 0..N {
        let start = SystemTime::now();
        if let Ok(meta_data) = std::fs::metadata(certd.base_dir()) {
            stat_one_tags[trial] = Tag::try_from(meta_data).unwrap();
        }
        let end = SystemTime::now();
        stat_one_trials[trial] = end.duration_since(start).unwrap().as_nanos();

        let start = SystemTime::now();
        tag_readdir_std_tags[trial] = certd.tag_readdir_std();
        let end = SystemTime::now();
        tag_readdir_std_trials[trial]
            = end.duration_since(start).unwrap().as_nanos();

        let start = SystemTime::now();
        tag_probe_std_tags[trial] = certd.tag_probe_std(None);
        let end = SystemTime::now();
        tag_probe_std_trials[trial]
            = end.duration_since(start).unwrap().as_nanos();

        #[cfg(unix)]
        {
            let start = SystemTime::now();
            tag_readdir_unix_tags[trial] = certd.tag_readdir_unix();
            let end = SystemTime::now();
            tag_readdir_unix_trials[trial]
                = end.duration_since(start).unwrap().as_nanos();
        }

        #[cfg(unix)]
        {
            let start = SystemTime::now();
            tag_probe_unix_tags[trial] = certd.tag_probe_unix(None);
            let end = SystemTime::now();
            tag_probe_unix_trials[trial]
                = end.duration_since(start).unwrap().as_nanos();
        }
    }


    let stat_one_mean: u128
        = stat_one_trials.iter().sum::<u128>() / (N as u128);

    let summarize = |desc, mut trials: [u128; N]| {
        // Add thousand separators.
        let ts = |n: u128| {
            n.to_string()
                .as_bytes()
                .rchunks(3)
                .rev()
                .map(String::from_utf8_lossy)
                .collect::<Vec<_>>()
                .join(",")
        };

        eprintln!("{}:", desc);

        let mean: u128 = trials.iter().sum::<u128>() / (N as u128);

        trials.sort();

        //eprintln!("     min: {} ns", ts(trials[0]));
        eprintln!("     10%: {} ns", ts(trials[1 * N / 10]));
        eprintln!("    mean: {} ns", ts(mean));
        eprintln!("  median: {} ns", ts(trials[5 * N / 10]));
        eprintln!("     90%: {} ns", ts(trials[9 * N / 10]));
        //eprintln!("     max: {} ns", ts(trials[N - 1]));
        eprintln!("stat factor: {}",
                  mean / stat_one_mean);
    };

    summarize("stats_one", stat_one_trials);

    summarize("CertD::tag_readdir_std", tag_readdir_std_trials);

    summarize("CertD::tag_probe_std", tag_probe_std_trials);
    // Make sure the two implementations compute the same
    // tags.
    assert_eq!(tag_readdir_std_tags, tag_probe_std_tags);

    #[cfg(unix)]
    {
        summarize("CertD::tag_readdir_unix", tag_readdir_unix_trials);

        // Make sure the two implementations compute the same
        // tags.
        assert_eq!(tag_readdir_std_tags, tag_readdir_unix_tags);
    }

    #[cfg(unix)]
    {
        summarize("CertD::tag_probe_unix", tag_probe_unix_trials);
        assert_eq!(tag_readdir_std_tags, tag_probe_unix_tags);
    }

    Ok(())
}
