use msgpacker::{MsgPacker, Packable, UnpackableBorrowed};
use proptest::prelude::*;

mod utils;

#[test]
fn nil() {
    utils::case(());
}

#[test]
fn bool() {
    utils::case(true);
    utils::case(false);
}

#[test]
fn generic() {
    trait Foo {
        fn _bar() {}
    }

    impl Foo for u8 {}

    #[derive(Debug, Default, Clone, PartialEq, Eq, MsgPacker)]
    struct Baz<F: Foo> {
        _p: core::marker::PhantomData<F>,
    }

    utils::case(Baz::<u8>::default());
}

mod isolated_declarations {
    #[derive(
        Debug, Clone, PartialEq, Eq, Hash, msgpacker::MsgPacker, proptest_derive::Arbitrary,
    )]
    pub enum Foo {
        Bar,
        Baz(u32, String),
        Qux { a: Vec<u8>, b: u64 },
    }

    #[derive(Debug, PartialEq, Eq, Hash, msgpacker::MsgPackerBorrowed)]
    pub enum FooBorrowed<'a> {
        Bar,
        Baz(u32, &'a str),
        Qux { a: &'a [u8], b: u64 },
    }
}

use isolated_declarations::*;

impl<'a> FooBorrowed<'a> {
    fn to_owned(&self) -> Foo {
        match self {
            FooBorrowed::Bar => Foo::Bar,
            FooBorrowed::Baz(a, b) => Foo::Baz(*a, b.to_string()),
            FooBorrowed::Qux { a, b } => Foo::Qux {
                a: a.to_vec(),
                b: *b,
            },
        }
    }
}

proptest! {
    #[test]
    fn array(a: [i32; 4]) {
        utils::case(a);
    }

    #[test]
    fn tuple(a: (i32, String, bool, usize)) {
        utils::case(a);
    }

    #[test]
    fn enum_foo(a: Foo) {
        utils::case(a.clone());

        let bytes = a.pack_to_vec();
        let borrowed = <FooBorrowed as UnpackableBorrowed>::unpack(&bytes).unwrap();
        let bytes_p = borrowed.pack_to_vec();

        assert_eq!(bytes, bytes_p);
        assert_eq!(a, borrowed.to_owned());
    }
}
