use cxx_enumext_test_suite::{
    ffi::{
        self, make_enum, make_enum_opaque, make_enum_shared, make_enum_shared_ref, make_enum_str,
        mul2_if_gt10, take_enum, take_mut_enum, take_optional,
    },
    OptionalI32, RustEnum, RustValue, SharedData,
};

fn print_enum(enm: &RustEnum) {
    match &enm {
        RustEnum::Empty => println!("The value is string Empty"),
        RustEnum::String(s) => println!("The value is string '{s}'"),
        RustEnum::Num(n) => println!("The value is {n}"),
        RustEnum::Bool(b) => println!("The value is {b}"),
        RustEnum::Shared(d) => println!("The value is Shared Data {d:?}"),
        _ => println!("The value is {enm:?}"),
    }
}

#[test]
fn test_pass_ffi() {
    let f = RustEnum::String(String::from("I'm a Rust String"));
    assert_eq!(take_enum(&f), 2);
    let f = RustEnum::Empty;
    assert_eq!(take_enum(&f), 0);
    let f = RustEnum::Num(42);
    assert_eq!(take_enum(&f), 1);

    let f = RustEnum::Shared(SharedData {
        size: 2,
        tags: vec!["rust_tag_1".into(), "rust_tag_2".into()],
    });
    assert_eq!(take_enum(&f), 4);
    {
        let shared = SharedData {
            size: 2,
            tags: vec![
                "rust_tag_1".into(),
                "rust_tag_2".into(),
                "rust_tag_3".into(),
            ],
        };
        let f = RustEnum::SharedRef(&shared);
        assert_eq!(take_enum(&f), 5);
    }

    let f = RustEnum::Opaque(Box::new(RustValue::new("A Hidden Rust String from Rust")));
    assert_eq!(take_enum(&f), 6);
    {
        let opaque = RustValue::new("A Hidden Rust String from Rust behind ref");
        let f = RustEnum::OpaqueRef(&opaque);

        assert_eq!(take_enum(&f), 7);
    }

    let f = RustEnum::Tuple(42, 1377);
    assert_eq!(take_enum(&f), 8);
    let f = RustEnum::Struct {
        val: 70,
        str: "Also a Rust String".into(),
    };
    assert_eq!(take_enum(&f), 9);
    let f = RustEnum::Unit1;
    assert_eq!(take_enum(&f), 10);
    let f = RustEnum::Unit2;
    assert_eq!(take_enum(&f), 11);
}

#[test]
fn test_from_ffi() {
    let mut f = make_enum();
    print_enum(&f);
    assert!(matches!(f, RustEnum::Num(1502)));
    take_mut_enum(&mut f);
    assert!(matches!(&f, RustEnum::Bool(false)));
    print_enum(&f);
    let f = make_enum_str();
    assert!(matches!(&f, RustEnum::String(s) if s == "String from c++"));
    print_enum(&f);
    let f = make_enum_shared();
    assert!(matches!(&f, RustEnum::Shared(s) if s.size == 4));
    print_enum(&f);
    let f = make_enum_shared_ref();
    assert!(matches!(&f, RustEnum::SharedRef(s) if s.size == 5));
    print_enum(&f);
    let f = make_enum_opaque();
    assert!(matches!(&f, RustEnum::Opaque(_)));
    print_enum(&f);
}

#[test]
fn test_pass_optional_ffi() {
    let optional = OptionalI32::Some(8);
    assert!(take_optional(&optional));
    let optional = OptionalI32::None;
    assert!(!take_optional(&optional));
}

#[test]
fn test_get_expected_ffi() {
    let result: Result<_, _> = mul2_if_gt10(12).into();
    println!("result is: {result:?}");
    assert!(matches!(result, Ok(24)));
    let result: Result<_, _> = mul2_if_gt10(8).into();
    println!("result is: {result:?}");
    assert!(matches!(result, Err(s) if s == "value too small"));
}

#[test]
fn test_expected_void_specialisation() {
    assert_eq!(ffi::take_expected_void(Err(100).into()), 100);
    assert_eq!(ffi::take_expected_void(Ok(()).into()), 1000);
    assert!(matches!(ffi::make_expected_void().into(), Ok(())));
    assert!(matches!(ffi::make_unexpected_void().into(), Err(42)));
}
