use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion};
use half::prelude::*;
use std::{f32, f64, iter};

const SIMD_LARGE_BENCH_SLICE_LEN: usize = 1024;

fn bench_f32_to_f16(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert f16 From f32");
    for val in &[
        0.,
        -0.,
        1.,
        f32::MIN,
        f32::MAX,
        f32::MIN_POSITIVE,
        f32::NEG_INFINITY,
        f32::INFINITY,
        f32::NAN,
        f32::consts::E,
        f32::consts::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("f16::from_f32", val), val, |b, i| {
            b.iter(|| f16::from_f32(*i))
        });
    }
}

fn bench_f64_to_f16(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert f16 From f64");
    for val in &[
        0.,
        -0.,
        1.,
        f64::MIN,
        f64::MAX,
        f64::MIN_POSITIVE,
        f64::NEG_INFINITY,
        f64::INFINITY,
        f64::NAN,
        f64::consts::E,
        f64::consts::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("f16::from_f64", val), val, |b, i| {
            b.iter(|| f16::from_f64(*i))
        });
    }
}

fn bench_f16_to_f32(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert f16 to f32");
    for val in &[
        f16::ZERO,
        f16::NEG_ZERO,
        f16::ONE,
        f16::MIN,
        f16::MAX,
        f16::MIN_POSITIVE,
        f16::NEG_INFINITY,
        f16::INFINITY,
        f16::NAN,
        f16::E,
        f16::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("f16::to_f32", val), val, |b, i| {
            b.iter(|| i.to_f32())
        });
    }
}

fn bench_f16_to_f64(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert f16 to f64");
    for val in &[
        f16::ZERO,
        f16::NEG_ZERO,
        f16::ONE,
        f16::MIN,
        f16::MAX,
        f16::MIN_POSITIVE,
        f16::NEG_INFINITY,
        f16::INFINITY,
        f16::NAN,
        f16::E,
        f16::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("f16::to_f64", val), val, |b, i| {
            b.iter(|| i.to_f64())
        });
    }
}

criterion_group!(
    f16_sisd,
    bench_f32_to_f16,
    bench_f64_to_f16,
    bench_f16_to_f32,
    bench_f16_to_f64
);

fn bench_slice_f32_to_f16(c: &mut Criterion) {
    let mut constant_buffer = [f16::ZERO; 11];
    let constants = [
        0.,
        -0.,
        1.,
        f32::MIN,
        f32::MAX,
        f32::MIN_POSITIVE,
        f32::NEG_INFINITY,
        f32::INFINITY,
        f32::NAN,
        f32::consts::E,
        f32::consts::PI,
    ];
    c.bench_function(
        "HalfFloatSliceExt::convert_from_f32_slice/constants",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&mut constant_buffer).convert_from_f32_slice(black_box(&constants)))
        },
    );

    let large: Vec<_> = iter::repeat(0)
        .enumerate()
        .map(|(i, _)| i as f32)
        .take(SIMD_LARGE_BENCH_SLICE_LEN)
        .collect();
    let mut large_buffer = [f16::ZERO; SIMD_LARGE_BENCH_SLICE_LEN];
    c.bench_function(
        "HalfFloatSliceExt::convert_from_f32_slice/large",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&mut large_buffer).convert_from_f32_slice(black_box(&large)))
        },
    );
}

fn bench_slice_f64_to_f16(c: &mut Criterion) {
    let mut constant_buffer = [f16::ZERO; 11];
    let constants = [
        0.,
        -0.,
        1.,
        f64::MIN,
        f64::MAX,
        f64::MIN_POSITIVE,
        f64::NEG_INFINITY,
        f64::INFINITY,
        f64::NAN,
        f64::consts::E,
        f64::consts::PI,
    ];
    c.bench_function(
        "HalfFloatSliceExt::convert_from_f64_slice/constants",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&mut constant_buffer).convert_from_f64_slice(black_box(&constants)))
        },
    );

    let large: Vec<_> = iter::repeat(0)
        .enumerate()
        .map(|(i, _)| i as f64)
        .take(SIMD_LARGE_BENCH_SLICE_LEN)
        .collect();
    let mut large_buffer = [f16::ZERO; SIMD_LARGE_BENCH_SLICE_LEN];
    c.bench_function(
        "HalfFloatSliceExt::convert_from_f64_slice/large",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&mut large_buffer).convert_from_f64_slice(black_box(&large)))
        },
    );
}

