use bone_kernel::{
    AngularSpan, ConeSurface, CylinderSurface, PlaneSurface, SphereSurface, Surface3, TorusSurface,
    TriMesh,
};
use bone_types::{
    Angle, AngleTolerance, ChordHeightTolerance, Length, Parameter, Plane3, Point3, Tolerance,
    UnitVec3, Vec3,
};
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use core::fmt::{Debug, Display};
use uom::si::angle::radian;
use uom::si::length::millimeter;

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

fn mm(value: f64) -> Length {
    Length::new::<millimeter>(value)
}

fn rad(value: f64) -> Angle {
    Angle::new::<radian>(value)
}

fn span(start: f64, sweep: f64) -> AngularSpan {
    AngularSpan::new(rad(start), rad(sweep))
}

fn p(u: f64) -> Parameter {
    Parameter::new(u)
}

fn fmt_component(value: f64) -> String {
    let snapped = if value.abs() < 5.0e-7 { 0.0 } else { value };
    format!("{snapped:.6}")
}

fn fmt_point(point: Point3) -> String {
    let (x, y, z) = point.coords_mm();
    format!(
        "({}, {}, {})",
        fmt_component(x),
        fmt_component(y),
        fmt_component(z)
    )
}

fn fmt_unit(normal: UnitVec3) -> String {
    let (x, y, z) = normal.components();
    format!(
        "[{}, {}, {}]",
        fmt_component(x),
        fmt_component(y),
        fmt_component(z)
    )
}

fn fmt_vec(vector: Vec3) -> String {
    let (x, y, z) = vector.coords_mm();
    format!(
        "[{}, {}, {}]",
        fmt_component(x),
        fmt_component(y),
        fmt_component(z)
    )
}

fn xy_plane(origin: Point3) -> Plane3 {
    Plane3::new_unchecked(origin, UnitVec3::x_axis(), UnitVec3::y_axis())
}

fn xz_plane(origin: Point3) -> Plane3 {
    Plane3::new_unchecked(origin, UnitVec3::x_axis(), UnitVec3::z_axis())
}

fn dot_un(vector: Vec3, unit: UnitVec3) -> f64 {
    vector.dot_mm2(unit.into_vec(mm(1.0)))
}

fn fmt_surface3<S: Surface3 + Display + Debug>(
    name: &str,
    s: &S,
    chord: ChordHeightTolerance,
    angle: AngleTolerance,
) -> String {
    let samples = [
        (0.0, 0.0),
        (0.5, 0.0),
        (1.0, 0.0),
        (0.0, 1.0),
        (0.5, 0.5),
        (1.0, 1.0),
    ];
    let rows = samples
        .iter()
        .map(|&(u, v)| {
            let (du, dv) = s.partials(p(u), p(v));
            format!(
                "(u={u:.2}, v={v:.2}) -> pt={} n={} du={} dv={}",
                fmt_point(s.evaluate(p(u), p(v))),
                fmt_unit(s.normal(p(u), p(v))),
                fmt_vec(du),
                fmt_vec(dv),
            )
        })
        .collect::<Vec<_>>()
        .join("\n    ");
    let mesh = s.tessellate(chord, angle);
    let verts = mesh
        .vertices()
        .iter()
        .map(|mv| format!("{} n={}", fmt_point(mv.position()), fmt_unit(mv.normal())))
        .collect::<Vec<_>>()
        .join("\n    ");
    let tris = mesh
        .triangles()
        .iter()
        .map(|t| format!("{t:?}"))
        .collect::<Vec<_>>()
        .join(" ");
    format!(
        "{name}_display = {s}\n\
         {name}_debug   = {s:?}\n\
         {name}_samples:\n    {rows}\n\
         {name}_bbox    = {}\n\
         {name}_mesh (verts={}, tris={}):\n    {verts}\n\
         {name}_triangles: {tris}",
        s.bounding_box(),
        mesh.vertices().len(),
        mesh.triangles().len(),
    )
}

fn full_cylinder(plane: Plane3, radius: Length, height: Length) -> CylinderSurface {
    let Ok(cyl) = CylinderSurface::new(plane, radius, rad(0.0), rad(TAU), height, TOL) else {
        panic!("nondegenerate cylinder");
    };
    cyl
}

