// Exact Rust translations of the CompositeCurrentOutfitPolicyTests fixture in
// LibreMetaverse.Tests/CurrentOutfitFolderTests.cs.
// Source SHA-256: 07177fff27fbee0d0714286c652253985add3a9401a50e8d7a829ce6c2b3b7a86

use libremetaverse::appearance::{CompositeCurrentOutfitPolicy, ICurrentOutfitPolicy};
use libremetaverse::{InventoryItem, InventoryWearable};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::compat::{CancellationToken, CancellationTokenSource};
use libremetaverse_types::{AssetType, Error, UUID};
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};

struct TestPolicy {
    allow_attach: AtomicBool,
    allow_detach: AtomicBool,
    attach_calls: AtomicUsize,
    detach_calls: AtomicUsize,
    report_calls: AtomicUsize,
    last_added: Mutex<Option<Arc<Vec<InventoryItem>>>>,
    last_removed: Mutex<Option<Arc<Vec<InventoryItem>>>>,
}

impl TestPolicy {
    fn new() -> Self {
        Self {
            allow_attach: AtomicBool::new(true),
            allow_detach: AtomicBool::new(true),
            attach_calls: AtomicUsize::new(0),
            detach_calls: AtomicUsize::new(0),
            report_calls: AtomicUsize::new(0),
            last_added: Mutex::new(None),
            last_removed: Mutex::new(None),
        }
    }
}

impl ICurrentOutfitPolicy for TestPolicy {
    fn can_attach(&self, _item: Option<Arc<InventoryItem>>) -> Result<bool, Error> {
        self.attach_calls.fetch_add(1, Ordering::SeqCst);
        Ok(self.allow_attach.load(Ordering::SeqCst))
    }

    fn can_detach(&self, _item: Option<Arc<InventoryItem>>) -> Result<bool, Error> {
        self.detach_calls.fetch_add(1, Ordering::SeqCst);
        Ok(self.allow_detach.load(Ordering::SeqCst))
    }

