use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::utils::sampling::PiecewiseConstant2D;
use crate::{gvec_from_slice, gvec_with_capacity, Float, GVec, Ptr, Transform};

#[repr(C)]
#[derive(Debug, Clone)]
pub struct TriangleMesh {
    pub p: GVec<Point3f>,
    pub n: GVec<Normal3f>,
    pub s: GVec<Vector3f>,
    pub uv: GVec<Point2f>,
    pub vertex_indices: GVec<i32>,
    pub face_indices: GVec<i32>,
    pub n_triangles: u32,
    pub n_vertices: u32,
    pub reverse_orientation: bool,
    pub transform_swaps_handedness: bool,
}

#[repr(C)]
#[derive(Debug, Clone)]
pub struct BilinearPatchMesh {
    pub p: GVec<Point3f>,
    pub n: GVec<Normal3f>,
    pub uv: GVec<Point2f>,
    pub vertex_indices: GVec<i32>,
    pub n_patches: u32,
    pub n_vertices: u32,
    pub reverse_orientation: bool,
    pub transform_swaps_handedness: bool,
    pub image_distribution: Ptr<PiecewiseConstant2D>,
}

unsafe impl Send for TriangleMesh {}
unsafe impl Sync for TriangleMesh {}
unsafe impl Send for BilinearPatchMesh {}
unsafe impl Sync for BilinearPatchMesh {}

impl TriangleMesh {
    pub fn new(
        render_from_object: &Transform,
        reverse_orientation: bool,
        vertex_indices: &[i32],
        p: &[Point3f],
        n: &[Normal3f],
        s: &[Vector3f],
        uv: &[Point2f],
        face_indices: &[i32],
    ) -> Self {
        let n_triangles = (vertex_indices.len() / 3) as u32;
        let n_vertices = p.len() as u32;

        let mut p_gvec = gvec_with_capacity(p.len());
        for pt in p {
            p_gvec.push(render_from_object.apply_to_point(*pt));
        }

        let mut n_gvec = gvec_with_capacity(n.len());
        if !n.is_empty() {
            assert_eq!(n_vertices as usize, n.len(), "Normal count mismatch");
            for nn in n {
                let mut transformed = render_from_object.apply_to_normal(*nn);
                if reverse_orientation {
                    transformed = -transformed;
                }
                n_gvec.push(transformed);
            }
        }

        let mut s_gvec = gvec_with_capacity(s.len());
        if !s.is_empty() {
            assert_eq!(n_vertices as usize, s.len(), "Tangent count mismatch");
            for ss in s {
                s_gvec.push(render_from_object.apply_to_vector(*ss));
            }
        }

        assert!(
            uv.is_empty() || uv.len() == n_vertices as usize,
            "UV count mismatch"
        );
        assert!(
            face_indices.is_empty() || face_indices.len() == n_triangles as usize,
            "Face index count mismatch"
        );

        Self {
            vertex_indices: gvec_from_slice(vertex_indices),
            p: p_gvec,
            n: n_gvec,
            s: s_gvec,
            uv: gvec_from_slice(uv),
            face_indices: gvec_from_slice(face_indices),
            n_triangles,
            n_vertices,
            reverse_orientation,
            transform_swaps_handedness: render_from_object.swaps_handedness(),
        }
    }

    pub fn positions(&self) -> &[Point3f] {
        &self.p
    }
    pub fn indices(&self) -> &[i32] {
        &self.vertex_indices
    }
    pub fn normals(&self) -> &[Normal3f] {
        &self.n
    }
    pub fn uvs(&self) -> &[Point2f] {
        &self.uv
    }
}

impl BilinearPatchMesh {
    pub fn new(
        render_from_object: &Transform,
        reverse_orientation: bool,
        vertex_indices: &[i32],
        p: &[Point3f],
        n: &[Normal3f],
        uv: &[Point2f],
        image_distribution: Option<&PiecewiseConstant2D>,
    ) -> Self {
        let n_patches = (vertex_indices.len() / 4) as u32;
        let n_vertices = p.len() as u32;

        let mut p_gvec = gvec_with_capacity(p.len());
        for pt in p {
            p_gvec.push(render_from_object.apply_to_point(*pt));
        }

        let mut n_gvec = gvec_with_capacity(n.len());
        if !n.is_empty() {
            assert_eq!(n_vertices as usize, n.len());
            for nn in n {
                let mut transformed = render_from_object.apply_to_normal(*nn);
                if reverse_orientation {
                    transformed = -transformed;
                }
                n_gvec.push(transformed);
            }
        }

        assert!(uv.is_empty() || uv.len() == n_vertices as usize);

        Self {
            vertex_indices: gvec_from_slice(vertex_indices),
            p: p_gvec,
            n: n_gvec,
            uv: gvec_from_slice(uv),
            image_distribution: Ptr::from(image_distribution),
            n_patches,
            n_vertices,
            reverse_orientation,
            transform_swaps_handedness: render_from_object.swaps_handedness(),
        }
    }
}