fn neg_sweep_cylinder() -> CylinderSurface {
    let Ok(cyl) = CylinderSurface::new(
        xz_plane(Point3::from_mm(1.0, -2.0, 0.0)),
        mm(3.0),
        rad(PI),
        rad(-PI),
        mm(5.0),
        TOL,
    ) else {
        panic!("nondegenerate cylinder");
    };
    cyl
}

#[test]
fn plane_surface_axis_aligned() {
    let Ok(plane) = PlaneSurface::new(
        xy_plane(Point3::from_mm(1.0, 2.0, 3.0)),
        mm(4.0),
        mm(3.0),
        TOL,
    ) else {
        panic!("positive extents");
    };
    insta::assert_snapshot!(fmt_surface3(
        "plane",
        &plane,
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn plane_surface_tilted() {
    let Ok(plane) = PlaneSurface::new(
        xz_plane(Point3::from_mm(0.0, 1.0, 0.0)),
        mm(2.0),
        mm(5.0),
        TOL,
    ) else {
        panic!("positive extents");
    };
    insta::assert_snapshot!(fmt_surface3(
        "plane_tilted",
        &plane,
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn cylinder_surface_full_circle() {
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    insta::assert_snapshot!(fmt_surface3(
        "cyl_full",
        &cyl,
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn cylinder_surface_half_wall() {
    let Ok(cyl) = CylinderSurface::new(
        xz_plane(Point3::from_mm(1.0, 0.0, 0.0)),
        mm(2.0),
        rad(0.0),
        rad(PI),
        mm(4.0),
        TOL,
    ) else {
        panic!("nondegenerate cylinder");
    };
    insta::assert_snapshot!(fmt_surface3(
        "cyl_half",
        &cyl,
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn plane_surface_rejects_zero_extent() {
    assert!(PlaneSurface::new(xy_plane(Point3::origin()), mm(0.0), mm(3.0), TOL).is_err());
    assert!(PlaneSurface::new(xy_plane(Point3::origin()), mm(3.0), mm(0.0), TOL).is_err());
}

#[test]
fn cylinder_surface_rejects_degenerate() {
    let plane = xy_plane(Point3::origin());
    assert!(CylinderSurface::new(plane, mm(0.0), rad(0.0), rad(TAU), mm(1.0), TOL).is_err());
    assert!(CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(TAU), mm(0.0), TOL).is_err());
    assert!(CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(0.0), mm(1.0), TOL).is_err());
    assert!(CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(3.0 * PI), mm(1.0), TOL).is_err());
}

#[test]
fn plane_surface_rejects_non_finite() {
    let plane = xy_plane(Point3::origin());
    assert!(PlaneSurface::new(plane, mm(f64::NAN), mm(3.0), TOL).is_err());
    assert!(PlaneSurface::new(plane, mm(4.0), mm(f64::NAN), TOL).is_err());
    assert!(PlaneSurface::new(plane, mm(f64::INFINITY), mm(3.0), TOL).is_err());
}

#[test]
fn cylinder_surface_rejects_non_finite() {
    let plane = xy_plane(Point3::origin());
    assert!(CylinderSurface::new(plane, mm(f64::NAN), rad(0.0), rad(PI), mm(1.0), TOL).is_err());
    assert!(CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(PI), mm(f64::NAN), TOL).is_err());
    assert!(CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(f64::NAN), mm(1.0), TOL).is_err());
    assert!(
        CylinderSurface::new(plane, mm(1.0), rad(0.0), rad(f64::INFINITY), mm(1.0), TOL).is_err()
    );
}

#[test]
fn plane_surface_bbox_spans_corners() {
    let Ok(plane) = PlaneSurface::new(
        xy_plane(Point3::from_mm(1.0, 2.0, 5.0)),
        mm(4.0),
        mm(3.0),
        TOL,
    ) else {
        panic!("positive extents");
    };
    let bbox = plane.bounding_box();
    let (lx, ly, lz) = bbox.min().coords_mm();
    let (hx, hy, hz) = bbox.max().coords_mm();
    assert!((lx - 1.0).abs() < 1e-9 && (hx - 5.0).abs() < 1e-9);
    assert!((ly - 2.0).abs() < 1e-9 && (hy - 5.0).abs() < 1e-9);
    assert!((lz - 5.0).abs() < 1e-9 && (hz - 5.0).abs() < 1e-9);
}

#[test]
fn cylinder_surface_bbox_spans_tube() {
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    let bbox = cyl.bounding_box();
    let (lx, ly, lz) = bbox.min().coords_mm();
    let (hx, hy, hz) = bbox.max().coords_mm();
    assert!((lx + 5.0).abs() < 1e-9 && (hx - 5.0).abs() < 1e-9);
    assert!((ly + 5.0).abs() < 1e-9 && (hy - 5.0).abs() < 1e-9);
    assert!(lz.abs() < 1e-9 && (hz - 10.0).abs() < 1e-9);
}

#[test]
fn cylinder_neg_sweep_bbox_spans_arc() {
    let bbox = neg_sweep_cylinder().bounding_box();
    let (lx, ly, lz) = bbox.min().coords_mm();
    let (hx, hy, hz) = bbox.max().coords_mm();
    assert!((lx + 2.0).abs() < 1e-9 && (hx - 4.0).abs() < 1e-9);
    assert!((ly + 7.0).abs() < 1e-9 && (hy + 2.0).abs() < 1e-9);
    assert!(lz.abs() < 1e-9 && (hz - 3.0).abs() < 1e-9);
}

fn assert_partials_orthogonal_to_normal<S: Surface3>(name: &str, s: &S) {
    let grid = [(0.0, 0.0), (0.3, 0.7), (0.5, 0.5), (1.0, 1.0), (0.8, 0.2)];
    grid.iter().for_each(|&(u, v)| {
        let (du, dv) = s.partials(p(u), p(v));
        let n = s.normal(p(u), p(v));
        assert!(
            dot_un(du, n).abs() < 1e-9,
            "{name}: du not orthogonal to normal at ({u},{v})"
        );
        assert!(
            dot_un(dv, n).abs() < 1e-9,
            "{name}: dv not orthogonal to normal at ({u},{v})"
        );
    });
}

#[test]
fn normal_is_orthogonal_to_partials() {
    let Ok(plane) = PlaneSurface::new(
        xz_plane(Point3::from_mm(1.0, 2.0, 3.0)),
        mm(4.0),
        mm(3.0),
        TOL,
    ) else {
        panic!("positive extents");
    };
    assert_partials_orthogonal_to_normal("plane", &plane);
    let cyl = full_cylinder(xz_plane(Point3::from_mm(2.0, 0.0, -1.0)), mm(3.0), mm(7.0));
    assert_partials_orthogonal_to_normal("cylinder", &cyl);
    assert_partials_orthogonal_to_normal("neg_cylinder", &neg_sweep_cylinder());
}

fn assert_normal_matches_cross<S: Surface3>(name: &str, s: &S) {
    let grid = [(0.0, 0.5), (0.4, 0.0), (1.0, 1.0)];
    grid.iter().for_each(|&(u, v)| {
        let (du, dv) = s.partials(p(u), p(v));
        let Ok(cross) = du.cross(dv).try_normalize(TOL) else {
            panic!("{name}: degenerate partials at ({u},{v})");
        };
        let n = s.normal(p(u), p(v));
        let (cx, cy, cz) = cross.components();
        let (nx, ny, nz) = n.components();
        assert!(
            (cx - nx).abs() < 1e-9 && (cy - ny).abs() < 1e-9 && (cz - nz).abs() < 1e-9,
            "{name}: normal disagrees with normalize(du x dv) at ({u},{v})"
        );
    });
}

#[test]
fn normal_agrees_with_partial_cross_product() {
    let Ok(plane) = PlaneSurface::new(xy_plane(Point3::origin()), mm(4.0), mm(3.0), TOL) else {
        panic!("positive extents");
    };
    assert_normal_matches_cross("plane", &plane);
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    assert_normal_matches_cross("cylinder", &cyl);
    assert_normal_matches_cross("neg_cylinder", &neg_sweep_cylinder());
}

fn assert_winding_matches_normals<S: Surface3>(name: &str, s: &S) {
    let mesh: TriMesh = s.tessellate(
        ChordHeightTolerance::from_mm(0.25),
        AngleTolerance::from_radians(0.4),
    );
    let verts = mesh.vertices();
    mesh.triangles().iter().for_each(|&[a, b, c]| {
        let va = verts[a as usize];
        let vb = verts[b as usize];
        let vc = verts[c as usize];
        let face = (vb.position() - va.position()).cross(vc.position() - va.position());
        [va, vb, vc].iter().for_each(|vert| {
            assert!(
                dot_un(face, vert.normal()) > 0.0,
                "{name}: triangle [{a},{b},{c}] winds against vertex normal"
            );
        });
    });
}

#[test]
fn tessellation_winding_follows_normals() {
    let Ok(plane) = PlaneSurface::new(xy_plane(Point3::origin()), mm(4.0), mm(3.0), TOL) else {
        panic!("positive extents");
    };
    assert_winding_matches_normals("plane", &plane);
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    assert_winding_matches_normals("cylinder", &cyl);
    let Ok(arc_wall) = CylinderSurface::new(
        xz_plane(Point3::origin()),
        mm(2.0),
        rad(FRAC_PI_2),
        rad(PI),
        mm(3.0),
        TOL,
    ) else {
        panic!("nondegenerate cylinder");
    };
    assert_winding_matches_normals("arc_wall", &arc_wall);
    assert_winding_matches_normals("neg_wall", &neg_sweep_cylinder());
}

#[test]
fn tessellation_is_deterministic() {
    let cyl = full_cylinder(xy_plane(Point3::from_mm(1.0, -2.0, 0.5)), mm(4.0), mm(6.0));
    let chord = ChordHeightTolerance::from_mm(0.3);
    let angle = AngleTolerance::from_radians(0.5);
    assert_eq!(cyl.tessellate(chord, angle), cyl.tessellate(chord, angle));
}

#[test]
fn cylinder_tessellation_count_responds_to_both_tolerances() {
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    let coarse = cyl.tessellate(
        ChordHeightTolerance::from_mm(1.0),
        AngleTolerance::from_radians(1.5),
    );
    let chord_driven = cyl.tessellate(
        ChordHeightTolerance::from_mm(0.05),
        AngleTolerance::from_radians(1.5),
    );
    let angle_driven = cyl.tessellate(
        ChordHeightTolerance::from_mm(1.0),
        AngleTolerance::from_radians(0.1),
    );
    assert!(chord_driven.vertices().len() > coarse.vertices().len());
    assert!(angle_driven.vertices().len() > coarse.vertices().len());
}

#[test]
fn plane_surface_membership() {
    let Ok(plane) = PlaneSurface::new(
        xy_plane(Point3::from_mm(1.0, 1.0, 2.0)),
        mm(4.0),
        mm(3.0),
        TOL,
    ) else {
        panic!("positive extents");
    };
    assert!(plane.contains_point(plane.evaluate(p(0.5), p(0.5)), Tolerance::new(1e-6)));
    assert!(!plane.contains_point(Point3::from_mm(2.0, 2.0, 3.0), Tolerance::new(1e-6)));
    assert!(!plane.contains_point(Point3::from_mm(20.0, 2.0, 2.0), Tolerance::new(1e-6)));
}

#[test]
fn cylinder_surface_membership() {
    let Ok(cyl) = CylinderSurface::new(
        xy_plane(Point3::origin()),
        mm(5.0),
        rad(0.0),
        rad(PI),
        mm(10.0),
        TOL,
    ) else {
        panic!("nondegenerate cylinder");
    };
    let on = Tolerance::new(1e-6);
    assert!(cyl.contains_point(cyl.evaluate(p(0.5), p(0.5)), on));
    assert!(!cyl.contains_point(Point3::from_mm(6.0, 0.0, 5.0), on));
    assert!(!cyl.contains_point(Point3::from_mm(5.0, 0.0, 12.0), on));
    assert!(!cyl.contains_point(Point3::from_mm(0.0, -5.0, 5.0), on));
}

#[test]
fn cylinder_full_circle_membership_wraps() {
    let cyl = full_cylinder(xy_plane(Point3::origin()), mm(5.0), mm(10.0));
    let on = Tolerance::new(1e-6);
    [0.0, 0.25, 0.5, 0.75].into_iter().for_each(|u| {
        assert!(
            cyl.contains_point(cyl.evaluate(p(u), p(0.5)), on),
            "full sweep should contain its surface point at u={u}"
        );
    });
    assert!(!cyl.contains_point(Point3::from_mm(0.0, 0.0, 5.0), on));
    assert!(!cyl.contains_point(Point3::from_mm(5.0, 0.0, 12.0), on));
}

fn full_cone() -> ConeSurface {
    let Ok(cone) = ConeSurface::new(
        xy_plane(Point3::origin()),
        mm(4.0),
        rad(0.3),
        span(0.0, TAU),
        mm(6.0),
        TOL,
    ) else {
        panic!("nondegenerate cone");
    };
    cone
}

fn partial_cone() -> ConeSurface {
    let Ok(cone) = ConeSurface::new(
        xz_plane(Point3::from_mm(1.0, 0.0, 0.0)),
        mm(2.0),
        rad(-0.4),
        span(FRAC_PI_2, PI),
        mm(3.0),
        TOL,
    ) else {
        panic!("nondegenerate cone");
    };
    cone
}

fn sphere_band() -> SphereSurface {
    let Ok(sphere) = SphereSurface::new(
        xy_plane(Point3::origin()),
        mm(5.0),
        span(FRAC_PI_2 / 2.0, FRAC_PI_2),
        span(0.0, TAU),
        TOL,
    ) else {
        panic!("nondegenerate sphere");
    };
    sphere
}

fn sphere_wedge() -> SphereSurface {
    let Ok(sphere) = SphereSurface::new(
        xz_plane(Point3::from_mm(1.0, -1.0, 2.0)),
        mm(3.0),
        span(0.2, 1.0),
        span(0.0, PI),
        TOL,
    ) else {
        panic!("nondegenerate sphere");
    };
    sphere
}

fn neg_sweep_sphere() -> SphereSurface {
    let Ok(sphere) = SphereSurface::new(
        xy_plane(Point3::origin()),
        mm(5.0),
        span(0.2, 1.0),
        span(PI, -FRAC_PI_2),
        TOL,
    ) else {
        panic!("nondegenerate sphere");
    };
    sphere
}

fn full_torus() -> TorusSurface {
    let Ok(torus) = TorusSurface::new(
        xy_plane(Point3::origin()),
        mm(5.0),
        mm(2.0),
        span(0.0, TAU),
        span(0.0, TAU),
        TOL,
    ) else {
        panic!("nondegenerate torus");
    };
    torus
}

fn partial_torus() -> TorusSurface {
    let Ok(torus) = TorusSurface::new(
        xz_plane(Point3::from_mm(0.0, 0.0, 1.0)),
        mm(4.0),
        mm(1.0),
        span(0.0, PI),
        span(FRAC_PI_2, PI),
        TOL,
    ) else {
        panic!("nondegenerate torus");
    };
    torus
}

fn neg_sweep_torus() -> TorusSurface {
    let Ok(torus) = TorusSurface::new(
        xy_plane(Point3::origin()),
        mm(4.0),
        mm(1.0),
        span(0.0, PI),
        span(0.0, -PI),
        TOL,
    ) else {
        panic!("nondegenerate torus");
    };
    torus
}

#[test]
fn cone_surface_full_revolution() {
    insta::assert_snapshot!(fmt_surface3(
        "cone_full",
        &full_cone(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn cone_surface_partial_wall() {
    insta::assert_snapshot!(fmt_surface3(
        "cone_partial",
        &partial_cone(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn sphere_surface_band_golden() {
    insta::assert_snapshot!(fmt_surface3(
        "sphere_band",
        &sphere_band(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn sphere_surface_wedge_golden() {
    insta::assert_snapshot!(fmt_surface3(
        "sphere_wedge",
        &sphere_wedge(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn torus_surface_full_golden() {
    insta::assert_snapshot!(fmt_surface3(
        "torus_full",
        &full_torus(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn torus_surface_partial_golden() {
    insta::assert_snapshot!(fmt_surface3(
        "torus_partial",
        &partial_torus(),
        ChordHeightTolerance::from_mm(0.5),
        AngleTolerance::from_radians(1.0),
    ));
}

#[test]
fn revolution_normals_orthogonal_to_partials() {
    assert_partials_orthogonal_to_normal("cone_full", &full_cone());
    assert_partials_orthogonal_to_normal("cone_partial", &partial_cone());
    assert_partials_orthogonal_to_normal("sphere_band", &sphere_band());
    assert_partials_orthogonal_to_normal("sphere_wedge", &sphere_wedge());
    assert_partials_orthogonal_to_normal("torus_full", &full_torus());
    assert_partials_orthogonal_to_normal("torus_partial", &partial_torus());
    assert_partials_orthogonal_to_normal("sphere_neg_sweep", &neg_sweep_sphere());
    assert_partials_orthogonal_to_normal("torus_neg_sweep", &neg_sweep_torus());
}

#[test]
fn revolution_normals_agree_with_partial_cross_product() {
    assert_normal_matches_cross("cone_full", &full_cone());
    assert_normal_matches_cross("cone_partial", &partial_cone());
    assert_normal_matches_cross("sphere_band", &sphere_band());
    assert_normal_matches_cross("sphere_wedge", &sphere_wedge());
    assert_normal_matches_cross("torus_full", &full_torus());
    assert_normal_matches_cross("torus_partial", &partial_torus());
    assert_normal_matches_cross("sphere_neg_sweep", &neg_sweep_sphere());
    assert_normal_matches_cross("torus_neg_sweep", &neg_sweep_torus());
}

#[test]
fn revolution_tessellation_winding_follows_normals() {
    assert_winding_matches_normals("cone_full", &full_cone());
    assert_winding_matches_normals("cone_partial", &partial_cone());
    assert_winding_matches_normals("sphere_band", &sphere_band());
    assert_winding_matches_normals("sphere_wedge", &sphere_wedge());
    assert_winding_matches_normals("torus_full", &full_torus());
    assert_winding_matches_normals("torus_partial", &partial_torus());
    assert_winding_matches_normals("sphere_neg_sweep", &neg_sweep_sphere());
    assert_winding_matches_normals("torus_neg_sweep", &neg_sweep_torus());
}

#[test]
fn revolution_tessellation_is_deterministic() {
    let chord = ChordHeightTolerance::from_mm(0.3);
    let angle = AngleTolerance::from_radians(0.5);
    assert_eq!(
        full_cone().tessellate(chord, angle),
        full_cone().tessellate(chord, angle)
    );
    assert_eq!(
        sphere_band().tessellate(chord, angle),
        sphere_band().tessellate(chord, angle)
    );
    assert_eq!(
        full_torus().tessellate(chord, angle),
        full_torus().tessellate(chord, angle)
    );
}

#[test]
fn sphere_bbox_spans_carrier() {
    let bbox = sphere_band().bounding_box();
    let (lx, ly, lz) = bbox.min().coords_mm();
    let (hx, hy, hz) = bbox.max().coords_mm();
    assert!((lx + 5.0).abs() < 1e-9 && (hx - 5.0).abs() < 1e-9);
    assert!((ly + 5.0).abs() < 1e-9 && (hy - 5.0).abs() < 1e-9);
    assert!((lz + 5.0).abs() < 1e-9 && (hz - 5.0).abs() < 1e-9);
}

#[test]
fn torus_bbox_spans_carrier() {
    let bbox = full_torus().bounding_box();
    let (lx, ly, lz) = bbox.min().coords_mm();
    let (hx, hy, hz) = bbox.max().coords_mm();
    assert!((lx + 7.0).abs() < 1e-9 && (hx - 7.0).abs() < 1e-9);
    assert!((ly + 7.0).abs() < 1e-9 && (hy - 7.0).abs() < 1e-9);
    assert!((lz + 2.0).abs() < 1e-9 && (hz - 2.0).abs() < 1e-9);
}

#[test]
fn cone_bbox_contains_surface() {
    let cone = full_cone();
    let bbox = cone.bounding_box();
    [(0.0, 0.0), (0.5, 1.0), (1.0, 0.5), (0.25, 0.75)]
        .into_iter()
        .for_each(|(u, v)| assert!(bbox.contains(cone.evaluate(p(u), p(v)))));
}

#[test]
fn revolution_membership() {
    let on = Tolerance::new(1e-6);
    let cone = full_cone();
    assert!(cone.contains_point(cone.evaluate(p(0.4), p(0.6)), on));
    assert!(!cone.contains_point(Point3::from_mm(0.0, 0.0, 3.0), on));

    let sphere = sphere_band();
    assert!(sphere.contains_point(sphere.evaluate(p(0.5), p(0.3)), on));
    assert!(!sphere.contains_point(Point3::from_mm(0.0, 0.0, 5.0), on));
    assert!(!sphere.contains_point(Point3::from_mm(6.0, 0.0, 0.0), on));

    let torus = full_torus();
    assert!(torus.contains_point(torus.evaluate(p(0.2), p(0.7)), on));
    assert!(!torus.contains_point(Point3::origin(), on));
    assert!(!torus.contains_point(Point3::from_mm(5.0, 0.0, 0.0), on));
}

#[test]
fn cone_surface_rejects_degenerate() {
    let plane = xy_plane(Point3::origin());
    assert!(ConeSurface::new(plane, mm(4.0), rad(0.3), span(0.0, TAU), mm(0.0), TOL).is_err());
    assert!(
        ConeSurface::new(plane, mm(4.0), rad(FRAC_PI_2), span(0.0, TAU), mm(6.0), TOL).is_err()
    );
    assert!(ConeSurface::new(plane, mm(-1.0), rad(0.3), span(0.0, TAU), mm(6.0), TOL).is_err());
    assert!(ConeSurface::new(plane, mm(1.0), rad(-1.4), span(0.0, TAU), mm(6.0), TOL).is_err());
    assert!(ConeSurface::new(plane, mm(4.0), rad(f64::NAN), span(0.0, TAU), mm(6.0), TOL).is_err());
}

#[test]
fn sphere_surface_rejects_degenerate() {
    let plane = xy_plane(Point3::origin());
    assert!(SphereSurface::new(plane, mm(0.0), span(0.2, 1.0), span(0.0, PI), TOL).is_err());
    assert!(SphereSurface::new(plane, mm(5.0), span(0.2, 0.0), span(0.0, PI), TOL).is_err());
    assert!(SphereSurface::new(plane, mm(5.0), span(0.2, 1.0), span(0.0, 0.0), TOL).is_err());
    assert!(SphereSurface::new(plane, mm(5.0), span(0.2, 4.0), span(0.0, PI), TOL).is_err());
    assert!(SphereSurface::new(plane, mm(f64::NAN), span(0.2, 1.0), span(0.0, PI), TOL).is_err());
}

#[test]
fn torus_surface_rejects_degenerate() {
    let plane = xy_plane(Point3::origin());
    assert!(
        TorusSurface::new(plane, mm(0.0), mm(2.0), span(0.0, TAU), span(0.0, TAU), TOL).is_err()
    );
    assert!(
        TorusSurface::new(plane, mm(5.0), mm(0.0), span(0.0, TAU), span(0.0, TAU), TOL).is_err()
    );
    assert!(
        TorusSurface::new(plane, mm(5.0), mm(2.0), span(0.0, 0.0), span(0.0, TAU), TOL).is_err()
    );
    assert!(
        TorusSurface::new(plane, mm(5.0), mm(2.0), span(0.0, TAU), span(0.0, 0.0), TOL).is_err()
    );
    assert!(
        TorusSurface::new(
            plane,
            mm(5.0),
            mm(f64::NAN),
            span(0.0, TAU),
            span(0.0, TAU),
            TOL
        )
        .is_err()
    );
    assert!(
        TorusSurface::new(plane, mm(2.0), mm(2.0), span(0.0, TAU), span(0.0, TAU), TOL).is_err()
    );
    assert!(
        TorusSurface::new(plane, mm(2.0), mm(5.0), span(0.0, TAU), span(0.0, TAU), TOL).is_err()
    );
}