fn bench_slice_f16_to_f32(c: &mut Criterion) {
    let mut constant_buffer = [0f32; 11];
    let constants = [
        f16::ZERO,
        f16::NEG_ZERO,
        f16::ONE,
        f16::MIN,
        f16::MAX,
        f16::MIN_POSITIVE,
        f16::NEG_INFINITY,
        f16::INFINITY,
        f16::NAN,
        f16::E,
        f16::PI,
    ];
    c.bench_function(
        "HalfFloatSliceExt::convert_to_f32_slice/constants",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&constants).convert_to_f32_slice(black_box(&mut constant_buffer)))
        },
    );

    let large: Vec<_> = iter::repeat(0)
        .enumerate()
        .map(|(i, _)| f16::from_f32(i as f32))
        .take(SIMD_LARGE_BENCH_SLICE_LEN)
        .collect();
    let mut large_buffer = [0f32; SIMD_LARGE_BENCH_SLICE_LEN];
    c.bench_function(
        "HalfFloatSliceExt::convert_to_f32_slice/large",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&large).convert_to_f32_slice(black_box(&mut large_buffer)))
        },
    );
}

fn bench_slice_f16_to_f64(c: &mut Criterion) {
    let mut constant_buffer = [0f64; 11];
    let constants = [
        f16::ZERO,
        f16::NEG_ZERO,
        f16::ONE,
        f16::MIN,
        f16::MAX,
        f16::MIN_POSITIVE,
        f16::NEG_INFINITY,
        f16::INFINITY,
        f16::NAN,
        f16::E,
        f16::PI,
    ];
    c.bench_function(
        "HalfFloatSliceExt::convert_to_f64_slice/constants",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&constants).convert_to_f64_slice(black_box(&mut constant_buffer)))
        },
    );

    let large: Vec<_> = iter::repeat(0)
        .enumerate()
        .map(|(i, _)| f16::from_f64(i as f64))
        .take(SIMD_LARGE_BENCH_SLICE_LEN)
        .collect();
    let mut large_buffer = [0f64; SIMD_LARGE_BENCH_SLICE_LEN];
    c.bench_function(
        "HalfFloatSliceExt::convert_to_f64_slice/large",
        |b: &mut Bencher<'_>| {
            b.iter(|| black_box(&large).convert_to_f64_slice(black_box(&mut large_buffer)))
        },
    );
}

criterion_group!(
    f16_simd,
    bench_slice_f32_to_f16,
    bench_slice_f64_to_f16,
    bench_slice_f16_to_f32,
    bench_slice_f16_to_f64
);

fn bench_f32_to_bf16(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert bf16 From f32");
    for val in &[
        0.,
        -0.,
        1.,
        f32::MIN,
        f32::MAX,
        f32::MIN_POSITIVE,
        f32::NEG_INFINITY,
        f32::INFINITY,
        f32::NAN,
        f32::consts::E,
        f32::consts::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("bf16::from_f32", val), val, |b, i| {
            b.iter(|| bf16::from_f32(*i))
        });
    }
}

fn bench_f64_to_bf16(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert bf16 From f64");
    for val in &[
        0.,
        -0.,
        1.,
        f64::MIN,
        f64::MAX,
        f64::MIN_POSITIVE,
        f64::NEG_INFINITY,
        f64::INFINITY,
        f64::NAN,
        f64::consts::E,
        f64::consts::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("bf16::from_f64", val), val, |b, i| {
            b.iter(|| bf16::from_f64(*i))
        });
    }
}

fn bench_bf16_to_f32(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert bf16 to f32");
    for val in &[
        bf16::ZERO,
        bf16::NEG_ZERO,
        bf16::ONE,
        bf16::MIN,
        bf16::MAX,
        bf16::MIN_POSITIVE,
        bf16::NEG_INFINITY,
        bf16::INFINITY,
        bf16::NAN,
        bf16::E,
        bf16::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("bf16::to_f32", val), val, |b, i| {
            b.iter(|| i.to_f32())
        });
    }
}

fn bench_bf16_to_f64(c: &mut Criterion) {
    let mut group = c.benchmark_group("Convert bf16 to f64");
    for val in &[
        bf16::ZERO,
        bf16::NEG_ZERO,
        bf16::ONE,
        bf16::MIN,
        bf16::MAX,
        bf16::MIN_POSITIVE,
        bf16::NEG_INFINITY,
        bf16::INFINITY,
        bf16::NAN,
        bf16::E,
        bf16::PI,
    ] {
        group.bench_with_input(BenchmarkId::new("bf16::to_f64", val), val, |b, i| {
            b.iter(|| i.to_f64())
        });
    }
}

criterion_group!(
    bf16_sisd,
    bench_f32_to_bf16,
    bench_f64_to_bf16,
    bench_bf16_to_f32,
    bench_bf16_to_f64
);

criterion_main!(f16_sisd, bf16_sisd, f16_simd);
