// Exact Rust translations of object and texture tests owned by milestone issue #23.
// Source SHA-256: 1a9d986c5e2d7526e95a7bb5b870ebee13ddf9f4e8c3fd69c34b7a7f22375916
// Source SHA-256: b444ada9b4d09944dbdf7668c0c8105de66fb590bee1d6d1f284b30700ee5284
// Source SHA-256: f9a33efa0e88e4a7d86bf7beda56c0f45757ff66f0cdc161da384ad5a31467a6

#![allow(clippy::float_cmp)]

use libremetaverse::{
    Bumpiness, Helpers, MappingType, Primitive, PrimitiveConstructionData, PrimitiveFlexibleData,
    PrimitiveLightData, PrimitiveLightImage, PrimitiveObjectProperties, PrimitiveSculptData,
    PrimitiveTextureEntry, Shininess,
};
use libremetaverse_structured_data::OSD;
use libremetaverse_types::{
    Color4, HoleType, Material, PCode, PathCurve, PrimFlags, ProfileCurve, Quaternion, SculptType,
    UUID, Vector3,
};
use std::collections::HashMap;

fn uuid() -> UUID {
    UUID::random().expect("UUID Random")
}

fn round_trip(entry: &PrimitiveTextureEntry) -> PrimitiveTextureEntry {
    let bytes = entry.get_bytes().expect("TextureEntry GetBytes");
    let length = i32::try_from(bytes.len()).expect("TextureEntry length fits Int32");
    PrimitiveTextureEntry::new_with_bytes_int32_int32(bytes, 0, length)
        .expect("TextureEntry byte constructor")
}

fn face(entry: &PrimitiveTextureEntry, index: usize) -> &libremetaverse::PrimitiveTextureEntryFace {
    entry.face_textures[index].as_ref().expect("per-face entry")
}

fn assert_vector(actual: Vector3, expected: Vector3) {
    assert_eq!(
        (actual.x, actual.y, actual.z),
        (expected.x, expected.y, expected.z)
    );
}

