use criterion::{criterion_group, criterion_main, Criterion, Fun};
use math::BaseNum;
use rand::distributions::{Distribution, Standard};
use rand::prelude::*;
/**
 * This module benchmarks the performance of zipping unused arrays together.
 */
#[cfg(feature = "rayon")]
use rayon::prelude::*;

static SEED: [u8; 32] = [3; 32];
static BUF_SIZE: usize = 750_000;

#[inline]
fn make_random_vec<T>() -> Vec<T>
where
    T: BaseNum,
    Standard: Distribution<T>,
{
    let n = BUF_SIZE;

    let mut rng: StdRng = SeedableRng::from_seed(SEED);
    let mut vec = Vec::new();
    for _ in 0..n {
        vec.push(rng.gen::<T>());
    }
    vec
}

// Vanilla iteration over a single vector
fn benchmark(v: &mut Vec<f64>, v1: &mut Vec<f64>) {
    let f = |x, y| x * 3.6321 + 42314.0 * y;
    for (a, b) in v.iter_mut().zip(v1.iter_mut()) {
        let (mut a2, mut b2) = (*a, *b);
        a2 = f(a2, b2);
        b2 = f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        *a = a2;
        *b = b2;
    }
}

fn unnecessary_zipping(
    v: &mut Vec<f64>,
    v1: &mut Vec<f64>,
    v2: &mut Vec<f64>,
    v3: &mut Vec<f64>,
    v4: &mut Vec<f64>,
    v5: &mut Vec<f64>,
    v6: &mut Vec<f64>,
) {
    let f = |x, y| x * 3.6321 + 42314.0 * y;
    for ((((((a, b), _c), _d), _e), _f), _g) in v
        .iter_mut()
        .zip(v1.iter_mut())
        .zip(v2.iter_mut())
        .zip(v3.iter_mut())
        .zip(v4.iter_mut())
        .zip(v5.iter_mut())
        .zip(v6.iter_mut())
    {
        let (mut a2, mut b2) = (*a, *b);
        a2 = f(a2, b2);
        b2 = f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        *a = a2;
        *b = b2;
    }
}

fn unnecessary_zipping_with_map(
    v: &mut Vec<f64>,
    v1: &mut Vec<f64>,
    v2: &mut Vec<f64>,
    v3: &mut Vec<f64>,
    v4: &mut Vec<f64>,
    v5: &mut Vec<f64>,
    v6: &mut Vec<f64>,
) {
    let f = |x, y| x * 3.6321 + 42314.0 * y;
    for (a, b, _c, _d, _e, _f, _g) in v
        .iter_mut()
        .zip(v1.iter_mut())
        .zip(v2.iter_mut())
        .zip(v3.iter_mut())
        .zip(v4.iter_mut())
        .zip(v5.iter_mut())
        .zip(v6.iter_mut())
        .map(|((((((a, b), c), d), e), f), g)| (a, b, c, d, e, f, g))
    {
        let (mut a2, mut b2) = (*a, *b);
        a2 = f(a2, b2);
        b2 = f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        b2 += f(b2, a2);
        a2 += f(a2, b2);
        *a = a2;
        *b = b2;
    }
}

#[allow(dead_code)]
struct Group {
    a: f64,
    b: f64,
    c: f64,
    d: f64,
    e: f64,
    f: f64,
    g: f64,
}
fn unnecessary_zipping_with_struct(
    v: &mut Vec<f64>,
    v1: &mut Vec<f64>,
    v2: &mut Vec<f64>,
    v3: &mut Vec<f64>,
    v4: &mut Vec<f64>,
    v5: &mut Vec<f64>,
    v6: &mut Vec<f64>,
) {
    let fu = |x, y| x * 3.6321 + 42314.0 * y;
    for ((((((a, b), c), d), e), f), g) in v
        .iter_mut()
        .zip(v1.iter_mut())
        .zip(v2.iter_mut())
        .zip(v3.iter_mut())
        .zip(v4.iter_mut())
        .zip(v5.iter_mut())
        .zip(v6.iter_mut())
    {
        let mut group = Group {
            a: *a,
            b: *b,
            c: *c,
            d: *d,
            e: *e,
            f: *f,
            g: *g,
        };
        group.a = fu(group.a, group.b);
        group.b = fu(group.b, group.a);
        group.a += fu(group.a, group.b);
        group.b += fu(group.b, group.a);
        group.a += fu(group.a, group.b);
        group.b += fu(group.b, group.a);
        group.a += fu(group.a, group.b);
        *a = group.a;
        *b = group.b;
    }
}

fn zipping(
    v: &mut Vec<f64>,
    v1: &mut Vec<f64>,
    v2: &mut Vec<f64>,
    v3: &mut Vec<f64>,
    v4: &mut Vec<f64>,
    v5: &mut Vec<f64>,
    v6: &mut Vec<f64>,
) {
    let f = |x, y| x * 3.6321 + 42314.0 * y;
    for ((((((a, b), c), d), e), f2), g) in v
        .iter_mut()
        .zip(v1.iter_mut())
        .zip(v2.iter_mut())
        .zip(v3.iter_mut())
        .zip(v4.iter_mut())
        .zip(v5.iter_mut())
        .zip(v6.iter_mut())
    {
        let (mut a2, mut b2) = (*a, *b);
        a2 = f(a2, b2);
        b2 = f(b2, a2);
        a2 += f(b2, *c);
        b2 += f(*c, *d);
        a2 += f(*d, *e);
        b2 += f(*e, *f2);
        a2 += f(*f2, *g);
        *a = a2;
        *b = b2;
    }
}

