use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use cord_sparse::*;
use std::time::Duration;

fn product_fn(fp: &FixedPoint) -> impl Fn(&[i64]) -> i64 + '_ {
    move |pt: &[i64]| {
        let mut result = fp.one();
        for &x in pt {
            result = fp.mul(result, x);
        }
        result
    }
}

fn bench_solve(c: &mut Criterion) {
    let fp = FixedPoint::new(32);
    let basis = MonomialBasis;
    let pts = GoldenPoints;

    let configs: Vec<(usize, usize)> = vec![
        (2, 25),
        (2, 75),
        (2, 216),
        (4, 10),
        (4, 25),
        (4, 56),
        (8, 5),
        (8, 8),
        (8, 14),
        (16, 4),
        (16, 7),
        (32, 3),
        (32, 5),
    ];

    let mut group = c.benchmark_group("solve");
    group.warm_up_time(Duration::from_millis(500));
    group.measurement_time(Duration::from_secs(3));

    for &(d, bound) in &configs {
        let n = cord_sparse::index::binom(bound + d, d);
        if n > 500_000 {
            group.sample_size(10);
        } else {
            group.sample_size(30);
        }

        let iter = BoundedSumIter::new(d, bound);
        let f = product_fn(&fp);
        let rhs = evaluate_function(&iter, &f, &pts, &fp);
        let mut op = create_interpolation_operator(fp, &iter, &basis, &pts);
        op.solve(rhs.clone());

        group.bench_with_input(
            BenchmarkId::new(format!("d{d}"), format!("b{bound}_n{n}")),
            &(d, bound),
            |b, &(d, bound)| {
                let iter = BoundedSumIter::new(d, bound);
                let f = product_fn(&fp);
                let rhs = evaluate_function(&iter, &f, &pts, &fp);
                let mut op = create_interpolation_operator(fp, &iter, &basis, &pts);
                b.iter(|| {
                    op.solve(rhs.clone())
                });
            },
        );
    }
    group.finish();
}

fn bench_apply(c: &mut Criterion) {
    let fp = FixedPoint::new(32);
    let basis = MonomialBasis;
    let pts = GoldenPoints;

    let configs: Vec<(usize, usize)> = vec![
        (2, 75),
        (4, 25),
        (8, 8),
        (16, 5),
    ];

    let mut group = c.benchmark_group("apply");
    group.warm_up_time(Duration::from_millis(500));
    group.measurement_time(Duration::from_secs(3));

    for &(d, bound) in &configs {
        let n = cord_sparse::index::binom(bound + d, d);
        if n > 100_000 {
            group.sample_size(10);
        } else {
            group.sample_size(30);
        }

        let iter = BoundedSumIter::new(d, bound);
        let f = product_fn(&fp);
        let rhs = evaluate_function(&iter, &f, &pts, &fp);
        let mut op = create_interpolation_operator(fp, &iter, &basis, &pts);
        let coeffs = op.solve(rhs);

        group.bench_with_input(
            BenchmarkId::new(format!("d{d}"), format!("b{bound}_n{n}")),
            &coeffs,
            |b, coeffs| {
                b.iter(|| {
                    op.apply(coeffs.clone())
                });
            },
        );
    }
    group.finish();
}

criterion_group!(benches, bench_solve, bench_apply);
criterion_main!(benches);
