// Exact Rust translations of the non-folder RLV exception tests.

use libremetaverse_compat_tests::block_on;
use libremetaverse_compat_tests::rlv_support::{RlvHarness, SENDER_ID, guid};
use libremetaverse_rlv::{
    RlvPermissionsServiceObjectLocation as ObjectLocation,
    RlvPermissionsServiceTouchLocation as TouchLocation,
};

const USER_1: &str = "00000000-0000-4000-8000-000000000000";
const USER_2: &str = "11111111-1111-4111-8111-111111111111";

fn harness() -> RlvHarness {
    RlvHarness::new().expect("RlvService constructor")
}

fn process(h: &RlvHarness, commands: &[String]) {
    for command in commands {
        block_on(h.process(command)).unwrap();
    }
}

#[allow(clippy::option_option)]
fn touch_matches(
    h: &RlvHarness,
    location: TouchLocation,
    object: libremetaverse_types::compat::Guid,
    user: Option<Option<libremetaverse_types::compat::Guid>>,
    distance: Option<Option<f32>>,
    expected: bool,
) -> bool {
    h.service
        .permissions()
        .can_touch(location, object, user, distance)
        .unwrap()
        == expected
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/AcceptPermissionExceptionTests.cs::AcceptPermissionExceptionTests.AcceptPermission::test a5ffc51718e89fb872c6df8b8667f6d231618d47a2ffc71d596e8e10e15cb02e translated
#[test]
fn accept_permission_add_and_remove() {
    let h = harness();
    assert!(block_on(h.process("@acceptpermission=add")).unwrap());
    assert!(
        h.service
            .permissions()
            .is_auto_accept_permissions()
            .unwrap()
    );
    assert!(block_on(h.process("@acceptpermission=rem")).unwrap());
    assert!(
        !h.service
            .permissions()
            .is_auto_accept_permissions()
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/AcceptTpExceptionTests.cs::AcceptTpExceptionTests.CanAutoAcceptTp_User::test e988496e81a717eae1a96c5501e748a441fa64f637aaa47a6ae7a8d571c30f6e translated
#[test]
fn accept_tp_specific_user() {
    let h = harness();
    block_on(h.process(&format!("@accepttp:{USER_1}=add"))).unwrap();
    let permissions = h.service.permissions();
    assert!(
        permissions
            .is_auto_accept_tp(Some(Some(guid(USER_1))))
            .unwrap()
    );
    assert!(
        !permissions
            .is_auto_accept_tp(Some(Some(guid(USER_2))))
            .unwrap()
    );
    assert!(!permissions.is_auto_accept_tp(None).unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/AcceptTpExceptionTests.cs::AcceptTpExceptionTests.CanAutoAcceptTp_All::test 428ec184c9d09f6ec57ac3429180f327ed1b79e9d9dea9815df3bb6f0f6244fd translated
#[test]
fn accept_tp_all_users() {
    let h = harness();
    block_on(h.process("@accepttp=add")).unwrap();
    let permissions = h.service.permissions();
    assert!(
        permissions
            .is_auto_accept_tp(Some(Some(guid(USER_1))))
            .unwrap()
    );
    assert!(
        permissions
            .is_auto_accept_tp(Some(Some(guid(USER_2))))
            .unwrap()
    );
    assert!(permissions.is_auto_accept_tp(None).unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/AcceptTpRequestExceptionTests.cs::AcceptTpRequestExceptionTests.CanAutoAcceptTpRequest_User::test 3a5af1a55e74b5e75a04abc11826ded17c327ac2ed99ab2a423674fbb0e61f31 translated
#[test]
fn accept_tp_request_specific_user() {
    let h = harness();
    block_on(h.process(&format!("@accepttprequest:{USER_1}=add"))).unwrap();
    let permissions = h.service.permissions();
    assert!(
        permissions
            .is_auto_accept_tp_request(Some(Some(guid(USER_1))))
            .unwrap()
    );
    assert!(
        !permissions
            .is_auto_accept_tp_request(Some(Some(guid(USER_2))))
            .unwrap()
    );
    assert!(!permissions.is_auto_accept_tp_request(None).unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/AcceptTpRequestExceptionTests.cs::AcceptTpRequestExceptionTests.CanAutoAcceptTpRequest_All::test 8e2cf7c4d17dcec4d7855fc430092699416108966fb5d97c170c13f0bcd04cfe translated
#[test]
fn accept_tp_request_all_users() {
    let h = harness();
    block_on(h.process("@accepttprequest=add")).unwrap();
    let permissions = h.service.permissions();
    assert!(
        permissions
            .is_auto_accept_tp_request(Some(Some(guid(USER_1))))
            .unwrap()
    );
    assert!(
        permissions
            .is_auto_accept_tp_request(Some(Some(guid(USER_2))))
            .unwrap()
    );
    assert!(permissions.is_auto_accept_tp_request(None).unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/DenyPermissionExceptionTests.cs::DenyPermissionExceptionTests.DenyPermission::test c2284b63e7b4a93f8613a3afe34466d14e1c6ed8528656d54ae64ca6d081cdd5 translated
#[test]
fn deny_permission_add_and_remove() {
    let h = harness();
    assert!(block_on(h.process("@denypermission=add")).unwrap());
    assert!(h.service.permissions().is_auto_deny_permissions().unwrap());
    assert!(block_on(h.process("@denypermission=rem")).unwrap());
    assert!(!h.service.permissions().is_auto_deny_permissions().unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/EditExceptionTests.cs::EditExceptionTests.CanEdit_Exception::test e435b03faef04dfe06d0cbec76e94cbf30e61ad7bc08502728f9239cf47292f8 translated
#[test]
fn edit_exception_allows_one_object() {
    let h = harness();
    process(&h, &["@edit=n".into(), format!("@edit:{USER_1}=add")]);
    let permissions = h.service.permissions();
    for location in [
        ObjectLocation::Hud,
        ObjectLocation::Attached,
        ObjectLocation::RezzedInWorld,
    ] {
        assert!(!permissions.can_edit(location, None).unwrap());
        assert!(
            permissions
                .can_edit(location, Some(Some(guid(USER_1))))
                .unwrap()
        );
        assert!(
            !permissions
                .can_edit(location, Some(Some(guid(USER_2))))
                .unwrap()
        );
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/EditExceptionTests.cs::EditExceptionTests.CanEdit_Specific::test db097167497bc650c6729f0687924adedda7097b4089107fc5ea03716c1cb4e5 translated
#[test]
fn edit_object_restricts_only_one_object() {
    let h = harness();
    block_on(h.process(&format!("@editobj:{USER_1}=n"))).unwrap();
    let permissions = h.service.permissions();
    for location in [
        ObjectLocation::Hud,
        ObjectLocation::Attached,
        ObjectLocation::RezzedInWorld,
    ] {
        assert!(permissions.can_edit(location, None).unwrap());
        assert!(
            !permissions
                .can_edit(location, Some(Some(guid(USER_1))))
                .unwrap()
        );
        assert!(
            permissions
                .can_edit(location, Some(Some(guid(USER_2))))
                .unwrap()
        );
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/EmoteExceptionTests.cs::EmoteExceptionTests.CanEmote::test a643401539d7258ae04ddebaf9c82876f59c4433b11ee61bfe8b416058c9fe38 translated
#[test]
fn emote_exception_add_and_remove() {
    let h = harness();
    block_on(h.process("@emote=n")).unwrap();
    assert!(!h.service.permissions().can_emote().unwrap());
    block_on(h.process("@emote=y")).unwrap();
    assert!(h.service.permissions().can_emote().unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RecvChatExceptionTests.cs::RecvChatExceptionTests.CanRecvChat_Except::test b34881785cf2df3d8f4e5ba971adcf2144d8de4db00acfbe136c79457742ec33 translated
#[test]
fn receive_chat_exception_allows_user_chat_and_emote() {
    let h = harness();
    let user = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
    process(&h, &["@recvchat=n".into(), format!("@recvchat:{user}=add")]);
    assert!(
        h.service
            .permissions()
            .can_receive_chat("Hello world".into(), guid(user))
            .unwrap()
    );
    assert!(
        h.service
            .permissions()
            .can_receive_chat("/me says Hello world".into(), guid(user))
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RecvEmoteExceptionTests.cs::RecvEmoteExceptionTests.CanRecvChat_RecvEmote_Except::test d4647d71c02e05405ae2acf7b58522aae559b1c90a4f7a1521c9a9a383220f8a translated
#[test]
fn receive_emote_exception_only_allows_selected_user_emotes() {
    let h = harness();
    process(
        &h,
        &["@recvemote=n".into(), format!("@recvemote:{USER_1}=add")],
    );
    let permissions = h.service.permissions();
    assert!(
        permissions
            .can_receive_chat("Hello world".into(), guid(USER_1))
            .unwrap()
    );
    assert!(
        permissions
            .can_receive_chat("Hello world".into(), guid(USER_2))
            .unwrap()
    );
    assert!(
        permissions
            .can_receive_chat("/me says Hello world".into(), guid(USER_1))
            .unwrap()
    );
    assert!(
        !permissions
            .can_receive_chat("/me says Hello world".into(), guid(USER_2))
            .unwrap()
    );
}

fn im_exception_matches(command: &str, receive: bool, group: Option<&str>) -> bool {
    let h = harness();
    let behavior = if receive { "recvim" } else { "sendim" };
    process(
        &h,
        &[
            format!("@{behavior}=n"),
            format!("@{behavior}:{command}=add"),
        ],
    );
    let user = Some(Some(guid(USER_1)));
    if receive {
        h.service
            .permissions()
            .can_receive_im("Hello world".into(), user, group.map(Into::into))
            .unwrap()
    } else {
        h.service
            .permissions()
            .can_send_im("Hello world".into(), user, group.map(Into::into))
            .unwrap()
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RecvImExceptionTests.cs::RecvImExceptionTests.CanReceiveIM_Exception::test 2e1afba517b29e8e7f2e0605340eb6e87443282857400eba259f0ebdf4874e23 translated
#[test]
fn receive_im_exception_for_user() {
    assert!(im_exception_matches(USER_1, true, None));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RecvImExceptionTests.cs::RecvImExceptionTests.CanReceiveIM_Exception_SingleGroup::test 9ec57daeed225caf960d00f0c83378d1836e86b1896063703ee1c3a38c7b582a translated
#[test]
fn receive_im_exception_for_group() {
    assert!(im_exception_matches("Group Name", true, Some("Group Name")));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RecvImExceptionTests.cs::RecvImExceptionTests.CanReceiveIM_Exception_AllGroups::test 633b8c680b583a6f8d80335c26f4ebcec77f9d8d5231e49bea562dcb66f00267 translated
#[test]
fn receive_im_exception_for_all_groups() {
    assert!(im_exception_matches("allgroups", true, Some("Group name")));
}

fn redirect_channels_match(command: &str, expected: &[i32], emote: bool) -> bool {
    let h = harness();
    for channel in expected {
        block_on(h.process(&format!("@{command}:{channel}=add"))).unwrap();
    }
    let mut channels = Vec::new();
    let found = if emote {
        h.service
            .permissions()
            .try_get_redir_emote_channels(&mut channels)
    } else {
        h.service
            .permissions()
            .try_get_redir_chat_channels(&mut channels)
    };
    found && channels == expected
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat::test 1eb25c5579789db22ee242c4b52c65989bd26dce6d7577021c329c6c49be9414 translated
#[test]
fn redirect_chat_channel() {
    assert!(redirect_channels_match("redirchat", &[1234], false));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat_Removed::test 62e03ff1db1e790d8bc1e38ac1495fc12723ba2eb5c9d6035d73c9fdb91a44ad translated
#[test]
fn redirect_chat_channel_removed() {
    let h = harness();
    process(
        &h,
        &["@redirchat:1234=add".into(), "@redirchat:1234=rem".into()],
    );
    assert!(
        !h.service
            .permissions()
            .try_get_redir_chat_channels(&mut Vec::new())
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat_MultipleChannels::test c7d0c428d121ab80eee9b6b5855cbe50e7a4cd74e81c369da09df04d55c7554f translated
#[test]
fn redirect_chat_multiple_channels() {
    assert!(redirect_channels_match("redirchat", &[1234, 12345], false));
}

fn redirected_message_matches(
    command: &str,
    message: &str,
    channels: &[i32],
    expected: &[(i32, &str)],
) -> bool {
    let h = harness();
    for channel in channels {
        block_on(h.process(&format!("@{command}:{channel}=add"))).unwrap();
    }
    block_on(h.service.report_send_public_message(message.into(), None)).unwrap();
    let expected: Vec<_> = expected
        .iter()
        .map(|(channel, text)| (*channel, (*text).into()))
        .collect();
    h.actions.lock().unwrap().replies == expected
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat_RedirectChat::test c4a083e495a578c1ed0855c43680d5b05144bf627f06df48c82e089e6f0699bf translated
#[test]
fn redirect_chat_sends_public_message() {
    assert!(redirected_message_matches(
        "redirchat",
        "Hello World",
        &[1234],
        &[(1234, "Hello World")],
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat_RedirectChatMultiple::test 5ff8665440d8d02e0aa900121e591034d51e2b48ca62fdec2fbc9135f8c83428 translated
#[test]
fn redirect_chat_sends_to_multiple_channels() {
    assert!(redirected_message_matches(
        "redirchat",
        "Hello World",
        &[1234, 5678],
        &[(1234, "Hello World"), (5678, "Hello World")],
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirChatExceptionTests.cs::RedirChatExceptionTests.IsRedirChat_RedirectChatEmote::test 5d88009e3e8d365c52315a9b189f6cffdc9d04fdb70aa26bcc03d6bc16227189 translated
#[test]
fn redirect_chat_does_not_redirect_emote() {
    assert!(redirected_message_matches(
        "redirchat",
        "/me says Hello World",
        &[1234],
        &[]
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote::test 02170a2b3eb0cbb88921517c9742d2fc0baaea7a621e5a3c3264a7a17d5efb61 translated
#[test]
fn redirect_emote_channel() {
    assert!(redirect_channels_match("rediremote", &[1234], true));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote_Removed::test 6ee89cedfd1e2d79d2447f2f48d1e2d1ac96295485b7509fe09f98e3bfcd9766 translated
#[test]
fn redirect_emote_channel_removed() {
    let h = harness();
    process(
        &h,
        &["@rediremote:1234=add".into(), "@rediremote:1234=rem".into()],
    );
    assert!(
        !h.service
            .permissions()
            .try_get_redir_emote_channels(&mut Vec::new())
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote_MultipleChannels::test 1a95f4cc5db96cf033fed0c1a4e573b1cb1ac17ef72b76e0ce115c051f10528a translated
#[test]
fn redirect_emote_multiple_channels() {
    assert!(redirect_channels_match("rediremote", &[1234, 12345], true));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote_RedirectEmote::test 525297be5b5ff466fc9a61f2bc87af38bd637cbef8e0c794e897e021d0cca464 translated
#[test]
fn redirect_emote_sends_emote() {
    assert!(redirected_message_matches(
        "rediremote",
        "/me says Hello World",
        &[1234],
        &[(1234, "/me says Hello World")],
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote_RedirectEmoteMultiple::test fd6af49a90e111ed6424c85565242e1205e13604b529909f7be0260e93f0b1c6 translated
#[test]
fn redirect_emote_sends_to_multiple_channels() {
    let h = harness();
    process(
        &h,
        &["@rediremote:1234=add".into(), "@rediremote:5678=n".into()],
    );
    block_on(
        h.service
            .report_send_public_message("/me says Hello World".into(), None),
    )
    .unwrap();
    assert_eq!(
        h.actions.lock().unwrap().replies,
        vec![
            (1234, "/me says Hello World".into()),
            (5678, "/me says Hello World".into()),
        ]
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/RedirEmoteExceptionTests.cs::RedirEmoteExceptionTests.IsRedirEmote_RedirectEmoteChat::test e8b78a6a34e3d3acdfc6d454b46a939d341260362ae5327dc4b70164d41431aa translated
#[test]
fn redirect_emote_does_not_redirect_chat() {
    assert!(redirected_message_matches(
        "rediremote",
        "Hello World",
        &[1234],
        &[]
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/SendChannelExceptExceptionTests.cs::SendChannelExceptExceptionTests.CanSendChannelExcept::test 08c309d08681fe067f67315a72014d0a05c87d6e14632fa04f6f2fb617d61be3 translated
#[test]
fn send_channel_exception_blocks_only_selected_channel() {
    let h = harness();
    block_on(h.process("@sendchannel_except:456=add")).unwrap();
    assert!(
        h.service
            .permissions()
            .can_chat(123, "Hello world".into())
            .unwrap()
    );
    assert!(
        !h.service
            .permissions()
            .can_chat(456, "Hello world".into())
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/SendChannelExceptionTests.cs::SendChannelExceptionTests.CanSendChannel_Exception::test 49bcacb6fb31d596ce9afc73099f8aaf645156ee222262174e1d7fed08393cf9 translated
#[test]
fn send_channel_adds_allowed_channel() {
    let h = harness();
    process(&h, &["@sendchannel=n".into(), "@sendchannel:123=n".into()]);
    assert!(
        h.service
            .permissions()
            .can_chat(123, "Hello world".into())
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/SendImExceptionTests.cs::SendImExceptionTests.CanSendIM_Exception::test 8124011921e8d4caf6840e960bd9f98d21df9db476e1726e0d54550cb8527ceb translated
#[test]
fn send_im_exception_for_user() {
    assert!(im_exception_matches(USER_1, false, None));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/SendImExceptionTests.cs::SendImExceptionTests.CanSendIM_Exception_SingleGroup::test b55a4bdba1d33652f3809c3478047afc65757fbe1dec6c5292e17b71a04b206c translated
#[test]
fn send_im_exception_for_group() {
    assert!(im_exception_matches(
        "Group Name",
        false,
        Some("Group Name")
    ));
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/SendImExceptionTests.cs::SendImExceptionTests.CanSendIM_Exception_AllGroups::test 4190ff51848f63dce5682317db79da23fa19ad9e53004534382f3eb6e8544788 translated
#[test]
fn send_im_exception_for_all_groups() {
    assert!(im_exception_matches("allgroups", false, Some("Group name")));
}

fn nullable_user_exception(
    behavior: &str,
    permission: fn(
        &libremetaverse_rlv::RlvPermissionsService,
        Option<Option<libremetaverse_types::compat::Guid>>,
    ) -> Result<bool, libremetaverse_types::Error>,
) -> [bool; 3] {
    let h = harness();
    process(
        &h,
        &[
            format!("@{behavior}=n"),
            format!("@{behavior}:{USER_1}=add"),
        ],
    );
    let permissions = h.service.permissions();
    [
        permission(&permissions, None).unwrap(),
        permission(&permissions, Some(Some(guid(USER_1)))).unwrap(),
        permission(&permissions, Some(Some(guid(USER_2)))).unwrap(),
    ]
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/ShareExceptionTests.cs::ShareExceptionTests.CanShare_Except::test 37297059a814bfde94c466e263a429258cce11ae258afd7953d26f65f9178f1d translated
#[test]
fn share_exception_for_user() {
    assert_eq!(
        nullable_user_exception(
            "share",
            libremetaverse_rlv::RlvPermissionsService::can_share,
        ),
        [false, true, false]
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/ShowNameTagsExceptionTests.cs::ShowNameTagsExceptionTests.CanShowNameTags_Except::test 5d1fe4ec1fa289623d473ffae1682ed725735cbdf48bdf5b7fed086237e32a1a translated
#[test]
fn show_name_tags_exception_for_user() {
    assert_eq!(
        nullable_user_exception(
            "shownametags",
            libremetaverse_rlv::RlvPermissionsService::can_show_name_tags,
        ),
        [false, true, false]
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/ShowNamesExceptionTests.cs::ShowNamesExceptionTests.CanShowNames_Except::test b11578e758631037440dd56a683a3ffc25b576eaa25e7388c950d8d647eb2a75 translated
#[test]
fn show_names_exception_for_user() {
    assert_eq!(
        nullable_user_exception(
            "shownames",
            libremetaverse_rlv::RlvPermissionsService::can_show_names,
        ),
        [false, true, false]
    );
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/StartImExceptionTests.cs::StartImExceptionTests.CanStartIM_Exception::test e0001ffc665f0f39833cf2376ea1fcb7559bdb87a48ea60bb348e14b078880e4 translated
#[test]
fn start_im_exception_for_user() {
    let h = harness();
    process(&h, &["@startim=n".into(), format!("@startim:{USER_1}=add")]);
    assert!(h.service.permissions().can_start_im(guid(USER_1)).unwrap());
    assert!(!h.service.permissions().can_start_im(guid(USER_2)).unwrap());
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/TouchMeExceptionTests.cs::TouchMeExceptionTests.TouchMe_default::test a0f3ffd3c70263738c074e258d9271e0a825f163c40d9c7ee8d20865873eb304 translated
#[test]
fn touch_me_exception_allows_sender_and_hud() {
    let h = harness();
    process(&h, &["@touchall=n".into(), "@touchme=add".into()]);
    let object = guid(USER_1);
    let sender = guid(SENDER_ID);
    let user = Some(Some(guid("55555555-5555-4555-8555-555555555555")));
    assert!(touch_matches(
        &h,
        TouchLocation::AttachedSelf,
        object,
        None,
        None,
        false
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::AttachedOther,
        object,
        user,
        None,
        false
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::RezzedInWorld,
        object,
        None,
        Some(Some(5.0)),
        false,
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::Hud,
        object,
        None,
        None,
        true
    ));
    for (location, callback_user, distance) in [
        (TouchLocation::AttachedSelf, None, None),
        (TouchLocation::AttachedOther, user, None),
        (TouchLocation::RezzedInWorld, None, Some(Some(5.0))),
        (TouchLocation::Hud, None, None),
    ] {
        assert!(touch_matches(
            &h,
            location,
            sender,
            callback_user,
            distance,
            true
        ));
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/TouchThisExceptionTests.cs::TouchThisExceptionTests.TouchThis_default::test e7e6c9e14badb0159f81adc96e17fcf3ebe2cf5351ec6465f03c430de06d85d2 translated
#[test]
fn touch_this_exception_blocks_only_selected_prim() {
    let h = harness();
    block_on(h.process(&format!("@touchthis:{USER_1}=add"))).unwrap();
    let user = Some(Some(guid("55555555-5555-4555-8555-555555555555")));
    for (location, callback_user, distance) in [
        (TouchLocation::AttachedSelf, None, None),
        (TouchLocation::AttachedOther, user, None),
        (TouchLocation::RezzedInWorld, None, Some(Some(5.0))),
        (TouchLocation::Hud, None, None),
    ] {
        assert!(touch_matches(
            &h,
            location,
            guid(USER_1),
            callback_user,
            distance,
            false
        ));
        assert!(touch_matches(
            &h,
            location,
            guid(USER_2),
            callback_user,
            distance,
            true
        ));
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/TouchWorldExceptionTests.cs::TouchWorldExceptionTests.TouchWorld_Exception::test ffef2f112b5718bab7b01fc1b624e588c181dadc5c50fd85de4404a501ee6f37 translated
#[test]
fn touch_world_exception_allows_selected_world_prim() {
    let h = harness();
    process(
        &h,
        &["@touchworld=n".into(), format!("@touchworld:{USER_2}=add")],
    );
    let user = Some(Some(guid("55555555-5555-4555-8555-555555555555")));
    assert!(touch_matches(
        &h,
        TouchLocation::AttachedSelf,
        guid(USER_1),
        None,
        None,
        true,
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::AttachedOther,
        guid(USER_1),
        user,
        None,
        true,
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::RezzedInWorld,
        guid(USER_1),
        None,
        Some(Some(5.0)),
        false,
    ));
    assert!(touch_matches(
        &h,
        TouchLocation::Hud,
        guid(USER_1),
        None,
        None,
        true
    ));
    for (location, callback_user, distance) in [
        (TouchLocation::AttachedSelf, None, None),
        (TouchLocation::AttachedOther, user, None),
        (TouchLocation::RezzedInWorld, None, Some(Some(5.0))),
        (TouchLocation::Hud, None, None),
    ] {
        assert!(touch_matches(
            &h,
            location,
            guid(USER_2),
            callback_user,
            distance,
            true
        ));
    }
}

// parity-case: LibreMetaverse.Tests/RLV/Exceptions/TpLureExceptionTests.cs::TpLureExceptionTests.CanTpLure_Except::test a32f4cdda5b48ffcaced683b2e77fc129c1c321951aa2e16623eea022f2aa0b9 translated
#[test]
fn tp_lure_exception_for_user() {
    assert_eq!(
        nullable_user_exception(
            "tplure",
            libremetaverse_rlv::RlvPermissionsService::can_tp_lure,
        ),
        [false, true, false]
    );
}