#[cfg(feature = "rayon")]
fn zipping_with_rayon(
    v: &mut Vec<f64>,
    v1: &mut Vec<f64>,
    v2: &mut Vec<f64>,
    v3: &mut Vec<f64>,
    v4: &mut Vec<f64>,
    v5: &mut Vec<f64>,
    v6: &mut Vec<f64>,
) {
    let f = |x, y| x * 3.6321 + 42314.0 * y;
    v.par_iter_mut()
        .zip(v1.par_iter_mut())
        .zip(v2.par_iter_mut())
        .zip(v3.par_iter_mut())
        .zip(v4.par_iter_mut())
        .zip(v5.par_iter_mut())
        .zip(v6.par_iter_mut())
        .for_each(|((((((a, b), c), d), e), f2), g)| {
            let (mut a2, mut b2) = (*a, *b);
            a2 = f(a2, b2);
            b2 = f(b2, a2);
            a2 += f(b2, *c);
            b2 += f(*c, *d);
            a2 += f(*d, *e);
            b2 += f(*e, *f2);
            a2 += f(*f2, *g);
            *a = a2;
            *b = b2;
        });
}

fn zip(c: &mut Criterion) {
    let bench = Fun::new("Benchmark", move |b, _| {
        let mut v = make_random_vec::<f64>();
        let mut v1 = make_random_vec::<f64>();
        b.iter(|| {
            benchmark(&mut v, &mut v1);
        })
    });

    let unnecessary_zipping = Fun::new("Unnecessary Zipping", move |b, _| {
        let mut v = make_random_vec::<f64>();
        let mut v1 = make_random_vec::<f64>();
        let mut v2 = make_random_vec::<f64>();
        let mut v3 = make_random_vec::<f64>();
        let mut v4 = make_random_vec::<f64>();
        let mut v5 = make_random_vec::<f64>();
        let mut v6 = make_random_vec::<f64>();
        b.iter(|| {
            unnecessary_zipping(&mut v, &mut v1, &mut v2, &mut v3, &mut v4, &mut v5, &mut v6);
        })
    });

    let unnecessary_zipping_with_map = Fun::new("Unnecessary Zipping With Map", move |b, _| {
        let mut v = make_random_vec::<f64>();
        let mut v1 = make_random_vec::<f64>();
        let mut v2 = make_random_vec::<f64>();
        let mut v3 = make_random_vec::<f64>();
        let mut v4 = make_random_vec::<f64>();
        let mut v5 = make_random_vec::<f64>();
        let mut v6 = make_random_vec::<f64>();
        b.iter(|| {
            unnecessary_zipping_with_map(
                &mut v, &mut v1, &mut v2, &mut v3, &mut v4, &mut v5, &mut v6,
            );
        })
    });

    let unnecessary_zipping_with_struct =
        Fun::new("Unnecessary Zipping With Struct", move |b, _| {
            let mut v = make_random_vec::<f64>();
            let mut v1 = make_random_vec::<f64>();
            let mut v2 = make_random_vec::<f64>();
            let mut v3 = make_random_vec::<f64>();
            let mut v4 = make_random_vec::<f64>();
            let mut v5 = make_random_vec::<f64>();
            let mut v6 = make_random_vec::<f64>();
            b.iter(|| {
                unnecessary_zipping_with_struct(
                    &mut v, &mut v1, &mut v2, &mut v3, &mut v4, &mut v5, &mut v6,
                );
            })
        });

    let zipping = Fun::new("Zipping", move |b, _| {
        let mut v = make_random_vec::<f64>();
        let mut v1 = make_random_vec::<f64>();
        let mut v2 = make_random_vec::<f64>();
        let mut v3 = make_random_vec::<f64>();
        let mut v4 = make_random_vec::<f64>();
        let mut v5 = make_random_vec::<f64>();
        let mut v6 = make_random_vec::<f64>();
        b.iter(|| {
            zipping(&mut v, &mut v1, &mut v2, &mut v3, &mut v4, &mut v5, &mut v6);
        })
    });

    #[cfg(feature = "rayon")]
    let zipping_with_rayon = Fun::new("Zipping With Rayon", move |b, _| {
        let mut v = make_random_vec::<f64>();
        let mut v1 = make_random_vec::<f64>();
        let mut v2 = make_random_vec::<f64>();
        let mut v3 = make_random_vec::<f64>();
        let mut v4 = make_random_vec::<f64>();
        let mut v5 = make_random_vec::<f64>();
        let mut v6 = make_random_vec::<f64>();
        b.iter(|| {
            zipping_with_rayon(&mut v, &mut v1, &mut v2, &mut v3, &mut v4, &mut v5, &mut v6);
        })
    });

    let fns = vec![
        bench,
        unnecessary_zipping,
        unnecessary_zipping_with_map,
        unnecessary_zipping_with_struct,
        zipping,
        #[cfg(feature = "rayon")]
        zipping_with_rayon,
    ];
    c.bench_functions("zip", fns, ());
}

criterion_group!(benches, zip);
criterion_main!(benches);
