//! Golden fixtures for skeleton, attention, and genepool generation.

#![allow(clippy::float_cmp)] // Golden XML values must retain their exact f32 representation.

use libremetaverse::rendering::Joint;
use libremetaverse::{Genepool, LindenAttentions, LookAtType};

fn skeleton_counts(root: &Joint) -> (usize, usize) {
    let mut bones = 1;
    let mut collision_volumes = root.collision_volume().len();
    if let Some(children) = root.bone() {
        for child in children {
            let (child_bones, child_collision_volumes) = skeleton_counts(&child);
            bones += child_bones;
            collision_volumes += child_collision_volumes;
        }
    }
    (bones, collision_volumes)
}

#[test]
fn default_skeleton_matches_the_complete_pinned_hierarchy() {
    let skeleton = libremetaverse::rendering::LindenSkeleton::get_default().unwrap();
    assert_eq!(skeleton.version(), 2.0);
    assert!(!skeleton.version_specified());
    assert_eq!(skeleton.num_bones(), "133");
    assert_eq!(skeleton.num_collision_volumes(), "26");
    assert_eq!(skeleton_counts(&skeleton.bone()), (133, 26));
    assert_eq!(skeleton.get_all_joints().unwrap().count(), 133);

    let pelvis = skeleton.bone();
    assert_eq!(pelvis.base.name(), "mPelvis");
    assert_eq!(pelvis.base.pos(), vec![0.0, 0.0, 1.067]);
    assert_eq!(pelvis.base.rot(), vec![0.0, 0.0, 0.0]);
    assert_eq!(pelvis.base.scale(), vec![1.0, 1.0, 1.0]);
    assert_eq!(pelvis.base.end(), vec![0.0, 0.0, 0.084]);
    assert_eq!(pelvis.pivot(), vec![0.0, 0.0, 1.067_015]);
    assert_eq!(pelvis.base.group(), "Torso");
    assert_eq!(pelvis.base.support(), "base");
    assert!(pelvis.base.reposition());
    assert!(!pelvis.connected());
    assert_eq!(
        pelvis.get_aliases_list().unwrap(),
        ["hip", "avatar_mPelvis"]
    );
    assert_eq!(pelvis.collision_volume().len(), 2);
    assert_eq!(pelvis.collision_volume()[0].base.name(), "PELVIS");

    let by_alias = skeleton.get_bone("hip".to_owned()).unwrap().unwrap();
    assert_eq!(by_alias.base.name(), "mPelvis");
    assert_eq!(
        libremetaverse::rendering::LindenSkeleton::load_with_method().unwrap(),
        skeleton
    );
    assert_eq!(
        libremetaverse::rendering::LindenSkeleton::load_with_string(None).unwrap(),
        skeleton
    );
}

#[test]
fn skeleton_records_preserve_mutable_mapped_properties() {
    let mut base = libremetaverse::rendering::JointBase::new().unwrap();
    base.set_name("joint".to_owned());
    base.set_pos(vec![1.0, 2.0, 3.0]);
    base.set_rot(vec![4.0, 5.0, 6.0]);
    base.set_scale(vec![7.0, 8.0, 9.0]);
    base.set_group("group".to_owned());
    base.set_support("extended".to_owned());
    base.set_end(vec![10.0, 11.0, 12.0]);
    base.set_reposition(true);
    assert_eq!(base.name(), "joint");
    assert_eq!(base.pos(), vec![1.0, 2.0, 3.0]);
    assert_eq!(base.rot(), vec![4.0, 5.0, 6.0]);
    assert_eq!(base.scale(), vec![7.0, 8.0, 9.0]);
    assert_eq!(base.group(), "group");
    assert_eq!(base.support(), "extended");
    assert_eq!(base.end(), vec![10.0, 11.0, 12.0]);
    assert!(base.reposition());

    let mut joint = libremetaverse::rendering::Joint::new().unwrap();
    joint.base = base;
    joint.set_aliases("one two".to_owned());
    joint.set_pivot(vec![13.0, 14.0, 15.0]);
    joint.set_connected(true);
    joint.set_collision_volume(vec![
        libremetaverse::rendering::CollisionVolume::new().unwrap(),
    ]);
    joint.set_bone(None);
    assert_eq!(joint.aliases(), "one two");
    assert_eq!(joint.pivot(), vec![13.0, 14.0, 15.0]);
    assert!(joint.connected());
    assert_eq!(joint.collision_volume().len(), 1);
    assert!(joint.bone().is_none());

    let mut skeleton = libremetaverse::rendering::LindenSkeleton::new().unwrap();
    skeleton.set_bone(joint);
    skeleton.set_version(3.0);
    skeleton.set_version_specified(true);
    skeleton.set_num_bones("1".to_owned());
    skeleton.set_num_collision_volumes("1".to_owned());
    assert_eq!(skeleton.version(), 3.0);
    assert!(skeleton.version_specified());
    assert_eq!(skeleton.num_bones(), "1");
    assert_eq!(skeleton.num_collision_volumes(), "1");
}

