use criterion::{Criterion, criterion_group, criterion_main};
use hronn::prelude::*;
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::hint::black_box;
use std::{env, path::Path};
use vector_traits::glam::{DVec2, DVec3, Vec3};
use vector_traits::prelude::Aabb2;
use vector_traits::prelude::GenericVector2;

fn original_triangles_large(c: &mut Criterion) {
    let probe_radius = 1.0;
    let minimum_z = 0.0;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let filename = Path::new(&manifest_dir).join("benches").join("sample.obj");
    let mesh_analyzer = MeshAnalyzerBuilder::<DVec3, Vec3>::default()
        .load_from_obj(&filename)
        .unwrap()
        .build()
        .unwrap();
    let probe = BallNoseProbe::new(&mesh_analyzer, probe_radius).unwrap();
    let config = SearchPatternConfig::<DVec3, Vec3>::new(&probe, minimum_z);
    let aabb = Aabb2::from_points([DVec2::new(-4.5, -4.50), DVec2::new(5.5, 5.5)]);
    let convex_hull = Vec::from(aabb);
    let pattern = MeanderPattern::new(aabb, convex_hull, 0.1).unwrap();
    let mut g = c.benchmark_group("edge_tests");
    g.sample_size(10)
        .bench_function("original_triangles_large", |b| {
            b.iter(|| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let _ = pattern.search(&mesh_analyzer, &config).unwrap();
                });
            })
        });
    g.finish();
}

fn split_triangles_large(c: &mut Criterion) {
    let probe_radius: f64 = 1.0;
    let minimum_z = 0.0;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let filename = Path::new(&manifest_dir).join("benches").join("sample.obj");
    let mesh_analyzer = MeshAnalyzerBuilder::<DVec3, Vec3>::default()
        .load_from_obj(&filename)
        .unwrap()
        .with_split(probe_radius)
        .unwrap()
        .build()
        .unwrap();
    let probe = BallNoseProbe::new(&mesh_analyzer, probe_radius).unwrap();
    let config = SearchPatternConfig::<DVec3, Vec3>::new(&probe, minimum_z);
    let aabb = <DVec2 as GenericVector2>::Aabb::from_points([
        DVec2::new(-4.5, -4.50),
        DVec2::new(5.5, 5.5),
    ]);
    let convex_hull = Vec::from(aabb);
    let pattern = MeanderPattern::new(aabb, convex_hull, 0.1).unwrap();
    let mut g = c.benchmark_group("edge_tests");
    g.sample_size(10)
        .bench_function("split_triangles_large", |b| {
            b.iter(|| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let _ = pattern.search(&mesh_analyzer, &config).unwrap();
                });
            })
        });
    g.finish();
}

/// test a small radius probe without splitting triangles
fn original_triangles_small(c: &mut Criterion) {
    let probe_radius = 0.5;
    let minimum_z = 0.0;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let filename = Path::new(&manifest_dir).join("benches").join("sample.obj");
    let mesh_analyzer = MeshAnalyzerBuilder::<DVec3, Vec3>::default()
        .load_from_obj(&filename)
        .unwrap()
        .build()
        .unwrap();
    let probe = BallNoseProbe::new(&mesh_analyzer, probe_radius).unwrap();
    let config = SearchPatternConfig::<DVec3, Vec3>::new(&probe, minimum_z);
    let aabb = <DVec2 as GenericVector2>::Aabb::from_points([
        DVec2::new(-4.5, -4.50),
        DVec2::new(5.5, 5.5),
    ]);
    let convex_hull = Vec::from(aabb);
    let pattern = MeanderPattern::new(aabb, convex_hull, 0.1).unwrap();
    let mut g = c.benchmark_group("edge_tests");

    g.sample_size(10)
        .bench_function("original_triangles_small", |b| {
            b.iter(|| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let _ = pattern.search(&mesh_analyzer, &config).unwrap();
                });
            })
        });
    g.finish();
}

