use bone_kernel::{Arc2, Circle2, Curve2, Curve2Kind, Line2, intersect_curves};
use bone_types::{Angle, Length, Point2, Tolerance};
use uom::si::angle::radian;
use uom::si::length::millimeter;

const TOL: Tolerance = Tolerance::new(1e-9);

fn line(ax: f64, ay: f64, bx: f64, by: f64) -> Curve2Kind {
    let Ok(l) = Line2::new(Point2::from_mm(ax, ay), Point2::from_mm(bx, by), TOL) else {
        panic!("line endpoints distinct");
    };
    l.as_kind()
}

fn circle(cx: f64, cy: f64, r: f64) -> Curve2Kind {
    let Ok(c) = Circle2::new(Point2::from_mm(cx, cy), Length::new::<millimeter>(r), TOL) else {
        panic!("radius positive");
    };
    c.as_kind()
}

fn arc(cx: f64, cy: f64, r: f64, start: f64, sweep: f64) -> Curve2Kind {
    let Ok(a) = Arc2::new(
        Point2::from_mm(cx, cy),
        Length::new::<millimeter>(r),
        Angle::new::<radian>(start),
        Angle::new::<radian>(sweep),
        TOL,
    ) else {
        panic!("sweep nonzero");
    };
    a.as_kind()
}

fn row(label: &str, a: &Curve2Kind, b: &Curve2Kind) -> String {
    format!("{label} = {}", intersect_curves(a, b, TOL))
}

#[test]
fn intersection_matrix_surface() {
    let line_diag = line(-5.0, -5.0, 5.0, 5.0);
    let line_cross = line(-5.0, 5.0, 5.0, -5.0);
    let line_parallel = line(-5.0, -4.0, 5.0, 6.0);
    let line_coincident = line(-2.0, -2.0, 2.0, 2.0);
    let line_collinear_touch = line(5.0, 5.0, 10.0, 10.0);
    let line_miss = line(10.0, 10.0, 11.0, 11.0);
    let line_tangent = line(-5.0, 3.0, 5.0, 3.0);
    let line_through_circle = line(-5.0, 0.0, 5.0, 0.0);

    let circle_unit = circle(0.0, 0.0, 3.0);
    let circle_shift = circle(4.0, 0.0, 3.0);
    let circle_contain = circle(0.0, 0.0, 1.0);
    let circle_dup = circle(0.0, 0.0, 3.0);
    let circle_far = circle(20.0, 0.0, 1.0);

    let arc_right = arc(
        0.0,
        0.0,
        3.0,
        -core::f64::consts::FRAC_PI_2,
        core::f64::consts::PI,
    );
    let arc_upper = arc(0.0, 0.0, 3.0, 0.0, core::f64::consts::PI);
    let arc_other = arc(
        4.0,
        0.0,
        3.0,
        core::f64::consts::FRAC_PI_2,
        core::f64::consts::PI,
    );
    let line_above_arc = line(-5.0, 5.0, 5.0, 5.0);

    let arc_same_disjoint_q1 = arc(0.0, 0.0, 3.0, 0.0, core::f64::consts::FRAC_PI_2);
    let arc_same_disjoint_q3 = arc(
        0.0,
        0.0,
        3.0,
        core::f64::consts::PI,
        core::f64::consts::FRAC_PI_2,
    );
    let arc_same_touch_left = arc(0.0, 0.0, 3.0, 0.0, core::f64::consts::PI);
    let arc_same_touch_right = arc(0.0, 0.0, 3.0, core::f64::consts::PI, core::f64::consts::PI);
    let arc_same_partial_b = arc(
        0.0,
        0.0,
        3.0,
        core::f64::consts::FRAC_PI_2,
        core::f64::consts::PI,
    );
    let arc_same_single_touch = arc(
        0.0,
        0.0,
        3.0,
        core::f64::consts::FRAC_PI_2,
        core::f64::consts::FRAC_PI_2,
    );

    let rows = [
        row("line_line_cross", &line_diag, &line_cross),
        row("line_line_parallel_miss", &line_diag, &line_parallel),
        row("line_line_coincident_overlap", &line_diag, &line_coincident),
        row(
            "line_line_collinear_touch",
            &line_diag,
            &line_collinear_touch,
        ),
        row("line_line_segment_miss", &line_diag, &line_miss),
        row("line_circle_tangent", &line_tangent, &circle_unit),
        row("line_circle_secant", &line_through_circle, &circle_unit),
        row("line_arc_right_hit", &line_through_circle, &arc_right),
        row("line_arc_upper_endpoints", &line_through_circle, &arc_upper),
        row("line_arc_above_miss", &line_above_arc, &arc_upper),
        row("circle_circle_two_points", &circle_unit, &circle_shift),
        row("circle_circle_contained", &circle_unit, &circle_contain),
        row("circle_circle_coincident", &circle_unit, &circle_dup),
        row("circle_circle_disjoint", &circle_unit, &circle_far),
        row("circle_arc_partial", &circle_shift, &arc_upper),
        row("arc_arc_upper_vs_other", &arc_upper, &arc_other),
        row(
            "arc_arc_same_circle_disjoint",
            &arc_same_disjoint_q1,
            &arc_same_disjoint_q3,
        ),
        row(
            "arc_arc_same_circle_two_touches",
            &arc_same_touch_left,
            &arc_same_touch_right,
        ),
        row(
            "arc_arc_same_circle_partial_overlap",
            &arc_upper,
            &arc_same_partial_b,
        ),
        row(
            "arc_arc_same_circle_single_touch",
            &arc_same_disjoint_q1,
            &arc_same_single_touch,
        ),
    ];

    let surface = rows.join("\n");
    insta::assert_snapshot!(surface);
}
