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

use libremetaverse::{
    DictionaryEventAction, DisposalHelper, EventSubscription, EventSubscriptionHelper, FileHelper,
    Helpers, NameValue, NameValueClassType, NameValueSendtoType, NameValueValueType,
    ObservableDictionary, PositionHelper, Repeat, Simulator, threading::OptimisticReaderWriterLock,
};
use libremetaverse_compat_tests::{assert_close, block_on};
use libremetaverse_types::compat::{
    CancellationTokenSource, Close, EventHandler, ExternalError, Guid, Object, Utf16CodeUnit,
};
use libremetaverse_types::{Matrix4, Quaternion, UUID, Utils, Vector3, Vector3d};
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Barrier, Mutex, mpsc};
use std::time::{Duration, Instant};

fn vector3(x: f32, y: f32, z: f32) -> Vector3 {
    Vector3::new_with_single_single_single(x, y, z).expect("Vector3 constructor")
}

fn vector3d(x: f64, y: f64, z: f64) -> Vector3d {
    Vector3d::new_with_double_double_double(x, y, z).expect("Vector3d constructor")
}

fn quaternion(x: f32, y: f32, z: f32, w: f32) -> Quaternion {
    Quaternion::new_with_single_single_single_single(x, y, z, w).expect("Quaternion constructor")
}

