use bone_types::{Plane3, Point3, Tolerance};

use crate::brep::boolean::{as_vec, plane_from_polygon};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FillContinuity {
    Contact,
    Tangent,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PatchGap {
    DegenerateBoundary,
    NonPlanarBoundary,
    TangentNeedsNurbs,
}

pub fn fill_patch(
    boundary: &[Point3],
    continuity: FillContinuity,
    tolerance: Tolerance,
) -> Result<Plane3, PatchGap> {
    if continuity == FillContinuity::Tangent {
        return Err(PatchGap::TangentNeedsNurbs);
    }
    let plane =
        plane_from_polygon(boundary, tolerance).map_err(|_| PatchGap::DegenerateBoundary)?;
    let normal = as_vec(plane.normal());
    let coplanar = boundary
        .iter()
        .all(|point| (*point - plane.origin()).dot_mm2(normal).abs() <= tolerance.value());
    if coplanar {
        Ok(plane)
    } else {
        Err(PatchGap::NonPlanarBoundary)
    }
}

#[cfg(test)]
mod tests {
    use super::{FillContinuity, PatchGap, fill_patch};
    use bone_types::{Point3, Tolerance};

    const TOL: Tolerance = Tolerance::new(1.0e-7);

    fn square() -> [Point3; 4] {
        [
            Point3::from_mm(0.0, 0.0, 2.0),
            Point3::from_mm(4.0, 0.0, 2.0),
            Point3::from_mm(4.0, 3.0, 2.0),
            Point3::from_mm(0.0, 3.0, 2.0),
        ]
    }

    #[test]
    fn coplanar_hole_fills_with_its_plane() {
        let Ok(plane) = fill_patch(&square(), FillContinuity::Contact, TOL) else {
            panic!("a coplanar loop fills with a contact plane");
        };
        let (_, _, oz) = plane.origin().coords_mm();
        assert!((oz - 2.0).abs() < 1.0e-9);
        let (nx, ny, nz) = plane.normal().components();
        assert!(nx.abs() < 1.0e-9 && ny.abs() < 1.0e-9 && (nz.abs() - 1.0).abs() < 1.0e-9);
    }

    #[test]
    fn non_planar_boundary_is_reported() {
        let mut points = square();
        points[2] = Point3::from_mm(4.0, 3.0, 5.0);
        assert_eq!(
            fill_patch(&points, FillContinuity::Contact, TOL),
            Err(PatchGap::NonPlanarBoundary)
        );
    }

    #[test]
    fn tangent_fill_defers_to_nurbs() {
        assert_eq!(
            fill_patch(&square(), FillContinuity::Tangent, TOL),
            Err(PatchGap::TangentNeedsNurbs)
        );
    }

    #[test]
    fn degenerate_boundary_is_reported() {
        let collinear = [
            Point3::from_mm(0.0, 0.0, 0.0),
            Point3::from_mm(1.0, 0.0, 0.0),
            Point3::from_mm(2.0, 0.0, 0.0),
        ];
        assert_eq!(
            fill_patch(&collinear, FillContinuity::Contact, TOL),
            Err(PatchGap::DegenerateBoundary)
        );
    }
}
