#![allow(clippy::float_cmp, clippy::too_many_lines)]

use libremetaverse::rendering::{DetailLevel, IRendering, SimpleMesh, Vertex};
use libremetaverse::{
    MappingType, Primitive, PrimitiveConstructionData, PrimitiveSculptData, PrimitiveTextureEntry,
    PrimitiveTextureEntryFace,
};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_prim_mesher::PrimMesh;
use libremetaverse_rendering_mesh_foundry::MeshFoundry;
use libremetaverse_rendering_simple::SimpleRenderer;
use libremetaverse_types::{
    Error, HoleType, PCode, PathCurve, ProfileCurve, SculptType, UUID, Vector2, Vector3,
};

fn uuid(value: &str) -> UUID {
    UUID::new_with_string(value.into()).expect("fixture UUID")
}

fn primitive(profile: ProfileCurve, path: PathCurve, path_scale_y: f32) -> 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 = 1.0;
    data.path_scale_y = path_scale_y;
    data.path_begin = 0.0;
    data.path_end = 1.0;
    data.profile_begin = 0.0;
    data.profile_end = 1.0;
    data.profile_hollow = 0.0;
    data.set_profile_hole(HoleType::Same);
    data.path_revolutions = 1.0;
    primitive.prim_data = data;
    primitive
}

fn box_primitive() -> Primitive {
    primitive(ProfileCurve::Square, PathCurve::Line, 1.0)
}

fn mesh_hash(mesh: &SimpleMesh) -> u64 {
    let mut hash = 0xcbf2_9ce4_8422_2325_u64;
    let mut mix = |value: u32| {
        hash ^= u64::from(value);
        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
    };
    mix(u32::try_from(mesh.vertices.len()).expect("fixture vertex count"));
    mix(u32::try_from(mesh.indices.len()).expect("fixture index count"));
    for vertex in &mesh.vertices {
        for value in [
            vertex.position.x,
            vertex.position.y,
            vertex.position.z,
            vertex.normal.x,
            vertex.normal.y,
            vertex.normal.z,
            vertex.tex_coord.x,
            vertex.tex_coord.y,
        ] {
            mix(value.to_bits());
        }
    }
    for index in &mesh.indices {
        mix(u32::from(*index));
    }
    hash
}

fn assert_valid_mesh(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 {
        for value in [
            vertex.position.x,
            vertex.position.y,
            vertex.position.z,
            vertex.normal.x,
            vertex.normal.y,
            vertex.normal.z,
            vertex.tex_coord.x,
            vertex.tex_coord.y,
        ] {
            assert!(value.is_finite());
        }
    }
}

fn sculpt_image() -> ManagedImage {
    let mut image = ManagedImage::new(8, 8, ManagedImageImageChannels::COLOR)
        .expect("sculpt image constructor");
    for index in 0_u8..64 {
        image.red[usize::from(index)] = index.wrapping_mul(3);
        image.green[usize::from(index)] = index.wrapping_mul(5);
        image.blue[usize::from(index)] = index.wrapping_mul(7);
    }
    image
}

fn vertex(x: f32, y: f32, normal: Vector3, position: Vector3) -> Vertex {
    Vertex {
        position,
        normal,
        tex_coord: Vector2 { x, y },
    }
}

#[test]
fn mesh_and_renderer_calls_compile_with_typed_elements() {
    let _: fn(i32, f32, f32, f32, i32) -> Result<PrimMesh, libremetaverse_prim_mesher::Error> =
        PrimMesh::new;
    let _: fn(
        &SimpleRenderer,
        Primitive,
        DetailLevel,
    ) -> Result<SimpleMesh, libremetaverse_rendering_simple::Error> =
        SimpleRenderer::generate_simple_mesh;
    let _: fn(
        &MeshFoundry,
        Primitive,
        DetailLevel,
    ) -> Result<Option<SimpleMesh>, libremetaverse_rendering_mesh_foundry::Error> =
        MeshFoundry::generate_simple_mesh;
}

#[test]
fn simple_renderer_box_pipeline_is_deterministic_and_matches_golden_mesh() {
    let renderer = SimpleRenderer::new().expect("SimpleRenderer constructor");
    let faceted = renderer
        .generate_faceted_mesh(box_primitive(), DetailLevel::High)
        .expect("faceted box");
    assert_eq!(faceted.faces.len(), 6);
    assert_eq!(
        faceted
            .faces
            .iter()
            .map(|face| face.vertices.len())
            .sum::<usize>(),
        24
    );
    assert_eq!(
        faceted
            .faces
            .iter()
            .map(|face| face.indices.len())
            .sum::<usize>(),
        36
    );
    for face in &faceted.faces {
        assert!(face.min_extent.x <= face.max_extent.x);
        assert!(face.min_extent.y <= face.max_extent.y);
        assert!(face.min_extent.z <= face.max_extent.z);
    }

    let first = renderer
        .generate_simple_mesh(box_primitive(), DetailLevel::High)
        .expect("first simple box");
    let second = renderer
        .generate_simple_mesh(box_primitive(), DetailLevel::High)
        .expect("second simple box");
    assert_valid_mesh(&first);
    assert_eq!(first.vertices.len(), 24);
    assert_eq!(first.indices.len(), 36);
    assert_eq!(mesh_hash(&first), mesh_hash(&second));
    assert_eq!(mesh_hash(&first), 0x3d2b_10e3_7da0_3d2e);
}