fn assert_quaternion(actual: Quaternion, expected: Quaternion) {
    assert_eq!(
        (actual.x, actual.y, actual.z, actual.w),
        (expected.x, expected.y, expected.z, expected.w)
    );
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.PathBegin::test 5c8e14e1d08a91a4e7c3e252c932b8fe15cf6b5231cc9fe5bb7ef2ae6e2c8299 translated
#[test]
fn path_begin() {
    for value in 0_u16..u16::from(u8::MAX) {
        let unpacked = Primitive::unpack_begin_cut(value).unwrap();
        assert_eq!(
            Primitive::pack_begin_cut(unpacked).unwrap(),
            value,
            "started with {value}, float value was {unpacked}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.PathEnd::test 693bfe29f448e433705d65b8b6907f1b270fd636f914e18b76d7a0047dbea506 translated
#[test]
fn path_end() {
    for value in 0_u16..u16::from(u8::MAX) {
        let unpacked = Primitive::unpack_end_cut(value).unwrap();
        assert_eq!(
            Primitive::pack_end_cut(unpacked).unwrap(),
            value,
            "started with {value}, float value was {unpacked}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.PathRevolutions::test c3f61c80a204e608976cb44a0c10b9651496074c25f254eec8621db65dbbbe37 translated
#[test]
fn path_revolutions() {
    for value in 0_u8..u8::MAX {
        let unpacked = Primitive::unpack_path_revolutions(value).unwrap();
        assert_eq!(
            Primitive::pack_path_revolutions(unpacked).unwrap(),
            value,
            "started with {value}, float value was {unpacked}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.PathScale::test b92e4ab375fcc788dea701d8579268f39b4f3b1d51d4eed91458547350405d61 translated
#[test]
fn path_scale() {
    for value in 0_u8..u8::MAX {
        let unpacked = Primitive::unpack_path_scale(value).unwrap();
        assert_eq!(
            Primitive::pack_path_scale(unpacked).unwrap(),
            value,
            "started with {value}, float value was {unpacked}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.PathTaper::test 93a0adbc0630de3c9fbea6f8080d74ef6e3180e3813f00460bce5d52ef2f12b7 translated
#[test]
fn path_taper() {
    for value in i8::MIN..i8::MAX {
        let unpacked = Primitive::unpack_path_taper(value).unwrap();
        assert_eq!(
            Primitive::pack_path_taper(unpacked).unwrap(),
            value,
            "started with {value}, float value was {unpacked}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntryOffsets::test 720ea1a9477d333b14786bc14e9abca24858e63acae94f4c6055d81f14ff033a translated
#[test]
fn texture_entry_offsets() {
    let mut value = -1.0_f32;
    while value <= 1.0 {
        value = (value * 1000.0).round() / 1000.0;
        let packed = Helpers::te_offset_short(value).unwrap();
        let unpacked = Helpers::te_offset_float(packed.to_le_bytes().to_vec(), 0).unwrap();
        let rounded = (unpacked * 1000.0).round() / 1000.0;
        assert!(
            (rounded - value).abs() < f32::from_bits(1),
            "{rounded} is not equal to {value}"
        );
        value += 0.001;
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntry::test 0670a9a8dcc4001e726df699ef8001604956b6674028a192502b92b725858990 translated
#[test]
fn texture_entry() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    let face = entry.create_face(0).unwrap();
    face.set_bump(Bumpiness::Concrete);
    face.set_fullbright(true);
    face.set_media_flags(true);
    face.set_offset_u(0.5);
    face.set_offset_v(-0.5);
    face.set_repeat_u(3.0);
    face.set_repeat_v(4.0);
    face.set_rgba(Color4 {
        r: 0.0,
        g: 0.25,
        b: 0.75,
        a: 1.0,
    });
    face.set_rotation(1.5);
    face.set_shiny(Shininess::Medium);
    face.set_tex_map_type(MappingType::Planar);
    face.set_texture_id(uuid());
    let first = entry.get_bytes().unwrap();
    let length = i32::try_from(first.len()).expect("TextureEntry length fits Int32");
    let decoded =
        PrimitiveTextureEntry::new_with_bytes_int32_int32(first.clone(), 0, length).unwrap();
    let second = decoded.get_bytes().unwrap();
    assert_eq!(first.len(), second.len());
    for (index, (actual, expected)) in first.iter().zip(&second).enumerate() {
        assert_eq!(actual, expected, "byte {index} is not equal");
    }
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntry_RenderMaterialID_DefaultFace_Roundtrips::test 8db56b448e53e6bec5540a5a4721dc75f85a933c6c89e166f7df7baf8e44aab3 translated
#[test]
fn render_material_id_default_face_round_trips() {
    let material = uuid();
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry
        .default_texture
        .as_mut()
        .unwrap()
        .set_render_material_id(material);
    assert_eq!(
        round_trip(&entry)
            .default_texture
            .as_ref()
            .unwrap()
            .render_material_id(),
        material
    );
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntry_RenderMaterialID_PerFace_Roundtrips::test 2897366ecaa41f07b4db6ebf3e80a7ebe56480f1c44f31cedea2d88a62214dc6 translated
#[test]
fn render_material_id_per_face_round_trips() {
    let material = uuid();
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry
        .create_face(0)
        .unwrap()
        .set_render_material_id(material);
    let decoded = round_trip(&entry);
    assert_eq!(face(&decoded, 0).render_material_id(), material);
    assert_eq!(
        decoded
            .default_texture
            .as_ref()
            .unwrap()
            .render_material_id(),
        UUID::zero()
    );
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntry_RenderMaterialID_MultipleFaces_Roundtrips::test f3d8c103e4b3d43a8e3bea1356ba13808633fdb2ea06583234551f9059b2a241 translated
#[test]
fn render_material_id_multiple_faces_round_trip() {
    let default_material = uuid();
    let first = uuid();
    let second = uuid();
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry
        .default_texture
        .as_mut()
        .unwrap()
        .set_render_material_id(default_material);
    entry.create_face(0).unwrap().set_render_material_id(first);
    entry.create_face(1).unwrap().set_render_material_id(second);
    let decoded = round_trip(&entry);
    assert_eq!(
        decoded
            .default_texture
            .as_ref()
            .unwrap()
            .render_material_id(),
        default_material
    );
    assert_eq!(face(&decoded, 0).render_material_id(), first);
    assert_eq!(face(&decoded, 1).render_material_id(), second);
}

// parity-case: LibreMetaverse.Tests/PrimObjectTests.cs::PrimObjectTests.TextureEntry_RenderMaterialID_InheritsFromDefault_WhenNotOverridden::test 2313a0134836651e23c693505c3c000ad7ff24956b432d0761a5e8b77cb01642 translated
#[test]
fn render_material_id_inherits_from_default_when_not_overridden() {
    let material = uuid();
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry
        .default_texture
        .as_mut()
        .unwrap()
        .set_render_material_id(material);
    let decoded = round_trip(&entry);
    assert_eq!(
        decoded
            .default_texture
            .as_ref()
            .unwrap()
            .render_material_id(),
        material
    );
    if let Some(face) = decoded.get_face(0).unwrap() {
        assert_eq!(face.render_material_id(), material);
    }
}

fn high_face_round_trip(index: u32) -> bool {
    let texture = uuid();
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry.create_face(index).unwrap().set_texture_id(texture);
    let decoded = round_trip(&entry);
    face(&decoded, index as usize).texture_id() == texture
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_HighFace_TextureID_IsPreserved::case:d3ad6116f2458248 5f215cd1c50fa8871967653b4667f848c14ff24a3b71906e51aac13c56de1f55 translated
#[test]
fn high_face_32_texture_id_is_preserved() {
    assert!(high_face_round_trip(32));
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_HighFace_TextureID_IsPreserved::case:c6f3ac57944a5314 5f215cd1c50fa8871967653b4667f848c14ff24a3b71906e51aac13c56de1f55 translated
#[test]
fn high_face_33_texture_id_is_preserved() {
    assert!(high_face_round_trip(33));
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_HighFace_TextureID_IsPreserved::case:d59eced1ded07f84 5f215cd1c50fa8871967653b4667f848c14ff24a3b71906e51aac13c56de1f55 translated
#[test]
fn high_face_40_texture_id_is_preserved() {
    assert!(high_face_round_trip(40));
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_HighFace_TextureID_IsPreserved::case:59df3d13bd106053 5f215cd1c50fa8871967653b4667f848c14ff24a3b71906e51aac13c56de1f55 translated
#[test]
fn high_face_44_texture_id_is_preserved() {
    assert!(high_face_round_trip(44));
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_AllHighFaces_TextureIDs_AllPreserved::test 203ca43b9f1965e1fae7feb36e84eff1df9ac5968eb23753077816d16dd24228 translated
#[test]
fn all_high_face_texture_ids_are_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    let expected: Vec<_> = (32..PrimitiveTextureEntry::MAX_FACES)
        .map(|index| {
            let texture = uuid();
            entry
                .create_face(u32::try_from(index).expect("face index fits UInt32"))
                .unwrap()
                .set_texture_id(texture);
            texture
        })
        .collect();
    let decoded = round_trip(&entry);
    for (offset, texture) in expected.into_iter().enumerate() {
        assert_eq!(face(&decoded, offset + 32).texture_id(), texture);
    }
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_LowAndHighFaces_BothPreserved::test dd520cac107415a186a44eb3a17bd000f933ec49b814a496e6788237fcfffb1e translated
#[test]
fn low_and_high_faces_are_both_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    let expected = [(0, uuid()), (15, uuid()), (32, uuid()), (44, uuid())];
    for (index, texture) in expected {
        entry.create_face(index).unwrap().set_texture_id(texture);
    }
    let decoded = round_trip(&entry);
    for (index, texture) in expected {
        assert_eq!(
            face(&decoded, index as usize).texture_id(),
            texture,
            "face {index}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_Face32_RGBA_IsPreserved::test db41d130d270d31f52f3928df88ccc052102673c1afbca03cc0648526fafbabb translated
#[test]
fn face_32_rgba_is_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry.default_texture.as_mut().unwrap().set_rgba(Color4 {
        r: 1.0,
        g: 1.0,
        b: 1.0,
        a: 1.0,
    });
    entry.create_face(32).unwrap().set_rgba(Color4 {
        r: 1.0,
        g: 0.0,
        b: 0.0,
        a: 1.0,
    });
    let color = face(&round_trip(&entry), 32).rgba();
    assert!((color.r - 1.0).abs() <= 0.01);
    assert!(color.g.abs() <= 0.01);
    assert!(color.b.abs() <= 0.01);
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_Face32_RepeatU_IsPreserved::test 58e80815263025ec54cd15e3c9a42771f5e55c81e9acb212a0f163faef9fd499 translated
#[test]
fn face_32_repeat_u_is_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry.create_face(32).unwrap().set_repeat_u(4.0);
    assert!((face(&round_trip(&entry), 32).repeat_u() - 4.0).abs() <= 0.01);
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_Face32_RepeatV_IsPreserved::test fbeb1fdd584a26576b6f1ba653b2995d1a6d6bccc2c1dc51e46aa9f211b78724 translated
#[test]
fn face_32_repeat_v_is_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    entry.create_face(32).unwrap().set_repeat_v(4.0);
    assert!((face(&round_trip(&entry), 32).repeat_v() - 4.0).abs() <= 0.01);
}

// parity-case: LibreMetaverse.Tests/TextureEntryTests.cs::TextureEntryTests.RoundTrip_AllFaces_TextureIDs_AllPreserved::test 5e5a390af8deff77ef80f751735456541f24d7e27666557d50d99065cc3ba6a4 translated
#[test]
fn all_face_texture_ids_are_preserved() {
    let mut entry = PrimitiveTextureEntry::new_with_uuid(uuid()).unwrap();
    let expected: Vec<_> = (0..PrimitiveTextureEntry::MAX_FACES)
        .map(|index| {
            let texture = uuid();
            entry
                .create_face(u32::try_from(index).expect("face index fits UInt32"))
                .unwrap()
                .set_texture_id(texture);
            texture
        })
        .collect();
    let decoded = round_trip(&entry);
    for (index, texture) in expected.into_iter().enumerate() {
        assert_eq!(face(&decoded, index).texture_id(), texture, "face {index}");
    }
}

// parity-case: LibreMetaverse.Tests/PrimitiveTests.cs::PrimitiveTests.GetOSD_FromOSD_Roundtrip_Minimal::test 5e81b8a195f3e59bfe9e3ebf50c3291f7cbcd49f567ede7c1c8b726e0c6c9c19 translated
#[test]
fn primitive_osd_round_trip_minimal() {
    let mut primitive = Primitive::new_with_constructor().unwrap();
    primitive.id = uuid();
    primitive.local_id = 12_345;
    primitive.parent_id = 54_321;
    primitive.flags =
        PrimFlags(PrimFlags::PHANTOM.0 | PrimFlags::PHYSICS.0 | PrimFlags::CAST_SHADOWS.0);
    primitive.position = Vector3 {
        x: 1.1,
        y: 2.2,
        z: 3.3,
    };
    primitive.rotation = Quaternion {
        x: 0.1,
        y: 0.2,
        z: 0.3,
        w: 0.9,
    };
    primitive.scale = Vector3 {
        x: 0.5,
        y: 0.6,
        z: 0.7,
    };
    let mut data = PrimitiveConstructionData::new_with_constructor().unwrap();
    data.material = Material::Wood;
    data.p_code = PCode::Prim;
    data.state = 7;
    data.path_curve = PathCurve::Circle;
    data.path_scale_x = 1.2;
    data.path_scale_y = 0.8;
    data.profile_begin = 0.0;
    data.profile_end = 1.0;
    data.profile_hollow = 0.1;
    data.set_profile_curve_with_property(ProfileCurve::Square);
    data.set_profile_hole(HoleType(1));
    primitive.prim_data = data;
    let mut properties = PrimitiveObjectProperties::new().unwrap();
    properties.name = "TestPrim".into();
    properties.description = "A primitive used in unit tests".into();
    primitive.properties = Some(properties);
    let decoded = Primitive::from_osd(primitive.get_osd().unwrap()).unwrap();
    assert_eq!(decoded.id, primitive.id);
    assert_eq!(decoded.local_id, primitive.local_id);
    assert_eq!(decoded.parent_id, primitive.parent_id);
    assert_ne!(decoded.flags.0 & PrimFlags::PHANTOM.0, 0);
    assert_ne!(decoded.flags.0 & PrimFlags::PHYSICS.0, 0);
    assert_ne!(decoded.flags.0 & PrimFlags::CAST_SHADOWS.0, 0);
    assert_vector(decoded.position, primitive.position);
    assert_quaternion(decoded.rotation, primitive.rotation);
    assert_vector(decoded.scale, primitive.scale);
    assert_eq!(decoded.prim_data.material, primitive.prim_data.material);
    assert_eq!(decoded.prim_data.p_code, primitive.prim_data.p_code);
    assert_eq!(decoded.prim_data.state, primitive.prim_data.state);
    assert_eq!(decoded.prim_data.path_curve, primitive.prim_data.path_curve);
    assert_eq!(
        decoded.prim_data.path_scale_x,
        primitive.prim_data.path_scale_x
    );
    assert_eq!(
        decoded.prim_data.path_scale_y,
        primitive.prim_data.path_scale_y
    );
    assert_eq!(
        decoded.prim_data.profile_begin,
        primitive.prim_data.profile_begin
    );
    assert_eq!(
        decoded.prim_data.profile_end,
        primitive.prim_data.profile_end
    );
    assert_eq!(
        decoded.prim_data.profile_hollow,
        primitive.prim_data.profile_hollow
    );
    assert_eq!(
        decoded.prim_data.profile_curve_with_property(),
        primitive.prim_data.profile_curve_with_property()
    );
    assert_eq!(
        decoded.prim_data.profile_hole(),
        primitive.prim_data.profile_hole()
    );
    let decoded_properties = decoded.properties.as_ref().expect("properties");
    let original_properties = primitive.properties.as_ref().unwrap();
    assert_eq!(decoded_properties.name, original_properties.name);
    assert_eq!(
        decoded_properties.description,
        original_properties.description
    );
}

// parity-case: LibreMetaverse.Tests/PrimitiveTests.cs::PrimitiveTests.GetOSD_FromOSD_Roundtrip_Extras::test 63bae8bddacab170aec00c6d7a219f96fa794f489190b34cb426bbf9a3672af5 translated
#[test]
fn primitive_osd_round_trip_extras() {
    let mut primitive = Primitive::new_with_constructor().unwrap();
    primitive.id = uuid();
    let mut flexible = PrimitiveFlexibleData::new_with_constructor().unwrap();
    flexible.softness = 3;
    flexible.tension = 1.5;
    flexible.drag = 0.7;
    flexible.gravity = -9.8;
    flexible.wind = 0.2;
    flexible.force = Vector3 {
        x: 0.1,
        y: 0.2,
        z: 0.3,
    };
    primitive.flexible = Some(flexible);
    let mut light = PrimitiveLightData::new_with_constructor().unwrap();
    light.color = Color4 {
        r: 0.2,
        g: 0.4,
        b: 0.6,
        a: 1.0,
    };
    light.intensity = 0.8;
    light.radius = 10.0;
    light.cutoff = 0.5;
    light.falloff = 2.0;
    primitive.light = Some(light);
    let mut image = PrimitiveLightImage::new_with_constructor().unwrap();
    image.light_texture = uuid();
    image.params = Vector3 {
        x: 1.0,
        y: 2.0,
        z: 3.0,
    };
    primitive.light_map = Some(image);
    let mut sculpt = PrimitiveSculptData::new_with_constructor().unwrap();
    sculpt.sculpt_texture = uuid();
    sculpt.set_type_(SculptType::Mesh);
    primitive.sculpt = Some(sculpt);
    let decoded = Primitive::from_osd(primitive.get_osd().unwrap()).unwrap();
    let (actual, expected) = (
        decoded.flexible.as_ref().unwrap(),
        primitive.flexible.as_ref().unwrap(),
    );
    assert_eq!(
        (
            actual.softness,
            actual.tension,
            actual.drag,
            actual.gravity,
            actual.wind
        ),
        (
            expected.softness,
            expected.tension,
            expected.drag,
            expected.gravity,
            expected.wind
        )
    );
    assert_vector(actual.force, expected.force);
    let (actual, expected) = (
        decoded.light.as_ref().unwrap(),
        primitive.light.as_ref().unwrap(),
    );
    assert_eq!(
        (
            actual.color.r,
            actual.color.g,
            actual.color.b,
            actual.intensity,
            actual.radius,
            actual.cutoff,
            actual.falloff
        ),
        (
            expected.color.r,
            expected.color.g,
            expected.color.b,
            expected.intensity,
            expected.radius,
            expected.cutoff,
            expected.falloff
        )
    );
    let (actual, expected) = (
        decoded.light_map.as_ref().unwrap(),
        primitive.light_map.as_ref().unwrap(),
    );
    assert_eq!(actual.light_texture, expected.light_texture);
    assert_vector(actual.params, expected.params);
    let (actual, expected) = (
        decoded.sculpt.as_ref().unwrap(),
        primitive.sculpt.as_ref().unwrap(),
    );
    assert_eq!(actual.sculpt_texture, expected.sculpt_texture);
    assert_eq!(actual.type_(), expected.type_());
}

// parity-case: LibreMetaverse.Tests/PrimitiveTests.cs::PrimitiveTests.FromOSD_MissingVolume_DoesNotThrow_AndUsesDefaults::test 03f9369aab6f6a71ee13982a7b6ba9780f86bcb5b6f081f9550f491b692f711c translated
#[test]
fn primitive_from_osd_missing_volume_uses_defaults() {
    let id = uuid();
    let primitive = Primitive::from_osd(OSD::Map(HashMap::from([
        ("id".into(), OSD::UUID(id)),
        ("localid".into(), OSD::Integer(99)),
        ("parentid".into(), OSD::Integer(100)),
    ])))
    .unwrap();
    assert_eq!(primitive.id, id);
    assert_eq!(primitive.local_id, 99);
    assert_eq!(primitive.parent_id, 100);
    assert_eq!(primitive.prim_data.path_curve, PathCurve::Line);
    assert_eq!(
        primitive.prim_data.profile_curve_with_property(),
        ProfileCurve::Circle
    );
    assert_eq!(primitive.prim_data.profile_end, 1.0);
}

// parity-case: LibreMetaverse.Tests/PrimitiveTests.cs::PrimitiveTests.FromOSD_WrongTypes_ForTransforms_AreIgnored::test a23d4b1ab6d24c11c96862bc18ba57fccf1a8a1d690c90c5e8da3092973220f7 translated
#[test]
fn primitive_from_osd_wrong_transform_types_are_ignored() {
    let primitive = Primitive::from_osd(OSD::Map(HashMap::from([
        ("position".into(), OSD::String("not-a-vector".into())),
        ("rotation".into(), OSD::Integer(123)),
        ("scale".into(), OSD::String("also-not-a-vector".into())),
    ])))
    .unwrap();
    assert_vector(
        primitive.position,
        Vector3 {
            x: 0.0,
            y: 0.0,
            z: 0.0,
        },
    );
    assert_quaternion(
        primitive.rotation,
        Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
    );
    assert_vector(
        primitive.scale,
        Vector3 {
            x: 1.0,
            y: 1.0,
            z: 1.0,
        },
    );
}

// parity-case: LibreMetaverse.Tests/PrimitiveTests.cs::PrimitiveTests.FromOSD_PartialProfile_UsesDefaultsForMissingFields::test f3a3e506e3ef417f6323875db82179a4ffbdc1d4ee383417f2b0bd5afd0d0dba translated
#[test]
fn primitive_from_osd_partial_profile_uses_defaults() {
    let profile = OSD::Map(HashMap::from([(
        "curve".into(),
        OSD::Integer(ProfileCurve::EqualTriangle as i32),
    )]));
    let volume = OSD::Map(HashMap::from([("profile".into(), profile)]));
    let primitive =
        Primitive::from_osd(OSD::Map(HashMap::from([("volume".into(), volume)]))).unwrap();
    assert_eq!(
        primitive.prim_data.profile_curve_with_property(),
        ProfileCurve::EqualTriangle
    );
    assert_eq!(primitive.prim_data.profile_begin, 0.0);
    assert_eq!(primitive.prim_data.profile_end, 1.0);
    assert_eq!(primitive.prim_data.profile_hollow, 0.0);
}