/// test a small radius probe with splitting triangles
fn split_triangles_small(c: &mut Criterion) {
    let probe_radius = 0.5;
    let minimum_z = 0.0;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let filename = Path::new(&manifest_dir).join("benches").join("sample.obj");
    let mesh_analyzer = MeshAnalyzerBuilder::<DVec3, Vec3>::default()
        .load_from_obj(&filename)
        .unwrap()
        .with_split(probe_radius)
        .unwrap()
        .build()
        .unwrap();
    let probe = BallNoseProbe::new(&mesh_analyzer, probe_radius).unwrap();
    let config = SearchPatternConfig::<DVec3, Vec3>::new(&probe, minimum_z);
    let aabb = <DVec2 as GenericVector2>::Aabb::from_points([
        DVec2::new(-4.5, -4.50),
        DVec2::new(5.5, 5.5),
    ]);
    let pattern = MeanderPattern::new(aabb, aabb.convex_hull(), 0.1).unwrap();
    let mut g = c.benchmark_group("edge_tests");
    g.sample_size(10)
        .bench_function("split_triangles_small", |b| {
            b.iter(|| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let _ = pattern.search(&mesh_analyzer, &config).unwrap();
                });
            })
        });
    g.finish();
}

fn bench_edge_detect(c: &mut Criterion) {
    let seed = 3243312434343; // A constant seed for deterministic results.
    let mut rng: StdRng = SeedableRng::seed_from_u64(seed);
    let probe_radius = 2.1;
    let center = DVec2::new(5.0, 5.0);

    // Pre-generate test cases with varying ratios between cone slope and line slope
    let test_cases: Vec<(DVec3, DVec3, f64)> = {
        let mut cases = Vec::with_capacity(300);

        // Generate lines and heights to ensure varied cases
        for _ in 0..100 {
            // Case 1: cone slope likely < line slope
            let p1 = DVec3::new(
                rng.random_range(1.0..9.0),
                rng.random_range(1.0..9.0),
                rng.random_range(1.0..9.0),
            );
            let p2 = DVec3::new(
                rng.random_range(1.0..9.0),
                rng.random_range(1.0..9.0),
                rng.random_range(1.0..9.0),
            );
            // Shorter cone height
            cases.push((p1, p2, rng.random_range(1.0..2.0)));

            // Case 2: cone slope likely > line slope
            let p1 = DVec3::new(
                rng.random_range(4.0..6.0), // Closer to center
                rng.random_range(4.0..6.0),
                rng.random_range(1.0..9.0),
            );
            let p2 = DVec3::new(
                rng.random_range(4.0..6.0), // Closer to center
                rng.random_range(4.0..6.0),
                rng.random_range(1.0..9.0),
            );
            // Taller cone height
            cases.push((p1, p2, rng.random_range(5.0..7.0)));

            // Case 3: boundary case
            // Calculate a position where cone slope ≈ line slope
            // This requires more complex calculation based on your specific algorithm
            let p1 = DVec3::new(
                rng.random_range(2.0..8.0),
                rng.random_range(2.0..8.0),
                rng.random_range(1.0..9.0),
            );
            let p2 = DVec3::new(
                rng.random_range(2.0..8.0),
                rng.random_range(2.0..8.0),
                rng.random_range(1.0..9.0),
            );
            // Medium cone height
            cases.push((p1, p2, rng.random_range(2.8..3.2)));
        }
        cases
    };

    let mut group = c.benchmark_group("edge_collision_group");
    group.bench_function("z_coord_sphere_to_edge_0", |b| {
        b.iter(|| {
            test_cases.iter().for_each(|(p1, p2, _)| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let edge = hronn::debug_access::xy_distance_to_line_squared(center, *p1, *p2);
                    hronn::debug_access::debug_ball_end_to_edge_collision::<DVec3>(
                        edge,
                        probe_radius,
                    );
                })
            })
        });
    });
    group.bench_function("z_coord_square_end_to_edge_0", |b| {
        b.iter(|| {
            test_cases.iter().for_each(|(p1, p2, _)| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let edge = hronn::debug_access::xy_distance_to_line_squared(center, *p1, *p2);
                    hronn::debug_access::debug_square_end_to_edge_collision::<DVec3>(
                        edge,
                        probe_radius,
                    );
                })
            })
        });
    });
    group.bench_function("z_coord_cone_to_edge_0", |b| {
        b.iter(|| {
            test_cases.iter().for_each(|(p1, p2, height)| {
                #[allow(clippy::unit_arg)]
                black_box({
                    let edge = hronn::debug_access::xy_distance_to_line_squared(center, *p1, *p2);
                    hronn::debug_access::debug_cone_to_edge_collision::<DVec3>(
                        edge,
                        probe_radius,
                        *height, // Use the varied height
                    );
                })
            })
        });
    });
    group.finish();
}

criterion_group!(
    collision_bench,
    split_triangles_large,
    original_triangles_large,
    split_triangles_small,
    original_triangles_small,
    bench_edge_detect,
);

criterion_main!(collision_bench);
