#![cfg_attr(feature = "specialize", feature(build_hasher_simple_hash_one))]

use ahash::{AHasher, RandomState};
use criterion::*;
use fxhash::FxHasher;
use rand::Rng;
use std::collections::hash_map::DefaultHasher;
use std::hash::{BuildHasherDefault, Hash, Hasher};

// Needs to be in sync with `src/lib.rs`
const AHASH_IMPL: &str = if cfg!(any(
    all(
        any(target_arch = "x86", target_arch = "x86_64"),
        target_feature = "aes",
        not(miri),
    ),
    all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
    all(
        feature = "nightly-arm-aes",
        target_arch = "arm",
        target_feature = "aes",
        not(miri)
    ),
)) {
    "aeshash"
} else {
    "fallbackhash"
};

fn ahash<H: Hash>(b: &H) -> u64 {
    let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
    build_hasher.hash_one(b)
}

fn fnvhash<H: Hash>(b: &H) -> u64 {
    let mut hasher = fnv::FnvHasher::default();
    b.hash(&mut hasher);
    hasher.finish()
}

fn siphash<H: Hash>(b: &H) -> u64 {
    let mut hasher = DefaultHasher::default();
    b.hash(&mut hasher);
    hasher.finish()
}

fn fxhash<H: Hash>(b: &H) -> u64 {
    let mut hasher = FxHasher::default();
    b.hash(&mut hasher);
    hasher.finish()
}

fn seahash<H: Hash>(b: &H) -> u64 {
    let mut hasher = seahash::SeaHasher::default();
    b.hash(&mut hasher);
    hasher.finish()
}

const STRING_LENGTHS: [u32; 12] = [1, 3, 4, 7, 8, 15, 16, 24, 33, 68, 132, 1024];

fn gen_strings() -> Vec<String> {
    STRING_LENGTHS
        .iter()
        .map(|len| {
            let mut string = String::default();
            for pos in 1..=*len {
                let c = (48 + (pos % 10) as u8) as char;
                string.push(c);
            }
            string
        })
        .collect()
}

macro_rules! bench_inputs {
    ($group:ident, $hash:ident) => {
        // Number of iterations per batch should be high enough to hide timing overhead.
        let size = BatchSize::NumIterations(50_000);

        let mut rng = rand::thread_rng();
        $group.bench_function("u8", |b| b.iter_batched(|| rng.gen::<u8>(), |v| $hash(&v), size));
        $group.bench_function("u16", |b| b.iter_batched(|| rng.gen::<u16>(), |v| $hash(&v), size));
        $group.bench_function("u32", |b| b.iter_batched(|| rng.gen::<u32>(), |v| $hash(&v), size));
        $group.bench_function("u64", |b| b.iter_batched(|| rng.gen::<u64>(), |v| $hash(&v), size));
        $group.bench_function("u128", |b| b.iter_batched(|| rng.gen::<u128>(), |v| $hash(&v), size));
        $group.bench_with_input("strings", &gen_strings(), |b, s| b.iter(|| $hash(black_box(s))));
    };
}

fn bench_ahash(c: &mut Criterion) {
    let mut group = c.benchmark_group(AHASH_IMPL);
    bench_inputs!(group, ahash);
}

fn bench_fx(c: &mut Criterion) {
    let mut group = c.benchmark_group("fx");
    bench_inputs!(group, fxhash);
}

fn bench_fnv(c: &mut Criterion) {
    let mut group = c.benchmark_group("fnv");
    bench_inputs!(group, fnvhash);
}

fn bench_sea(c: &mut Criterion) {
    let mut group = c.benchmark_group("sea");
    bench_inputs!(group, seahash);
}

fn bench_sip(c: &mut Criterion) {
    let mut group = c.benchmark_group("sip");
    bench_inputs!(group, siphash);
}

fn bench_map(c: &mut Criterion) {
    #[cfg(feature = "std")]
    {
        let mut group = c.benchmark_group("map");
        group.bench_function("aHash-alias", |b| {
            b.iter(|| {
                let hm: ahash::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
        group.bench_function("aHash-hashBrown", |b| {
            b.iter(|| {
                let hm: hashbrown::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
        group.bench_function("aHash-hashBrown-explicit", |b| {
            b.iter(|| {
                let hm: hashbrown::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
        group.bench_function("aHash-wrapper", |b| {
            b.iter(|| {
                let hm: ahash::AHashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
        group.bench_function("aHash-rand", |b| {
            b.iter(|| {
                let hm: std::collections::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
        group.bench_function("aHash-default", |b| {
            b.iter(|| {
                let hm: std::collections::HashMap<i32, i32, BuildHasherDefault<AHasher>> =
                    (0..1_000_000).map(|i| (i, i)).collect();
                let mut sum = 0;
                for i in 0..1_000_000 {
                    if let Some(x) = hm.get(&i) {
                        sum += x;
                    }
                }
            })
        });
    }
}

criterion_main!(benches);

criterion_group!(
    benches,
    bench_ahash,
    bench_fx,
    bench_fnv,
    bench_sea,
    bench_sip,
    bench_map
);
