#![cfg(all(feature = "derive", feature = "alloc"))]

use std::marker::PhantomData;

use bin_proto::{BitCodec, BitDecode, BitEncode};
use bitstream_io::BigEndian;

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
#[bin_proto(discriminant_type = u8)]
#[bin_proto(ctx = ())]
pub enum Enum<'a, T: BitDecode + BitEncode> {
    #[bin_proto(discriminant = 1)]
    Variant1 {
        a: T,
        len: u8,
        #[bin_proto(tag = len as usize)]
        arr: Vec<u8>,
    },
    #[bin_proto(discriminant = 2)]
    Variant2(u32, bool, PhantomData<&'a T>),
}

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
#[bin_proto(discriminant_type = u8)]
#[bin_proto(bits = 2)]
pub enum Enum2 {
    #[bin_proto(discriminant = 3, other)]
    CatchAll(u8),
    #[bin_proto(discriminant = 1)]
    Variant1(u8),
    #[bin_proto(discriminant = 2)]
    Variant2(u16),
}

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
pub struct EnumContainer {
    e: Enum2,
}

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
pub struct TaggedEnumContainer {
    #[bin_proto(tag_type = u16, tag_value = ::bin_proto::Discriminable::discriminant(&self.e).unwrap() as u16)]
    e: Enum2,
}

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
pub struct BitFieldTaggedEnumContainer {
    #[bin_proto(write_value = ::bin_proto::Discriminable::discriminant(&self.e).unwrap())]
    #[bin_proto(bits = 3)]
    discriminant: u8,
    #[bin_proto(tag = discriminant)]
    e: Enum2,
}

#[derive(Debug, BitDecode, BitEncode, PartialEq)]
#[repr(u8)]
pub enum Repr {
    VariantA = 1,
    VariantB = 2,
}

#[test]
fn decode_enum_variant() {
    assert_eq!(
        (
            Enum::Variant1 {
                a: 64u8,
                len: 2,
                arr: vec![1, 2]
            },
            40
        ),
        Enum::decode_bytes(&[1, 64, 2, 1, 2], BigEndian).unwrap()
    );
}

#[test]
fn encode_enum_variant() {
    assert_eq!(
        Enum::Variant2::<u32>(20, true, PhantomData)
            .encode_bytes(BigEndian)
            .unwrap(),
        vec![2, 0, 0, 0, 20, 1]
    );
}

#[test]
fn decode_enum_variant_in_container() {
    assert_eq!(
        (
            EnumContainer {
                e: Enum2::Variant1(2)
            },
            10
        ),
        EnumContainer::decode_bytes(&[64, 128], BigEndian).unwrap()
    );
}

#[test]
fn encode_enum_variant_in_container() {
    assert_eq!(
        EnumContainer {
            e: Enum2::Variant2(511)
        }
        .encode_bytes(BigEndian)
        .unwrap(),
        vec![128, 127, 192]
    );
}

#[test]
fn decode_enum_variant_in_container_tagged() {
    assert_eq!(
        (
            TaggedEnumContainer {
                e: Enum2::Variant1(2)
            },
            24
        ),
        TaggedEnumContainer::decode_bytes(&[0, 1, 2], BigEndian).unwrap()
    );
}

#[test]
fn encode_enum_variant_in_container_tagged() {
    assert_eq!(
        TaggedEnumContainer {
            e: Enum2::Variant2(511)
        }
        .encode_bytes(BigEndian)
        .unwrap(),
        vec![0, 2, 1, 255,]
    );
}

#[test]
fn decode_enum_variant_in_container_tagged_bitfield() {
    assert_eq!(
        (
            BitFieldTaggedEnumContainer {
                discriminant: 1,
                e: Enum2::Variant1(2)
            },
            11
        ),
        BitFieldTaggedEnumContainer::decode_bytes(&[32, 64], BigEndian).unwrap()
    );
}

#[test]
fn encode_enum_variant_in_container_tagged_bitfield() {
    assert_eq!(
        BitFieldTaggedEnumContainer {
            discriminant: 2,
            e: Enum2::Variant2(511)
        }
        .encode_bytes(BigEndian)
        .unwrap(),
        vec![64, 63, 224]
    );
}

#[test]
fn encode_enum_variant_catch_all() {
    assert_eq!(
        Enum2::CatchAll(8).encode_bytes(BigEndian).unwrap(),
        vec![194, 0]
    );
}

#[test]
fn decode_enum_variant_catch_all_discriminant() {
    assert_eq!(
        (Enum2::CatchAll(8), 10),
        Enum2::decode_bytes(&[194, 0], BigEndian).unwrap()
    );
}

#[test]
fn decode_enum_variant_catch_all() {
    assert_eq!(
        (Enum2::CatchAll(8), 10),
        Enum2::decode_bytes(&[2, 0], BigEndian).unwrap()
    );
}

#[test]
fn decode_enum_repr() {
    assert_eq!(
        (Repr::VariantA, 8),
        Repr::decode_bytes(&[1], BigEndian).unwrap()
    );
}

#[test]
fn encode_enum_repr() {
    assert_eq!(Repr::VariantB.encode_bytes(BigEndian).unwrap(), vec![2]);
}