fn matrix(values: [f32; 16]) -> Matrix4 {
    Matrix4::new_with_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single(
        values[0], values[1], values[2], values[3], values[4], values[5], values[6],
        values[7], values[8], values[9], values[10], values[11], values[12], values[13],
        values[14], values[15],
    )
    .expect("Matrix4 constructor")
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.UUIDs::test 4f95d48c474bbd824254196dd605051773c711614bf661b5aa996054b6c98967 translated
#[test]
fn uuids() {
    let uuid = UUID::default();
    let bytes = uuid.get_bytes().expect("UUID.GetBytes");
    assert!(bytes.len() >= 16);
    for byte in &bytes[..16] {
        assert_eq!(*byte, 0);
    }

    let bytes_with_tail = vec![
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
        0x0f, 0xff, 0xff,
    ];
    let exact_bytes = vec![
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
        0x0f,
    ];
    let a = UUID::new_with_bytes_int32(bytes_with_tail, 0).expect("UUID byte constructor");
    let b = UUID::new_with_bytes_int32(exact_bytes.clone(), 0).expect("UUID byte constructor");
    assert!(a.equals_with_uuid(b), "UUID comparison operator failed");

    let text = "00010203-0405-0607-0809-0a0b0c0d0e0f";
    let a = UUID::new_with_bytes_int32(exact_bytes, 0).expect("UUID byte constructor");
    let b = UUID::new_with_string(text.to_owned()).expect("UUID string constructor");
    assert!(
        a.equals_with_uuid(b),
        "UUID hyphenated string constructor failed"
    );
    assert!(a.equals_with_uuid(b));
    assert!(a.equals_with_uuid(UUID::try_from(text.to_owned())));
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.Vector3ApproxEquals::test 1438175bb0c0bd8b17dd878e8fb95fb0dba82abbab71882f2ceb39ec554189d9 translated
#[test]
fn vector3_approx_equals() {
    let mut a = vector3(1.0, 0.0, 0.0);
    let b = vector3(0.0, 0.0, 0.0);
    assert!(!a.approx_equals(b, 0.9).expect("ApproxEquals (1)"));
    assert!(a.approx_equals(b, 1.0).expect("ApproxEquals (2)"));

    a = vector3(-1.0, 0.0, 0.0);
    let b = vector3(1.0, 0.0, 0.0);
    assert!(!a.approx_equals(b, 1.9).expect("ApproxEquals (3)"));
    assert!(a.approx_equals(b, 2.0).expect("ApproxEquals (4)"));

    a = vector3(0.0, -1.0, 0.0);
    let b = vector3(0.0, -1.1, 0.0);
    assert!(!a.approx_equals(b, 0.09).expect("ApproxEquals (5)"));
    assert!(a.approx_equals(b, 0.11).expect("ApproxEquals (6)"));

    a = vector3(0.0, 0.0, 0.00001);
    let mut b = vector3(0.0, 0.0, 0.0);
    assert!(
        !b.approx_equals(a, f32::from_bits(1))
            .expect("ApproxEquals epsilon")
    );
    assert!(b.approx_equals(a, 0.0001).expect("ApproxEquals (7)"));
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test 27c0a4b8402ef66f11dca075d8c1a0f0b647ccef9c2803125dde6ca292951cd4 translated
#[test]
fn vector_casting() {
    for (label, value) in [
        ("1.0", 1.0),
        ("1.1", 1.1),
        ("1.01", 1.01),
        ("1.001", 1.001),
        ("1.0001", 1.0001),
        ("1.00001", 1.00001),
        ("1.000001", 1.000_001),
        ("1.0000001", 1.000_000_1),
        ("1.00000001", 1.000_000_01),
    ] {
        let narrowed = value as f32;
        let no_precision_loss = value == f64::from(narrowed);
        let a = vector3(narrowed, narrowed, narrowed);
        let b = vector3d(value, value, value);
        let c = Vector3::try_from(b);
        let d = Vector3d::from(a);

        if no_precision_loss {
            eprintln!(
                "Unsuitable test value used- test number should have precision loss when cast to float ({label})."
            );
        } else {
            assert!(
                !b.equals_with_vector3d(d),
                "explicit double-to-float cast unexpectedly retained precision for {label}"
            );
        }
        assert!(
            a.equals_with_vector3(c),
            "explicit Vector3d-to-Vector3 cast differed for {label}"
        );
        assert!(
            Vector3d::eq(Vector3d::from(a), d),
            "implicit Vector3-to-Vector3d cast differed for {label}"
        );
    }
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.Quaternions::test 071cf3ef6932e093c0fd09fe461a738e75af905707fce9a321b8f08dd89c09dd translated
#[test]
fn quaternions() {
    let mut a = quaternion(1.0, 0.0, 0.0, 0.0);
    let mut b = quaternion(1.0, 0.0, 0.0, 0.0);
    assert!(a.equals_with_quaternion(b), "Quaternion comparison failed");

    let mut expected = quaternion(0.0, 0.0, 0.0, -1.0);
    let mut result = Quaternion::mul_with_quaternion_quaternion(a, b);
    assert!(result.equals_with_quaternion(expected));

    a = quaternion(1.0, 0.0, 0.0, 0.0);
    b = quaternion(0.0, 1.0, 0.0, 0.0);
    expected = quaternion(0.0, 0.0, 1.0, 0.0);
    result = Quaternion::mul_with_quaternion_quaternion(a, b);
    assert!(result.equals_with_quaternion(expected));

    a = quaternion(0.0, 0.0, 1.0, 0.0);
    b = quaternion(0.0, 1.0, 0.0, 0.0);
    expected = quaternion(-1.0, 0.0, 0.0, 0.0);
    result = Quaternion::mul_with_quaternion_quaternion(a, b);
    assert!(result.equals_with_quaternion(expected));
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.TestMatrix::test 5d5ccc8b9ea1741b013fd945e42ba7e6511322fdce51998e358d42c1736457e3 translated
#[test]
fn test_matrix() {
    let mut value = matrix([
        0.0, 0.0, 74.0, 1.0, 0.0, 435.0, 0.0, 1.0, 345.0, 0.0, 34.0, 1.0, 0.0, 0.0, 0.0, 0.0,
    ]);
    assert_close(
        f64::from(value.determinant().expect("Matrix4 determinant")),
        0.0,
        0.001,
    );
    let identity = Matrix4::identity();
    assert!(identity.equals_with_matrix4(
        Matrix4::inverse(Matrix4::identity()).expect("Matrix4 inverse identity")
    ));

    value = matrix([
        1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
    ]);
    let expected = matrix([
        0.0, 1.0, -1.0, 0.0, 1.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
    ]);
    assert!(
        Matrix4::inverse(value)
            .expect("Matrix4 inverse")
            .equals_with_matrix4(expected)
    );
}

// parity-case: LibreMetaverse.Tests/TypeTests.cs::TypeTests.FloatsToTerseStrings::test ce34cdfe106d5dadfa82056e7aa7690d2da748c7e753b9d2a2ad682d86327def translated
#[test]
fn floats_to_terse_strings() {
    let mut value = 1.20;
    let mut expected = "1.2";
    let mut actual = Helpers::float_to_terse_string(value).expect("FloatToTerseString");
    assert_eq!(actual, expected);

    value = 24.00;
    expected = "24";
    actual = Helpers::float_to_terse_string(value).expect("FloatToTerseString");
    assert_eq!(actual, expected);

    value = -0.59;
    expected = "-.59";
    actual = Helpers::float_to_terse_string(value).expect("FloatToTerseString");
    assert_eq!(actual, expected);

    value = 0.59;
    expected = ".59";
    actual = Helpers::float_to_terse_string(value).expect("FloatToTerseString");
    assert_eq!(actual, expected);
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.FileHelper_SanitizeAndSafeNames::test 770d1cb751c4b8290c9443bcb4a91d17703628a157cc428c607a7d36ae0af985 translated
#[test]
fn file_helper_sanitize_and_safe_names() {
    let safe = FileHelper::safe_file_name("inva:lid/fi*le?.txt".to_owned())
        .expect("FileHelper.SafeFileName");
    assert!(!safe.is_empty());
    assert!(!safe.contains(':'));
    assert!(
        !FileHelper::safe_dir_name("path|with?bad".to_owned())
            .expect("FileHelper.SafeDirName")
            .contains('|')
    );

    let sanitized = FileHelper::sanitize(
        "abcxyz".to_owned(),
        vec![Utf16CodeUnit('a' as u16), Utf16CodeUnit('b' as u16)],
        Some(Utf16CodeUnit('-' as u16)),
    )
    .expect("FileHelper.Sanitize");
    assert_eq!(sanitized, "--cxyz");

    let mut first = None;
    let mut last = None;
    assert!(FileHelper::try_parse_two_names(
        "First Last".to_owned(),
        &mut first,
        &mut last
    ));
    assert_eq!(first.as_deref(), Some("First"));
    assert_eq!(last.as_deref(), Some("Last"));
    assert!(!FileHelper::try_parse_two_names(
        "  Too   Many  Parts  Extra".to_owned(),
        &mut first,
        &mut last
    ));
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.ObservableDictionary_Events_FireOnAddRemoveAndClear::test 579412c3c839e1fe5b44cf956f8d329a15d4e05c05778b4e6e3ff9b7d2fb7607 translated
#[test]
fn observable_dictionary_events_fire_on_add_remove_and_clear() {
    let mut dictionary = ObservableDictionary::<String, i32>::new_with_constructor()
        .expect("ObservableDictionary constructor");
    let adds = Arc::new(AtomicI32::new(0));
    let removes = Arc::new(AtomicI32::new(0));
    let changes = Arc::new(AtomicI32::new(0));

    let count = Arc::clone(&adds);
    dictionary
        .add_delegate(
            DictionaryEventAction::Add,
            Arc::new(move |action, _entry| {
                if action == DictionaryEventAction::Add {
                    count.fetch_add(1, Ordering::SeqCst);
                }
            }),
        )
        .expect("add Add callback");
    let count = Arc::clone(&removes);
    dictionary
        .add_delegate(
            DictionaryEventAction::Remove,
            Arc::new(move |action, _entry| {
                if action == DictionaryEventAction::Remove {
                    count.fetch_add(1, Ordering::SeqCst);
                }
            }),
        )
        .expect("add Remove callback");
    let count = Arc::clone(&changes);
    dictionary
        .add_delegate(
            DictionaryEventAction::Change,
            Arc::new(move |action, _entry| {
                if action == DictionaryEventAction::Change {
                    count.fetch_add(1, Ordering::SeqCst);
                }
            }),
        )
        .expect("add Change callback");

    dictionary
        .add("one".to_owned(), Some(1))
        .expect("dictionary add one");
    assert_eq!(adds.load(Ordering::SeqCst), 1);
    dictionary.set_item(Some("one".to_owned()), Some(2));
    assert!(adds.load(Ordering::SeqCst) >= 1);
    assert!(
        dictionary
            .remove("one".to_owned())
            .expect("dictionary remove one")
    );
    assert_eq!(removes.load(Ordering::SeqCst), 1);
    dictionary
        .add("a".to_owned(), Some(1))
        .expect("dictionary add a");
    dictionary
        .add("b".to_owned(), Some(2))
        .expect("dictionary add b");
    dictionary.clear().expect("dictionary clear");
    assert!(removes.load(Ordering::SeqCst) >= 3);
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.EventSubscriptionHelper_WaitForEventAndAsync::test bab59e0caf1630edda9a2a6daecca95cd1a28f189fdde01b1b6afee4cd96d8cb translated
#[test]
fn event_subscription_helper_wait_for_event_and_async() {
    type UnitHandler = EventHandler<Option<()>>;

    let event = Arc::new(Mutex::new(None::<UnitHandler>));
    let subscribed_event = Arc::clone(&event);
    let unsubscribed_event = Arc::clone(&event);
    let fired_event = Arc::clone(&event);
    let (subscribed_tx, subscribed_rx) = mpsc::sync_channel(0);
    let fire_task = std::thread::spawn(move || {
        if subscribed_rx.recv_timeout(Duration::from_secs(5)).is_ok()
            && let Some(handler) = fired_event.lock().expect("event state").as_ref()
        {
            handler(Some(()));
        }
    });

    let result =
        EventSubscriptionHelper::wait_for_event_with_action_action_func_func_int32_t_result::<
            (),
            i32,
        >(
            Box::new(move |handler| {
                *subscribed_event.lock().expect("event state") = Some(handler);
                subscribed_tx.send(()).expect("subscription signal");
            }),
            Box::new(move |_handler| {
                *unsubscribed_event.lock().expect("event state") = None;
            }),
            Box::new(|_event| true),
            Box::new(|_event| Some(123)),
            5_000,
            Some(-1),
        )
        .expect("EventSubscriptionHelper.WaitForEvent");
    assert_eq!(result, Some(123));
    fire_task.join().expect("event fire task");

    let event = Arc::new(Mutex::new(None::<UnitHandler>));
    let subscribed_event = Arc::clone(&event);
    let unsubscribed_event = Arc::clone(&event);
    let fired_event = Arc::clone(&event);
    let (subscribed_tx, subscribed_rx) = mpsc::sync_channel(0);
    let fire_task = std::thread::spawn(move || {
        if subscribed_rx.recv_timeout(Duration::from_secs(5)).is_ok()
            && let Some(handler) = fired_event.lock().expect("event state").as_ref()
        {
            handler(Some(()));
        }
    });

    let result = block_on(
        EventSubscriptionHelper::wait_for_event_with_action_action_func_func_int32_cancellation_token_t_result::<(), Guid>(
            Box::new(move |handler| {
                *subscribed_event.lock().expect("event state") = Some(handler);
                subscribed_tx.send(()).expect("subscription signal");
            }),
            Box::new(move |_handler| {
                *unsubscribed_event.lock().expect("event state") = None;
            }),
            Box::new(|_event| true),
            Box::new(|_event| Some(Guid([0; 16]))),
            5_000,
            None,
            Some(Guid([1; 16])),
        ),
    )
    .expect("EventSubscriptionHelper.WaitForEventAsync");
    assert_eq!(result, Some(Guid([0; 16])));
    fire_task.join().expect("async event fire task");

    let event = Arc::new(Mutex::new(None::<UnitHandler>));
    let subscribed_event = Arc::clone(&event);
    let unsubscribed_event = Arc::clone(&event);
    let subscription = EventSubscription::<()>::new(
        Box::new(move |handler| {
            *subscribed_event.lock().expect("event state") = Some(handler);
        }),
        Box::new(move |_handler| {
            *unsubscribed_event.lock().expect("event state") = None;
        }),
        Arc::new(|_event| {}),
    )
    .expect("EventSubscription constructor");
    subscription
        .dispose()
        .expect("EventSubscription disposable close");
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.Repeat_Interval_ExecutesAndCancels::test 43250eb6d7799ca3c00e51810b3e04d9a874bfdeafd77af6f7b78f8f6c73ff77 translated
#[test]
fn repeat_interval_executes_and_cancels() {
    let count = Arc::new(AtomicI32::new(0));
    let action_count = Arc::clone(&count);
    let (executed_tx, executed_rx) = mpsc::sync_channel(1);
    let cancellation = CancellationTokenSource::new();
    let token = cancellation.token();
    let (task_tx, task_rx) = mpsc::sync_channel(1);
    let task = std::thread::spawn(move || {
        let result = block_on(
            Repeat::interval_with_time_span_action_cancellation_token_boolean(
                Duration::from_millis(50),
                Box::new(move || {
                    action_count.fetch_add(1, Ordering::SeqCst);
                    let _ = executed_tx.try_send(());
                }),
                token,
                Some(true),
            ),
        );
        task_tx.send(result).expect("repeat task result");
    });

    let started = Instant::now();
    loop {
        if let Ok(result) = task_rx.try_recv() {
            result.expect("Repeat.Interval");
            panic!("Repeat.Interval completed before cancellation");
        }
        if executed_rx.recv_timeout(Duration::from_millis(10)).is_ok() {
            break;
        }
        assert!(
            started.elapsed() < Duration::from_secs(30),
            "Expected at least 1 execution within 30s, but got {}",
            count.load(Ordering::SeqCst)
        );
    }

    cancellation.cancel();
    if let Ok(result) = task_rx.recv_timeout(Duration::from_secs(2)) {
        result.expect("Repeat.Interval");
        task.join().expect("repeat task");
    }
    assert!(
        count.load(Ordering::SeqCst) >= 1,
        "Expected at least 1 execution"
    );
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.NameValue_ParseAndToString::test f779a81d286c0028ea202bab00f8db0d1bbaea611003458918a9ec9a24207557 translated
#[test]
fn name_value_parse_and_to_string() {
    let value = NameValue::new_with_string("greeting STRING R S Hello".to_owned())
        .expect("NameValue string constructor");
    assert_eq!(value.name, "greeting");
    assert_eq!(value.value, Some(Object::from("Hello")));

    let values = vec![
        NameValue::new_with_string_value_type_class_type_sendto_type_object(
            "n1".to_owned(),
            NameValueValueType::String,
            NameValueClassType::ReadOnly,
            NameValueSendtoType::Sim,
            Object::from("v1"),
        )
        .expect("NameValue component constructor"),
    ];
    let rendered = NameValue::name_values_to_string(values).expect("NameValuesToString");
    assert!(rendered.contains("n1") && rendered.contains("v1"));
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.Helpers_SplitAndFloatToTerseString::test aa2c378f56cfd04d6d4cfa50cd3efafd2b2d479d681336d72fbb9d5ec5c363e6 translated
#[test]
fn helpers_split_and_float_to_terse_string() {
    let chunks: Vec<_> = Helpers::split_by("abcdef".to_owned(), 2)
        .expect("SplitBy")
        .collect();
    assert_eq!(chunks, ["ab", "cd", "ef"]);
    assert_eq!(
        Helpers::float_to_terse_string(0.0).expect("FloatToTerseString zero"),
        ".00"
    );
    assert!(
        Helpers::float_to_terse_string(1.2300)
            .expect("FloatToTerseString 1.23")
            .contains("1.23")
    );
}

struct ThrowOnClose {
    disposed: Arc<AtomicBool>,
}

impl Close for ThrowOnClose {
    fn close(&mut self) -> Result<(), ExternalError> {
        self.disposed.store(true, Ordering::SeqCst);
        Err(ExternalError("boom".to_owned()))
    }
}

struct DisposableFlag(Cell<i32>);

impl Close for DisposableFlag {
    fn close(&mut self) -> Result<(), ExternalError> {
        Ok(())
    }
}

// parity-case: LibreMetaverse.Tests/UtilUnitTests.cs::UtilUnitTests.DisposalHelper_SafeDisposeAndUsing::test 433ebbfeb69eeb7ceaac86c4b3637468152a1f5a9f4c12ea8d7a74e1cb0c4fec translated
#[test]
fn disposal_helper_safe_dispose_and_using() {
    let disposed = Arc::new(AtomicBool::new(false));
    let logged = Arc::new(Mutex::new(None::<String>));
    let logged_message = Arc::clone(&logged);
    DisposalHelper::safe_dispose(
        Box::new(ThrowOnClose {
            disposed: Arc::clone(&disposed),
        }),
        Some("test".to_owned()),
        Some(Box::new(move |message, _error| {
            *logged_message.lock().expect("logger state") = Some(message);
        })),
    )
    .expect("DisposalHelper.SafeDispose");
    assert!(disposed.load(Ordering::SeqCst));
    assert!(logged.lock().expect("logger state").is_some());

    let result = DisposalHelper::using_with_func_func_3fbb9598(
        Box::new(|| DisposableFlag(Cell::new(0))),
        Box::new(|value| {
            value.0.set(5);
            Some(value.0.get())
        }),
    )
    .expect("DisposalHelper.Using");
    assert_eq!(result, Some(5));
}

// parity-case: LibreMetaverse.Tests/PositionHelperTests.cs::PositionHelperTests.ToGlobalAndLocalPosition_RoundTrip::test 695c6a640e5353e02def9a1b4a1c1eabb37a8543c13466dff570f58d847e7772 translated
#[test]
fn to_global_and_local_position_round_trip() {
    let global_x = 512 * Simulator::DEFAULT_REGION_SIZE_X;
    let global_y = 1024 * Simulator::DEFAULT_REGION_SIZE_Y;
    let handle = Utils::u_ints_to_long(global_x, global_y).expect("Utils.UIntsToLong");
    let local = vector3(10.5, 20.25, 5.0);
    let global = PositionHelper::to_global_position(handle, local).expect("ToGlobalPosition");
    assert_close(global.x, f64::from(global_x) + f64::from(local.x), 0.0001);
    assert_close(global.y, f64::from(global_y) + f64::from(local.y), 0.0001);
    assert_close(global.z, f64::from(local.z), 0.0001);

    let back = PositionHelper::to_local_position(handle, global).expect("ToLocalPosition");
    assert_close(f64::from(back.x), f64::from(local.x), 0.0001);
    assert_close(f64::from(back.y), f64::from(local.y), 0.0001);
    assert_close(f64::from(back.z), f64::from(local.z), 0.0001);
}

// parity-case: LibreMetaverse.Tests/PositionHelperTests.cs::PositionHelperTests.FormatCoordinates_FormatsCorrectly::test fe4256d00dab5cc2de602b7b27442cac627a5956c4ce79c05d92de830f6fc5d2 translated
#[test]
fn format_coordinates_formats_correctly() {
    assert_eq!(
        PositionHelper::format_coordinates(None, None, None).expect("FormatCoordinates()"),
        ""
    );
    assert_eq!(
        PositionHelper::format_coordinates(Some(Some(10)), None, None)
            .expect("FormatCoordinates(10)"),
        " (10)"
    );
    assert_eq!(
        PositionHelper::format_coordinates(Some(Some(10)), Some(Some(20)), None)
            .expect("FormatCoordinates(10, 20)"),
        " (10,20)"
    );
    assert_eq!(
        PositionHelper::format_coordinates(Some(Some(10)), Some(Some(20)), Some(Some(30)))
            .expect("FormatCoordinates(10, 20, 30)"),
        " (10,20,30)"
    );
}

// parity-case: LibreMetaverse.Tests/ReaderWriterLockTests.cs::ReaderWriterLockTests.MultipleReadersOneWriter_ReaderConcurrencyAndWriterExclusion::test bf63804480951667455038244228f77c30454a365b496f062051f7b34e7b2618 translated
#[test]
fn multiple_readers_one_writer_reader_concurrency_and_writer_exclusion() {
    const READER_COUNT: usize = 5;
    let lock = Arc::new(OptimisticReaderWriterLock::new().expect("lock constructor"));
    let readers_ready = Arc::new(Barrier::new(READER_COUNT + 1));
    let start_gate = Arc::new(Barrier::new(READER_COUNT + 1));
    let start_release = Arc::new(Barrier::new(READER_COUNT + 1));
    let current_readers = Arc::new(AtomicI32::new(0));
    let max_concurrent_readers = Arc::new(AtomicI32::new(0));
    let (entered_tx, entered_rx) = mpsc::channel();
    let (exited_tx, exited_rx) = mpsc::channel();
    let mut readers = Vec::new();

    for _ in 0..READER_COUNT {
        let lock = Arc::clone(&lock);
        let readers_ready = Arc::clone(&readers_ready);
        let start_gate = Arc::clone(&start_gate);
        let start_release = Arc::clone(&start_release);
        let current_readers = Arc::clone(&current_readers);
        let max_concurrent_readers = Arc::clone(&max_concurrent_readers);
        let entered_tx = entered_tx.clone();
        let exited_tx = exited_tx.clone();
        readers.push(std::thread::spawn(move || {
            readers_ready.wait();
            start_gate.wait();
            let mut guard = lock.read_lock().expect("read lock");
            let current = current_readers.fetch_add(1, Ordering::SeqCst) + 1;
            max_concurrent_readers.fetch_max(current, Ordering::SeqCst);
            entered_tx.send(()).expect("reader entered");
            start_release.wait();
            std::thread::sleep(Duration::from_millis(20));
            current_readers.fetch_sub(1, Ordering::SeqCst);
            exited_tx.send(()).expect("reader exited");
            guard.dispose().expect("dispose read lock");
        }));
    }

    readers_ready.wait();
    start_gate.wait();
    for _ in 0..READER_COUNT {
        entered_rx
            .recv_timeout(Duration::from_secs(30))
            .expect("all readers should enter within 30 seconds");
    }

    let writer_lock = Arc::clone(&lock);
    let (writer_tx, writer_rx) = mpsc::channel();
    let writer = std::thread::spawn(move || {
        let mut guard = writer_lock.write_lock().expect("write lock");
        writer_tx.send(()).expect("writer acquired");
        guard.dispose().expect("dispose write lock");
    });
    assert!(writer_rx.recv_timeout(Duration::from_millis(100)).is_err());
    start_release.wait();
    for _ in 0..READER_COUNT {
        exited_rx
            .recv_timeout(Duration::from_secs(30))
            .expect("all readers should exit within 30 seconds");
    }
    writer_rx
        .recv_timeout(Duration::from_secs(30))
        .expect("writer should acquire after readers exit");
    assert!(max_concurrent_readers.load(Ordering::SeqCst) >= 2);

    for reader in readers {
        reader.join().expect("reader task");
    }
    writer.join().expect("writer task");
}

// parity-case: LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test 0bef42801dc2a50e132b05440424c44da54b53c853708ee2c0ec9799dbe0e8b2 translated
#[test]
fn string_to_bytes() {
    let text = "Your squirrel, your imagination";
    let bytes = Utils::string_to_bytes(text.to_owned()).expect("Utils.StringToBytes");
    let decoded = Utils::bytes_to_string_with_bytes(bytes).expect("Utils.BytesToString");
    assert_eq!(decoded, text);
}