    fn report_item_change(
        &self,
        added: Option<Arc<Vec<InventoryItem>>>,
        removed: Option<Arc<Vec<InventoryItem>>>,
        _cancellation_token: Option<CancellationToken>,
    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>> {
        self.report_calls.fetch_add(1, Ordering::SeqCst);
        *self.last_added.lock().unwrap() = added;
        *self.last_removed.lock().unwrap() = removed;
        Box::pin(async { Ok(()) })
    }
}

fn item() -> Arc<InventoryItem> {
    let mut wearable = InventoryWearable::new(UUID::random().unwrap()).unwrap();
    wearable.base.set_asset_type(AssetType::Clothing);
    wearable.base.base.set_name("Test Item".into());
    Arc::new(wearable.base)
}

fn as_policy(policy: &Arc<TestPolicy>) -> Arc<dyn ICurrentOutfitPolicy> {
    policy.clone()
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddPolicy_WithValidPolicy_ReturnsThis::test 31c1829eaa2b82b498cc1eaea131d2e34d1a0b5620be24f4da853c62c49807cc translated
#[test]
fn add_policy_with_valid_policy_returns_this() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let policy = Arc::new(TestPolicy::new());
    let result = composite.add_policy(Some(as_policy(&policy))).unwrap();
    assert!(std::ptr::eq(
        std::ptr::from_ref(result),
        std::ptr::from_ref(&composite)
    ));
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddPolicy_WithNullPolicy_ThrowsArgumentNullException::test 6a111f0ad2e91c8566b5459939e3ecc76282cd033bb8d6483e1672c36891e597 translated
#[test]
fn add_policy_with_null_policy_throws_argument_null_exception() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    assert!(matches!(
        composite.add_policy(None),
        Err(Error::ArgumentNull)
    ));
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddPolicy_MultiplePolicies_AllAreConsulted::test 1767ee3dee6b07e71f10b52613962e211fcf9906a45d12188f517cf751896f24 translated
#[test]
fn add_policy_multiple_policies_all_are_consulted() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_attach.store(true, Ordering::SeqCst);
    second.allow_attach.store(true, Ordering::SeqCst);
    let result = composite.can_attach(Some(item())).unwrap();
    assert_eq!(first.attach_calls.load(Ordering::SeqCst), 1);
    assert_eq!(second.attach_calls.load(Ordering::SeqCst), 1);
    assert!(result);
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddPolicy_SamePolicyTwice_OnlyAddsOnce::test e3d1340780680bfc46267194ddb3c47e4ae24299cf532d92dd71d9770f1955fa translated
#[test]
fn add_policy_same_policy_twice_only_adds_once() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let policy = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&policy))).unwrap();
    composite.add_policy(Some(as_policy(&policy))).unwrap();
    policy.allow_attach.store(true, Ordering::SeqCst);
    composite.can_attach(Some(item())).unwrap();
    assert_eq!(policy.attach_calls.load(Ordering::SeqCst), 1);
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.RemovePolicy_WithAddedPolicy_RemovesPolicy::test 9dd407dceeacdcc60fccb0d9869ad261d90acd74ae4257331e0cf8d4be2ee603 translated
#[test]
fn remove_policy_with_added_policy_removes_policy() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let policy = Arc::new(TestPolicy::new());
    let policy_dyn = as_policy(&policy);
    composite.add_policy(Some(Arc::clone(&policy_dyn))).unwrap();
    composite.remove_policy(Some(policy_dyn)).unwrap();
    policy.allow_attach.store(false, Ordering::SeqCst);
    assert!(composite.can_attach(Some(item())).unwrap());
    assert_eq!(policy.attach_calls.load(Ordering::SeqCst), 0);
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.RemovePolicy_WithNonAddedPolicy_DoesNotThrow::test 9fa18bd628f6f02912168309e8e2afb6346a73a235dcc16cc81bc35e2dcf766f translated
#[test]
fn remove_policy_with_non_added_policy_does_not_throw() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    composite
        .remove_policy(Some(Arc::new(TestPolicy::new())))
        .unwrap();
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.RemovePolicy_WithNull_DoesNotThrow::test 28075d5f7c5458f1ccdf9981970e212e1663a9e5ebee65fce636a11a22390bfb translated
#[test]
fn remove_policy_with_null_does_not_throw() {
    CompositeCurrentOutfitPolicy::new()
        .unwrap()
        .remove_policy(None)
        .unwrap();
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WithNoPolicies_ReturnsTrue::test 74bc06bb6cedfe84a3cd7e5b4c32b251ee6eff81c272c7d899436dbd7f21886b translated
#[test]
fn can_attach_with_no_policies_returns_true() {
    assert!(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .can_attach(Some(item()))
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WithNullItem_ThrowsArgumentNullException::test a47ebf9c89b603c10109c6e643de5043ba13cd2eaf5f04d73ab1fd5f40e9b332 translated
#[test]
fn can_attach_with_null_item_throws_argument_null_exception() {
    assert!(matches!(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .can_attach(None),
        Err(Error::ArgumentNull)
    ));
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WithAllPoliciesAllowing_ReturnsTrue::test 32c2f5dcc5cbdf24e4205c25c833aaa3c0f203aeb8ff20c30fdac1264f12f6bd translated
#[test]
fn can_attach_with_all_policies_allowing_returns_true() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_attach.store(true, Ordering::SeqCst);
    second.allow_attach.store(true, Ordering::SeqCst);
    assert!(composite.can_attach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WithOnePolicyDenying_ReturnsFalse::test f38d910142a563bded6d35b969c0c560ffa072fd8df073a15fbbbed745681d0d translated
#[test]
fn can_attach_with_one_policy_denying_returns_false() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_attach.store(true, Ordering::SeqCst);
    second.allow_attach.store(false, Ordering::SeqCst);
    assert!(!composite.can_attach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WithAllPoliciesDenying_ReturnsFalse::test 6cdda22d59a3cd690a187b597a7c8a2474e43c27902e737f15f0c71adc5bcf7c translated
#[test]
fn can_attach_with_all_policies_denying_returns_false() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_attach.store(false, Ordering::SeqCst);
    second.allow_attach.store(false, Ordering::SeqCst);
    assert!(!composite.can_attach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanDetach_WithNoPolicies_ReturnsTrue::test fe53b90f59fd9a73c4af1b7071f04cae0a7b623d6a50a3d8eb2fcae82e6872e9 translated
#[test]
fn can_detach_with_no_policies_returns_true() {
    assert!(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .can_detach(Some(item()))
            .unwrap()
    );
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanDetach_WithNullItem_ThrowsArgumentNullException::test 8a39f9c6e26f3a136de641f38507db144f731aed0861edbeafcfdb70306ecffb translated
#[test]
fn can_detach_with_null_item_throws_argument_null_exception() {
    assert!(matches!(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .can_detach(None),
        Err(Error::ArgumentNull)
    ));
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanDetach_WithAllPoliciesAllowing_ReturnsTrue::test 2cbe1f0a5907c1dfa01af4329fa64177463798ec6caa357c8da8f5d3291901bf translated
#[test]
fn can_detach_with_all_policies_allowing_returns_true() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_detach.store(true, Ordering::SeqCst);
    second.allow_detach.store(true, Ordering::SeqCst);
    assert!(composite.can_detach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanDetach_WithOnePolicyDenying_ReturnsFalse::test 11896040dd8e4b5bbd7d85a1da126c648ad77eb349a73edf1e18fa5710cde1c9 translated
#[test]
fn can_detach_with_one_policy_denying_returns_false() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_detach.store(true, Ordering::SeqCst);
    second.allow_detach.store(false, Ordering::SeqCst);
    assert!(!composite.can_detach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanDetach_WithAllPoliciesDenying_ReturnsFalse::test 62b8478f593f2bf16b44267d80668e327a245d0b5c0386e7229a5857d669ae6c translated
#[test]
fn can_detach_with_all_policies_denying_returns_false() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    first.allow_detach.store(false, Ordering::SeqCst);
    second.allow_detach.store(false, Ordering::SeqCst);
    assert!(!composite.can_detach(Some(item())).unwrap());
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithNoPolicies_CompletesSuccessfully::test e3f6824512503bea532826b9a9e4f7bb5abdea53f962fbbc6fdae299a6fe44a0 translated
#[test]
fn report_item_change_with_no_policies_completes_successfully() {
    let added = Arc::new(vec![Arc::try_unwrap(item()).ok().unwrap()]);
    let removed = Arc::new(Vec::new());
    block_on(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .report_item_change(Some(added), Some(removed), None),
    )
    .unwrap();
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithMultiplePolicies_CallsAllPolicies::test 4797cc1851d78364553dac59a6f9a894107fa04fb3fddfb52ed0e4fb5b4bcd1a translated
#[test]
fn report_item_change_with_multiple_policies_calls_all_policies() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let first = Arc::new(TestPolicy::new());
    let second = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&first))).unwrap();
    composite.add_policy(Some(as_policy(&second))).unwrap();
    let added = Arc::new(vec![Arc::try_unwrap(item()).ok().unwrap()]);
    let removed = Arc::new(Vec::new());
    block_on(composite.report_item_change(
        Some(Arc::clone(&added)),
        Some(Arc::clone(&removed)),
        None,
    ))
    .unwrap();
    assert_eq!(first.report_calls.load(Ordering::SeqCst), 1);
    assert_eq!(second.report_calls.load(Ordering::SeqCst), 1);
    assert!(Arc::ptr_eq(
        first.last_added.lock().unwrap().as_ref().unwrap(),
        &added
    ));
    assert!(Arc::ptr_eq(
        second.last_added.lock().unwrap().as_ref().unwrap(),
        &added
    ));
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithCancelledToken_ThrowsOperationCanceledException::test e4eaf9c1ee2d55a3e6c7bca634ff1af2a0e19b7560ea616984d258df46ec89fd translated
#[test]
fn report_item_change_with_cancelled_token_throws_operation_cancelled_exception() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    composite
        .add_policy(Some(Arc::new(TestPolicy::new())))
        .unwrap();
    let source = CancellationTokenSource::new();
    source.cancel();
    let added = Arc::new(vec![Arc::try_unwrap(item()).ok().unwrap()]);
    let error = block_on(composite.report_item_change(
        Some(added),
        Some(Arc::new(Vec::new())),
        Some(source.token()),
    ))
    .unwrap_err();
    assert_eq!(error, Error::Cancelled);
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithEmptyLists_DoesNotThrow::test 6ba7e22fb7425e523f301612f7d7f6aed88e7d422050a70137d93b4a8004ae50 translated
#[test]
fn report_item_change_with_empty_lists_does_not_throw() {
    let composite = CompositeCurrentOutfitPolicy::new().unwrap();
    let policy = Arc::new(TestPolicy::new());
    composite.add_policy(Some(as_policy(&policy))).unwrap();
    block_on(composite.report_item_change(
        Some(Arc::new(Vec::new())),
        Some(Arc::new(Vec::new())),
        None,
    ))
    .unwrap();
    assert_eq!(policy.report_calls.load(Ordering::SeqCst), 1);
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithNullLists_DoesNotThrowFromComposite::test c512c54f78c46fb4774d9b5c9c1057627592e1c25267811cddf3d687d3d1ec4d translated
#[test]
fn report_item_change_with_null_lists_does_not_throw_from_composite() {
    block_on(
        CompositeCurrentOutfitPolicy::new()
            .unwrap()
            .report_item_change(None, None, None),
    )
    .unwrap();
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddRemovePolicy_ConcurrentAccess_DoesNotThrow::test a73a01065867d90305f491e41d9ea61bc27b80c026672ed920368877bfb0fe14 translated
#[test]
fn add_remove_policy_concurrent_access_does_not_throw() {
    let composite = Arc::new(CompositeCurrentOutfitPolicy::new().unwrap());
    let policies: Vec<Arc<dyn ICurrentOutfitPolicy>> = (0..10)
        .map(|_| Arc::new(TestPolicy::new()) as Arc<dyn ICurrentOutfitPolicy>)
        .collect();
    let mut threads = Vec::new();
    for policy in &policies {
        let composite = Arc::clone(&composite);
        let policy = Arc::clone(policy);
        threads.push(std::thread::spawn(move || {
            composite.add_policy(Some(policy)).map(|_| ())
        }));
    }
    for policy in policies {
        let composite = Arc::clone(&composite);
        threads.push(std::thread::spawn(move || {
            composite.remove_policy(Some(policy))
        }));
    }
    for thread in threads {
        thread.join().unwrap().unwrap();
    }
}

// parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WhileModifyingPolicies_DoesNotThrow::test 697999feeb776fae9a47cb9678c0ae0234d3b3831ce40c0e6f50fe622a5cc2b7 translated
#[test]
fn can_attach_while_modifying_policies_does_not_throw() {
    let composite = Arc::new(CompositeCurrentOutfitPolicy::new().unwrap());
    let stop = Arc::new(AtomicBool::new(false));
    let read_composite = Arc::clone(&composite);
    let read_stop = Arc::clone(&stop);
    let test_item = item();
    let reader = std::thread::spawn(move || {
        while !read_stop.load(Ordering::SeqCst) {
            let _ = read_composite.can_attach(Some(Arc::clone(&test_item)));
        }
    });
    let write_composite = Arc::clone(&composite);
    let writer = std::thread::spawn(move || {
        for _ in 0..100 {
            let policy: Arc<dyn ICurrentOutfitPolicy> = Arc::new(TestPolicy::new());
            write_composite
                .add_policy(Some(Arc::clone(&policy)))
                .unwrap();
            write_composite.remove_policy(Some(policy)).unwrap();
        }
    });
    writer.join().unwrap();
    stop.store(true, Ordering::SeqCst);
    reader.join().unwrap();
}
