use bone_types::{
    Aabb3, AngleTolerance, ChordHeightTolerance, Length, Parameter, Plane3, Point3, Tolerance,
    UnitVec3, Vec3,
};
use uom::si::length::millimeter;

use crate::KernelError;
use crate::circular3;
use crate::mesh::{MeshVertex, TriMesh};
use crate::surface3::Surface3;

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PlaneSurface {
    plane: Plane3,
    u_extent: Length,
    v_extent: Length,
}

impl PlaneSurface {
    pub fn new(
        plane: Plane3,
        u_extent: Length,
        v_extent: Length,
        tolerance: Tolerance,
    ) -> Result<Self, KernelError> {
        let u = u_extent.get::<millimeter>();
        let v = v_extent.get::<millimeter>();
        if !u.is_finite() || !v.is_finite() || u < tolerance.value() || v < tolerance.value() {
            return Err(KernelError::DegeneratePlane);
        }
        Ok(Self {
            plane,
            u_extent,
            v_extent,
        })
    }

    #[must_use]
    pub(crate) const fn new_unchecked(plane: Plane3, u_extent: Length, v_extent: Length) -> Self {
        Self {
            plane,
            u_extent,
            v_extent,
        }
    }

    #[must_use]
    pub const fn plane(self) -> Plane3 {
        self.plane
    }

    #[must_use]
    pub const fn u_extent(self) -> Length {
        self.u_extent
    }

    #[must_use]
    pub const fn v_extent(self) -> Length {
        self.v_extent
    }

    #[must_use]
    fn point_at(self, u: f64, v: f64) -> Point3 {
        let (ox, oy, oz) = self.plane.origin().coords_mm();
        let (xx, xy, xz) = self.plane.x_axis().components();
        let (yx, yy, yz) = self.plane.y_axis().components();
        let su = u * self.u_extent.get::<millimeter>();
        let sv = v * self.v_extent.get::<millimeter>();
        Point3::from_mm(
            ox + su * xx + sv * yx,
            oy + su * xy + sv * yy,
            oz + su * xz + sv * yz,
        )
    }
}

impl Surface3 for PlaneSurface {
    fn evaluate(&self, u: Parameter, v: Parameter) -> Point3 {
        self.point_at(u.value(), v.value())
    }

    fn partials(&self, _u: Parameter, _v: Parameter) -> (Vec3, Vec3) {
        (
            self.plane.x_axis().into_vec(self.u_extent),
            self.plane.y_axis().into_vec(self.v_extent),
        )
    }

    fn normal(&self, _u: Parameter, _v: Parameter) -> UnitVec3 {
        self.plane.normal()
    }

    fn bounding_box(&self) -> Aabb3 {
        Aabb3::from_corners(self.point_at(0.0, 0.0), self.point_at(1.0, 1.0)).union(
            Aabb3::from_corners(self.point_at(1.0, 0.0), self.point_at(0.0, 1.0)),
        )
    }

    fn contains_point(&self, p: Point3, tolerance: Tolerance) -> bool {
        let (a, b, off_plane) = circular3::local_coords(self.plane, p);
        if off_plane.abs() > tolerance.value() {
            return false;
        }
        let tol = tolerance.value();
        (-tol..=self.u_extent.get::<millimeter>() + tol).contains(&a)
            && (-tol..=self.v_extent.get::<millimeter>() + tol).contains(&b)
    }

    fn tessellate(&self, _chord: ChordHeightTolerance, _angle: AngleTolerance) -> TriMesh {
        let normal = self.plane.normal();
        TriMesh::from_grid(2, 2, |i, j| {
            MeshVertex::new(self.point_at(f64::from(i), f64::from(j)), normal)
        })
    }
}

impl core::fmt::Display for PlaneSurface {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "plane_surface{{ o={}, normal={}, u={} mm, v={} mm }}",
            self.plane.origin(),
            self.plane.normal(),
            self.u_extent.get::<millimeter>(),
            self.v_extent.get::<millimeter>(),
        )
    }
}
