//! Integration contracts for the data consumed by the future rendering pipeline.

use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_prim_mesher::{
    PathType, PrimMesh, SculptMesh, SculptMeshSculptType, ViewerPolygon, ViewerVertex,
};

fn assert_rendering_vertices(vertices: &[ViewerVertex], polygons: &[ViewerPolygon]) {
    assert!(vertices.len() <= usize::from(u16::MAX) + 1);
    for vertex in vertices {
        assert!(vertex.v.x.is_finite() && vertex.v.y.is_finite() && vertex.v.z.is_finite());
        assert!(vertex.n.x.is_finite() && vertex.n.y.is_finite() && vertex.n.z.is_finite());
        assert!(vertex.uv.u.is_finite() && vertex.uv.v.is_finite());
    }
    for polygon in polygons {
        for index in [polygon.v1, polygon.v2, polygon.v3] {
            let index = usize::try_from(index).expect("non-negative viewer index");
            assert!(index < vertices.len());
            u16::try_from(index).expect("MeshFoundry-facing index fits u16");
        }
    }
}

#[test]
fn prim_mesh_viewer_groups_are_rendering_ready() {
    let mut mesh = PrimMesh::new(24, 0.05, 0.95, 0.25, 12).expect("checked profile");
    mesh.viewer_mode = true;
    mesh.calc_vertex_normals = true;
    mesh.path_cut_begin = 0.03;
    mesh.path_cut_end = 0.96;
    mesh.twist_begin = -30;
    mesh.twist_end = 120;
    mesh.taper_x = 0.1;
    mesh.skew = 0.15;
    mesh.revolutions = 1.25;
    mesh.steps_per_revolution = 48;
    mesh.extrude(PathType::Circular)
        .expect("checked viewer extrusion");

    let indexer = mesh
        .get_vertex_indexer()
        .expect("checked viewer indexing")
        .expect("viewer mode produces an indexer");
    assert_eq!(indexer.num_prim_faces, mesh.num_prim_faces);
    assert_eq!(indexer.viewer_vertices.len(), indexer.viewer_polygons.len());
    for (vertices, polygons) in indexer
        .viewer_vertices
        .iter()
        .zip(indexer.viewer_polygons.iter())
    {
        assert_rendering_vertices(
            vertices,
            polygons
                .as_deref()
                .expect("allocated prim-face polygon group"),
        );
    }
}

#[test]
fn sculpt_lods_and_topologies_keep_rendering_indices_and_attributes_aligned() {
    let mut image = ManagedImage::new(128, 128, ManagedImageImageChannels::COLOR)
        .expect("bounded sculpt fixture");
    for index in 0..image.red.len() {
        image.red[index] = u8::try_from(index & 0xff).expect("masked sample");
        image.green[index] = u8::try_from((index / 128) & 0xff).expect("masked sample");
        image.blue[index] = u8::try_from((index * 17) & 0xff).expect("masked sample");
    }

    for lod in [8, 16, 32] {
        for topology in [
            SculptMeshSculptType::Plane,
            SculptMeshSculptType::Sphere,
            SculptMeshSculptType::Torus,
            SculptMeshSculptType::Cylinder,
        ] {
            let mesh =
                SculptMesh::new_with_managed_image_sculpt_type_int32_boolean_boolean_boolean(
                    image.clone().expect("independent image fixture"),
                    topology,
                    lod,
                    true,
                    true,
                    false,
                )
                .expect("checked sculpt mesh");
            assert_eq!(mesh.coords.len(), mesh.normals.len());
            assert_eq!(mesh.coords.len(), mesh.uvs.len());
            assert_eq!(mesh.faces.len(), mesh.viewer_faces.len());
            assert!(mesh.coords.len() <= usize::from(u16::MAX) + 1);
            for face in &mesh.faces {
                for index in [face.v1, face.v2, face.v3] {
                    let index = usize::try_from(index).expect("non-negative sculpt index");
                    assert!(index < mesh.coords.len());
                    u16::try_from(index).expect("MeshFoundry-facing sculpt index fits u16");
                }
            }
            for viewer in &mesh.viewer_faces {
                for coord in [viewer.v1, viewer.v2, viewer.v3] {
                    assert!(coord.x.is_finite() && coord.y.is_finite() && coord.z.is_finite());
                }
                for normal in [viewer.n1, viewer.n2, viewer.n3] {
                    assert!(normal.x.is_finite() && normal.y.is_finite() && normal.z.is_finite());
                }
                for uv in [viewer.uv1, viewer.uv2, viewer.uv3] {
                    assert!(uv.u.is_finite() && uv.v.is_finite());
                }
            }
        }
    }
}
