const BYTES: usize = 1 << 10;

macro_rules! bench_template {
    ($op:path, $name:ident, $mask:expr) => {
        #[bench]
        fn $name(bench: &mut ::test::Bencher) {
            use ::rand::distr::{Distribution, Uniform};
            let mut rng = crate::bench_rng();
            let mut dst = vec![0; ITERATIONS];
            let distr = &Uniform::try_from(0..=U::MAX).unwrap();
            let src1: Vec<U> = distr.sample_iter(&mut rng).take(ITERATIONS).collect();
            let mut src2: Vec<U> = distr.sample_iter(&mut rng).take(ITERATIONS).collect();
            // Fix the loop invariant mask
            src2[0] = U::MAX / 3;
            let dst = dst.first_chunk_mut().unwrap();
            let src1 = src1.first_chunk().unwrap();
            let src2 = src2.first_chunk().unwrap();

            #[allow(unused)]
            fn vectored(dst: &mut Data, src1: &Data, src2: &Data) {
                let mask = $mask;
                for k in 0..ITERATIONS {
                    dst[k] = $op(src1[k], mask(src2, k));
                }
            }
            let f: fn(&mut Data, &Data, &Data) = vectored;
            let f = ::test::black_box(f);

            bench.iter(|| {
                f(dst, src1, src2);
            });
        }
    };
}

macro_rules! bench_type {
    ($U:ident) => {
        mod $U {
            type U = $U;
            const ITERATIONS: usize = super::BYTES / size_of::<U>();
            type Data = [U; ITERATIONS];
            bench_mask_kind!(constant, |_, _| const { U::MAX / 3 });
            bench_mask_kind!(invariant, |src: &Data, _| src[0]);
            bench_mask_kind!(variable, |src: &Data, k| src[k]);
        }
    };
}

macro_rules! bench_mask_kind {
    ($mask_kind:ident, $mask:expr) => {
        mod $mask_kind {
            use super::{Data, ITERATIONS, U};
            bench_template!(U::extract_bits, extract_bits, $mask);
            bench_template!(U::deposit_bits, deposit_bits, $mask);
        }
    };
}

bench_type!(u8);
bench_type!(u16);
bench_type!(u32);
bench_type!(u64);
bench_type!(u128);
