use libremetaverse::rendering::{DetailLevel, SimpleMesh};
use libremetaverse::{
    Primitive, PrimitiveConstructionData, PrimitiveSculptData, PrimitiveTextureEntry,
};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_rendering_simple::SimpleRenderer;
use libremetaverse_types::{HoleType, PCode, PathCurve, ProfileCurve, SculptType, UUID};

fn primitive(profile: ProfileCurve, path: PathCurve) -> Primitive {
    let mut primitive = Primitive::new_with_constructor().expect("Primitive constructor");
    let mut data =
        PrimitiveConstructionData::new_with_constructor().expect("ConstructionData constructor");
    data.p_code = PCode::Prim;
    data.path_curve = path;
    data.set_profile_curve_with_property(profile);
    data.path_scale_x = 0.8;
    data.path_scale_y = 0.7;
    data.path_begin = 0.05;
    data.path_end = 0.95;
    data.profile_begin = 0.03;
    data.profile_end = 0.97;
    data.profile_hollow = 0.15;
    data.set_profile_hole(HoleType::Circle);
    data.path_shear_x = 0.1;
    data.path_shear_y = -0.1;
    data.path_revolutions = 1.25;
    data.path_twist = 0.1;
    primitive.prim_data = data;
    primitive.textures = Some(
        PrimitiveTextureEntry::new_with_uuid(UUID::zero()).expect("texture entry constructor"),
    );
    primitive
}

fn assert_valid(mesh: &SimpleMesh) {
    assert!(!mesh.vertices.is_empty());
    assert!(!mesh.indices.is_empty());
    assert_eq!(mesh.indices.len() % 3, 0);
    for index in &mesh.indices {
        assert!(usize::from(*index) < mesh.vertices.len());
    }
    for vertex in &mesh.vertices {
        assert!(vertex.position.x.is_finite());
        assert!(vertex.position.y.is_finite());
        assert!(vertex.position.z.is_finite());
        assert!(vertex.normal.x.is_finite());
        assert!(vertex.normal.y.is_finite());
        assert!(vertex.normal.z.is_finite());
        assert!(vertex.tex_coord.x.is_finite());
        assert!(vertex.tex_coord.y.is_finite());
    }
}

fn sculpt_image() -> ManagedImage {
    let mut image = ManagedImage::new(32, 32, ManagedImageImageChannels::COLOR)
        .expect("sculpt image constructor");
    for (index, red) in image.red.iter_mut().enumerate() {
        *red = u8::try_from(index % 256).expect("bounded sample");
        image.green[index] = u8::try_from((index * 3) % 256).expect("bounded sample");
        image.blue[index] = u8::try_from((index * 7) % 256).expect("bounded sample");
    }
    image
}

#[test]
fn every_profile_path_and_lod_produces_checked_geometry() {
    let renderer = SimpleRenderer::new().expect("renderer");
    for profile in [
        ProfileCurve::Circle,
        ProfileCurve::EqualTriangle,
        ProfileCurve::HalfCircle,
        ProfileCurve::IsoTriangle,
        ProfileCurve::RightTriangle,
        ProfileCurve::Square,
    ] {
        for path in [PathCurve::Flexible, PathCurve::Line, PathCurve::Circle] {
            for lod in [
                DetailLevel::Low,
                DetailLevel::Medium,
                DetailLevel::High,
                DetailLevel::Highest,
            ] {
                let mesh = renderer
                    .generate_simple_mesh(primitive(profile, path), lod)
                    .expect("supported prim topology");
                assert_valid(&mesh);
            }
        }
    }
}

#[test]
fn every_sculpt_topology_produces_checked_geometry_at_each_lod() {
    let renderer = SimpleRenderer::new().expect("renderer");
    for sculpt_type in [
        SculptType::Plane,
        SculptType::Cylinder,
        SculptType::Sphere,
        SculptType::Torus,
    ] {
        for lod in [
            DetailLevel::Low,
            DetailLevel::Medium,
            DetailLevel::High,
            DetailLevel::Highest,
        ] {
            let mut prim = primitive(ProfileCurve::Square, PathCurve::Line);
            let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
            sculpt.set_type_(sculpt_type);
            prim.sculpt = Some(sculpt);
            let mesh = renderer
                .generate_simple_sculpt_mesh(prim, sculpt_image(), lod)
                .expect("supported sculpt topology");
            assert_valid(&mesh);
        }
    }
}