#[test]
fn attention_catalogs_match_default_and_updated_fixtures() {
    let default = LindenAttentions::default();
    let updated = LindenAttentions::updated();
    assert_eq!(default.len(), 2);
    assert_eq!(updated.len(), 2);
    assert_eq!(default[0].name(), "Masculine");
    assert_eq!(default[1].name(), "Feminine");
    assert!(
        default
            .iter()
            .chain(&updated)
            .all(|set| set.entries().len() == 11)
    );

    let none = default[0].get(LookAtType::None).unwrap();
    assert_eq!(none.priority(), 0.0);
    assert_eq!(none.timeout(), 0.0);
    let clear = default[0].get(LookAtType::Clear).unwrap();
    assert_eq!(clear.priority(), 0.0);
    assert_eq!(clear.timeout(), 0.0);
    let default_conversation = default[0].get(LookAtType::Conversation).unwrap();
    assert_eq!(default_conversation.priority(), 0.0);
    assert_eq!(default_conversation.timeout(), -1.0);
    let updated_conversation = updated[0].get(LookAtType::Conversation).unwrap();
    assert_eq!(updated_conversation.priority(), 5.0);
    assert_eq!(updated_conversation.timeout(), 3.0);
    assert_eq!(
        LindenAttentions::updated_feminine()
            .get(LookAtType::Focus)
            .unwrap()
            .timeout(),
        10.0
    );
    assert_eq!(LindenAttentions::default_masculine(), default[0]);
    assert_eq!(LindenAttentions::default_feminine(), default[1]);
    assert_eq!(LindenAttentions::updated_masculine(), updated[0]);
    assert_eq!(LindenAttentions::updated_feminine(), updated[1]);

    let mut changed = updated_conversation;
    changed.set_priority(8.0);
    changed.set_timeout(9.0);
    assert_eq!(changed.priority(), 8.0);
    assert_eq!(changed.timeout(), 9.0);
    let mut set = default[0].clone();
    set.set_name("Changed".to_owned());
    set.set_entries(vec![changed.clone()]);
    assert_eq!(set.name(), "Changed");
    assert_eq!(set.entries(), vec![changed]);
    assert_eq!(
        set.get(LookAtType::Clear),
        Err(libremetaverse::Error::IndexOutOfRange)
    );
}

#[test]
fn genepool_matches_every_pinned_archetype_and_supports_lookup() {
    let archetypes = Genepool::archetypes();
    assert_eq!(archetypes.len(), 24);
    assert!(
        archetypes
            .iter()
            .all(|archetype| archetype.params().len() == 140)
    );
    assert_eq!(
        archetypes
            .iter()
            .map(|archetype| archetype.params().len())
            .sum::<usize>(),
        3360
    );
    assert_eq!(archetypes[0].name(), "M W Skinny");
    assert_eq!(Genepool::find_index("M W Skinny".to_owned()).unwrap(), 0);
    assert_eq!(Genepool::find_index("missing".to_owned()).unwrap(), -1);
    assert_eq!(Genepool::get(0).unwrap(), archetypes[0]);
    assert_eq!(
        Genepool::get(-1),
        Err(libremetaverse::Error::IndexOutOfRange)
    );
    assert_eq!(
        Genepool::get(24),
        Err(libremetaverse::Error::IndexOutOfRange)
    );

    let mut first = archetypes[0].params()[0].clone();
    assert_eq!(first.id(), 1);
    assert_eq!(first.name(), "Big_Brow");
    assert_eq!(first.value(), 1.54);
    first.set_id(99);
    first.set_name("Changed".to_owned());
    first.set_value(0.25);
    assert_eq!(first.id(), 99);
    assert_eq!(first.name(), "Changed");
    assert_eq!(first.value(), 0.25);

    let mut archetype = archetypes[0].clone();
    archetype.set_name("Changed archetype".to_owned());
    archetype.set_params(vec![first.clone()]);
    assert_eq!(archetype.name(), "Changed archetype");
    assert_eq!(archetype.params(), vec![first]);
}