#[test]
fn simple_renderer_maps_default_and_per_face_texture_metadata() {
    let default_texture = uuid("11111111-1111-1111-1111-111111111111");
    let override_texture = uuid("22222222-2222-2222-2222-222222222222");
    let material = uuid("33333333-3333-3333-3333-333333333333");
    let render_material = uuid("44444444-4444-4444-4444-444444444444");
    let mut textures =
        PrimitiveTextureEntry::new_with_uuid(default_texture).expect("texture entry constructor");
    let face = textures.create_face(0).expect("face override");
    face.set_texture_id(override_texture);
    face.set_material_id(material);
    face.set_render_material_id(render_material);
    let mut prim = box_primitive();
    prim.textures = Some(textures);

    let mesh = SimpleRenderer::new()
        .expect("renderer")
        .generate_faceted_mesh(prim, DetailLevel::Medium)
        .expect("textured box");
    assert_eq!(mesh.faces[0].texture_face.texture_id(), override_texture);
    assert_eq!(mesh.faces[0].texture_face.material_id(), material);
    assert_eq!(
        mesh.faces[0].texture_face.render_material_id(),
        render_material
    );
    assert_eq!(mesh.faces[1].texture_face.texture_id(), default_texture);
}

#[test]
fn simple_renderer_transforms_default_and_planar_texture_coordinates() {
    let renderer = SimpleRenderer::new().expect("renderer");
    let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
    face.set_repeat_u(2.0);
    face.set_repeat_v(3.0);
    face.set_offset_u(0.1);
    face.set_offset_v(-0.2);
    face.set_rotation(0.0);
    face.set_tex_map_type(MappingType::Default);
    let mut vertices = vec![vertex(0.25, 0.75, Vector3::unit_z(), Vector3::zero())];
    renderer
        .transform_tex_coords(
            &mut vertices,
            Vector3::zero(),
            face,
            Vector3 {
                x: 1.0,
                y: 1.0,
                z: 1.0,
            },
        )
        .expect("default texture transform");
    assert!((vertices[0].tex_coord.x - 0.1).abs() < 1.0e-6);
    assert!((vertices[0].tex_coord.y - 1.05).abs() < 1.0e-6);

    let mut planar = PrimitiveTextureEntryFace::new(None).expect("planar face");
    planar.set_tex_map_type(MappingType::Planar);
    let mut vertices = vec![vertex(
        99.0,
        99.0,
        Vector3::unit_z(),
        Vector3 {
            x: 0.25,
            y: -0.25,
            z: 0.5,
        },
    )];
    renderer
        .transform_tex_coords(
            &mut vertices,
            Vector3::zero(),
            planar,
            Vector3 {
                x: 2.0,
                y: 4.0,
                z: 1.0,
            },
        )
        .expect("planar texture transform");
    assert!((vertices[0].tex_coord.x - 1.5).abs() < 1.0e-6);
    assert!((vertices[0].tex_coord.y - -1.5).abs() < 1.0e-6);
}

#[test]
fn simple_renderer_generates_deterministic_sculpt_geometry() {
    let mut prim = box_primitive();
    let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
    sculpt.set_type_(SculptType::Plane);
    prim.sculpt = Some(sculpt);
    let renderer = SimpleRenderer::new().expect("renderer");
    let first = renderer
        .generate_simple_sculpt_mesh(prim.clone(), sculpt_image(), DetailLevel::Low)
        .expect("first sculpt mesh");
    let second = renderer
        .generate_simple_sculpt_mesh(prim, sculpt_image(), DetailLevel::Low)
        .expect("second sculpt mesh");
    assert_valid_mesh(&first);
    assert_eq!(mesh_hash(&first), mesh_hash(&second));
}

#[test]
fn simple_renderer_trait_dispatch_returns_geometry() {
    let renderer = SimpleRenderer::new().expect("renderer");
    let mesh = IRendering::generate_simple_mesh(&renderer, box_primitive(), DetailLevel::Low)
        .expect("trait call")
        .expect("renderer returned geometry");
    assert_valid_mesh(&mesh);
}

#[test]
fn simple_renderer_reports_source_context_for_invalid_and_mesh_sculpt_inputs() {
    let renderer = SimpleRenderer::new().expect("renderer");
    let source = uuid("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
    let mut invalid = box_primitive();
    invalid.id = source;
    invalid.prim_data.path_end = f32::NAN;
    assert!(matches!(
        renderer.generate_simple_mesh(invalid, DetailLevel::High),
        Err(Error::Rendering {
            source: actual,
            context: "non-finite primitive construction data"
        }) if actual == source
    ));

    let mut mesh_sculpt = box_primitive();
    mesh_sculpt.id = source;
    let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
    sculpt.set_type_(SculptType::Mesh);
    mesh_sculpt.sculpt = Some(sculpt);
    assert!(matches!(
        renderer.generate_simple_sculpt_mesh(
            mesh_sculpt,
            sculpt_image(),
            DetailLevel::High,
        ),
        Err(Error::Rendering {
            source: actual,
            context: "mesh sculpt requires a decoded mesh asset"
        }) if actual == source
    ));
}

#[test]
fn simple_renderer_rejects_non_finite_texture_inputs() {
    let renderer = SimpleRenderer::new().expect("renderer");
    let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
    face.set_repeat_u(f32::INFINITY);
    let mut vertices = vec![vertex(0.0, 0.0, Vector3::unit_z(), Vector3::zero())];
    assert_eq!(
        renderer.transform_tex_coords(
            &mut vertices,
            Vector3::zero(),
            face,
            Vector3 {
                x: 1.0,
                y: 1.0,
                z: 1.0
            },
        ),
        Err(Error::Argument)
    );
}
