use clap::{crate_description, crate_name, value_t, App, Arg};
use solana_ledger::entry::{self, create_ticks, init_poh, EntrySlice, VerifyRecyclers};
use solana_measure::measure::Measure;
use solana_perf::perf_libs;
use solana_sdk::hash::hash;

fn main() {
    solana_logger::setup();

    let matches = App::new(crate_name!())
        .about(crate_description!())
        .version(solana_version::version!())
        .arg(
            Arg::with_name("max_num_entries")
                .long("max-num-entries")
                .takes_value(true)
                .value_name("SIZE")
                .help("Number of entries."),
        )
        .arg(
            Arg::with_name("start_num_entries")
                .long("start-num-entries")
                .takes_value(true)
                .value_name("SIZE")
                .help("Packets per chunk"),
        )
        .arg(
            Arg::with_name("hashes_per_tick")
                .long("hashes-per-tick")
                .takes_value(true)
                .value_name("SIZE")
                .help("hashes per tick"),
        )
        .arg(
            Arg::with_name("num_transactions_per_entry")
                .long("num-transactions-per-entry")
                .takes_value(true)
                .value_name("NUM")
                .help("Skip transaction sanity execution"),
        )
        .arg(
            Arg::with_name("iterations")
                .long("iterations")
                .takes_value(true)
                .help("Number of iterations"),
        )
        .arg(
            Arg::with_name("num_threads")
                .long("num-threads")
                .takes_value(true)
                .help("Number of threads"),
        )
        .arg(
            Arg::with_name("cuda")
                .long("cuda")
                .takes_value(false)
                .help("Use cuda"),
        )
        .get_matches();

    let max_num_entries = value_t!(matches, "max_num_entries", u64).unwrap_or(64);
    let start_num_entries = value_t!(matches, "start_num_entries", u64).unwrap_or(max_num_entries);
    let iterations = value_t!(matches, "iterations", usize).unwrap_or(10);
    let hashes_per_tick = value_t!(matches, "hashes_per_tick", u64).unwrap_or(10_000);
    let start_hash = hash(&[1, 2, 3, 4]);
    let ticks = create_ticks(max_num_entries, hashes_per_tick, start_hash);
    let mut num_entries = start_num_entries as usize;
    if matches.is_present("cuda") {
        perf_libs::init_cuda();
    }
    init_poh();
    while num_entries <= max_num_entries as usize {
        let mut time = Measure::start("time");
        for _ in 0..iterations {
            assert!(ticks[..num_entries]
                .verify_cpu_generic(&start_hash)
                .finish_verify(&ticks[..num_entries]));
        }
        time.stop();
        println!(
            "{},cpu_generic,{}",
            num_entries,
            time.as_us() / iterations as u64
        );

        if is_x86_feature_detected!("avx2") && entry::api().is_some() {
            let mut time = Measure::start("time");
            for _ in 0..iterations {
                assert!(ticks[..num_entries]
                    .verify_cpu_x86_simd(&start_hash, 8)
                    .finish_verify(&ticks[..num_entries]));
            }
            time.stop();
            println!(
                "{},cpu_simd_avx2,{}",
                num_entries,
                time.as_us() / iterations as u64
            );
        }

        if is_x86_feature_detected!("avx512f") && entry::api().is_some() {
            let mut time = Measure::start("time");
            for _ in 0..iterations {
                assert!(ticks[..num_entries]
                    .verify_cpu_x86_simd(&start_hash, 16)
                    .finish_verify(&ticks[..num_entries]));
            }
            time.stop();
            println!(
                "{},cpu_simd_avx512,{}",
                num_entries,
                time.as_us() / iterations as u64
            );
        }

        if perf_libs::api().is_some() {
            let mut time = Measure::start("time");
            let recyclers = VerifyRecyclers::default();
            for _ in 0..iterations {
                assert!(ticks[..num_entries]
                    .start_verify(&start_hash, recyclers.clone(), true)
                    .finish_verify(&ticks[..num_entries]));
            }
            time.stop();
            println!(
                "{},gpu_cuda,{}",
                num_entries,
                time.as_us() / iterations as u64
            );
        }

        println!();
        num_entries *= 2;
    }
}
