use deku::prelude::*;

#[test]
fn test_primitive_sizes() {
    assert_eq!(u8::SIZE_BYTES, Some(1));
    assert_eq!(u16::SIZE_BYTES, Some(2));
    assert_eq!(u32::SIZE_BYTES, Some(4));
    assert_eq!(u64::SIZE_BYTES, Some(8));
    assert_eq!(u128::SIZE_BYTES, Some(16));

    assert_eq!(i8::SIZE_BYTES, Some(1));
    assert_eq!(i16::SIZE_BYTES, Some(2));
    assert_eq!(i32::SIZE_BYTES, Some(4));
    assert_eq!(i64::SIZE_BYTES, Some(8));
    assert_eq!(i128::SIZE_BYTES, Some(16));

    assert_eq!(f32::SIZE_BYTES, Some(4));
    assert_eq!(f64::SIZE_BYTES, Some(8));

    assert_eq!(bool::SIZE_BYTES, Some(1));
}

#[test]
fn test_array_sizes() {
    assert_eq!(<[u8; 4]>::SIZE_BYTES, Some(4));
    assert_eq!(<[u16; 4]>::SIZE_BYTES, Some(8));
    assert_eq!(<[u32; 10]>::SIZE_BYTES, Some(40));
    assert_eq!(<[u8; 0]>::SIZE_BYTES, Some(0));

    assert_eq!(<[[u8; 2]; 3]>::SIZE_BYTES, Some(6));
}

#[test]
fn test_tuple_sizes() {
    assert_eq!(<(u8,)>::SIZE_BYTES, Some(1));
    assert_eq!(<(u8, u16)>::SIZE_BYTES, Some(3));
    assert_eq!(<(u8, u16, u32)>::SIZE_BYTES, Some(7));
    assert_eq!(<(u8, u8, u8, u8)>::SIZE_BYTES, Some(4));
}

#[test]
fn test_simple_struct() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct SimpleStruct {
        a: u8,
        b: u16,
        c: u32,
    }

    assert_eq!(SimpleStruct::SIZE_BYTES, Some(7));
}

#[test]
#[cfg(feature = "bits")]
fn test_bit_sized_fields() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct BitStruct {
        #[deku(bits = 4)]
        a: u8,
        #[deku(bits = 4)]
        b: u8,
        c: u8,
    }

    assert_eq!(BitStruct::SIZE_BYTES, Some(2));
}

#[test]
#[cfg(feature = "bits")]
fn test_non_byte_aligned() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct NonAligned {
        #[deku(bits = 3)]
        a: u8,
        #[deku(bits = 5)]
        b: u8,
        #[deku(bits = 6)]
        c: u8,
    }

    assert_eq!(NonAligned::SIZE_BITS, 14);
    assert_eq!(NonAligned::SIZE_BYTES, None);
}

#[test]
fn test_nested_struct() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct Inner {
        x: u16,
        y: u16,
    }

    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct Outer {
        header: u8,
        inner: Inner,
        footer: u8,
    }

    assert_eq!(Inner::SIZE_BYTES, Some(4));
    assert_eq!(Outer::SIZE_BYTES, Some(6));
}

#[test]
fn test_struct_with_arrays() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct WithArray {
        count: u8,
        data: [u8; 4],
    }

    assert_eq!(WithArray::SIZE_BYTES, Some(5));
}

#[test]
fn test_unit_struct() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct UnitStruct;

    assert_eq!(UnitStruct::SIZE_BYTES, Some(0));
}

#[test]
fn test_tuple_struct() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct TupleStruct(u8, u16, u32);

    assert_eq!(TupleStruct::SIZE_BYTES, Some(7));
}

#[test]
fn test_simple_enum() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum SimpleEnum {
        #[deku(id = 0)]
        A,
        #[deku(id = 1)]
        B,
    }

    assert_eq!(SimpleEnum::SIZE_BYTES, Some(1));
}

#[test]
fn test_enum_with_fields() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum DataEnum {
        #[deku(id = 0)]
        Small(u8),
        #[deku(id = 1)]
        Large(u32),
    }

    assert_eq!(DataEnum::SIZE_BYTES, Some(5));
}

#[test]
fn test_enum_with_named_fields() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum NamedEnum {
        #[deku(id = 0)]
        A { x: u16 },
        #[deku(id = 1)]
        B { y: u32, z: u8 },
    }

    assert_eq!(NamedEnum::SIZE_BYTES, Some(6));
}

#[test]
fn test_with_to_slice() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct Message {
        msg_type: u8,
        payload: [u8; 16],
        checksum: u16,
    }

    const MSG_SIZE: usize = Message::SIZE_BYTES.unwrap();
    let mut buffer = [0u8; MSG_SIZE];

    assert_eq!(MSG_SIZE, 19);

    let msg = Message {
        msg_type: 0x01,
        payload: [0xFF; 16],
        checksum: 0xABCD,
    };

    let written = msg.to_slice(&mut buffer).unwrap();
    assert_eq!(written, MSG_SIZE);

    let (_, parsed) = Message::from_bytes((&buffer, 0)).unwrap();
    assert_eq!(parsed.msg_type, 0x01);
    assert_eq!(parsed.checksum, 0xABCD);
}

#[test]
#[cfg(feature = "bits")]
fn test_enum_with_bit_fields() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8", bits = 2)]
    enum BitEnum {
        #[deku(id = 0)]
        A {
            #[deku(bits = 6)]
            val: u8,
        },
        #[deku(id = 1)]
        B {
            #[deku(bits = 3)]
            x: u8,
            #[deku(bits = 3)]
            y: u8,
        },
    }

    assert_eq!(BitEnum::SIZE_BYTES, Some(1));
}

#[test]
fn test_enum_uses_max_variant_size() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum MixedSizeEnum {
        #[deku(id = "1")]
        Small { a: u8 },

        #[deku(id = "2")]
        Medium { b: u16 },

        #[deku(id = "3")]
        Large { c: u64 },
    }

    assert_eq!(MixedSizeEnum::SIZE_BYTES, Some(9));
}

#[cfg(feature = "alloc")]
#[test]
fn test_enum_actual_vs_max_size() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum VarySizes {
        #[deku(id = "1")]
        Tiny { x: u8 },

        #[deku(id = "2")]
        Huge {
            #[deku(endian = "big")]
            a: u64,
            #[deku(endian = "big")]
            b: u64,
            #[deku(endian = "big")]
            c: u32,
        },
    }

    assert_eq!(VarySizes::SIZE_BYTES, Some(21));

    let tiny = VarySizes::Tiny { x: 42 };
    assert_eq!(tiny.to_bytes().unwrap().len(), 2);

    let huge = VarySizes::Huge { a: 1, b: 2, c: 3 };
    assert_eq!(
        huge.to_bytes().unwrap().len(),
        VarySizes::SIZE_BYTES.unwrap()
    );
}

#[cfg(feature = "alloc")]
#[test]
fn test_unit_variants_are_zero_size() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum Status {
        #[deku(id = "0")]
        Idle,

        #[deku(id = "1")]
        Active { pid: u32 },

        #[deku(id = "2")]
        Failed,
    }

    assert_eq!(Status::SIZE_BYTES, Some(5));

    let idle = Status::Idle;
    assert_eq!(idle.to_bytes().unwrap().len(), 1);

    let active = Status::Active { pid: 999 };
    assert_eq!(
        active.to_bytes().unwrap().len(),
        Status::SIZE_BYTES.unwrap()
    );
}

#[test]
fn test_enum_field_is_another_enum() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum InnerEnum {
        #[deku(id = "1")]
        Small { x: u8 },

        #[deku(id = "2")]
        Large { y: u64 },
    }

    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum OuterEnum {
        #[deku(id = "10")]
        Simple { value: u32 },

        #[deku(id = "20")]
        WithInner { inner: InnerEnum },
    }

    assert_eq!(InnerEnum::SIZE_BYTES, Some(9));
    assert_eq!(OuterEnum::SIZE_BYTES, Some(10));
}

#[test]
fn test_deeply_nested_enums() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum Level3 {
        #[deku(id = "1")]
        A { a: u16 },

        #[deku(id = "2")]
        B { b: u32 },
    }

    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum Level2 {
        #[deku(id = "1")]
        X { x: u8 },

        #[deku(id = "2")]
        Y { inner: Level3 },
    }

    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8")]
    enum Level1 {
        #[deku(id = "1")]
        Simple { s: u8 },

        #[deku(id = "2")]
        Nested { n: Level2 },
    }

    assert_eq!(Level3::SIZE_BYTES, Some(5));
    assert_eq!(Level2::SIZE_BYTES, Some(6));
    assert_eq!(Level1::SIZE_BYTES, Some(7));
}

#[test]
fn test_arrays_of_various_sizes() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    struct WithArrays {
        small: [u8; 1],
        medium: [u16; 5],
        large: [u32; 10],
    }

    assert_eq!(WithArrays::SIZE_BYTES, Some(51));
}

#[test]
fn test_nested_arrays() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    struct NestedArrays {
        matrix: [[u8; 4]; 3],
    }

    assert_eq!(NestedArrays::SIZE_BYTES, Some(12));
}

#[test]
fn test_complex_tuples() {
    assert_eq!(<(u8, u16, u32, u64)>::SIZE_BYTES, Some(15));

    assert_eq!(<((u8, u16), (u32, u64))>::SIZE_BYTES, Some(15));

    assert_eq!(<(u8, [u8; 4], u16)>::SIZE_BYTES, Some(7));
}

#[test]
fn test_zero_sized_types() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    struct ZeroSized;

    assert_eq!(ZeroSized::SIZE_BYTES, Some(0));

    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    struct WithZeroSizedField {
        a: u8,
        b: ZeroSized,
        c: u16,
    }

    assert_eq!(WithZeroSizedField::SIZE_BYTES, Some(3));
}

#[test]
#[cfg(feature = "bits")]
fn test_enum_with_bits_discriminant_and_data() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    #[deku(id_type = "u8", bits = 4)]
    enum SmallDiscriminant {
        #[deku(id = "0")]
        A,
        #[deku(id = "1")]
        B { x: u8 },
        #[deku(id = "2")]
        C { y: u16, z: u8 },
    }

    assert_eq!(SmallDiscriminant::SIZE_BITS, 28);
    assert_eq!(SmallDiscriminant::SIZE_BYTES, None);
}

#[test]
fn test_generic_struct_with_deku_size() {
    #[derive(Debug, DekuRead, DekuWrite, DekuSize)]
    struct GenericStruct<T>
    where
        T: DekuSize + for<'a> DekuReader<'a> + DekuWriter,
    {
        value: T,
    }

    assert_eq!(GenericStruct::<u8>::SIZE_BYTES, Some(1));
    assert_eq!(GenericStruct::<u16>::SIZE_BYTES, Some(2));
    assert_eq!(GenericStruct::<u32>::SIZE_BYTES, Some(4));
}

#[test]
fn test_struct_with_top_level_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"DEKU")]
    struct MagicStruct {
        a: u8,
        b: u16,
    }

    assert_eq!(MagicStruct::SIZE_BYTES, Some(7));
    assert_eq!(MagicStruct::SIZE_BITS, 56);
}

#[test]
fn test_enum_with_top_level_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"TEST", id_type = "u8")]
    enum MagicEnum {
        #[deku(id = 0)]
        Small(u8),
        #[deku(id = 1)]
        Large(u32),
    }

    assert_eq!(MagicEnum::SIZE_BYTES, Some(9));
    assert_eq!(MagicEnum::SIZE_BITS, 72);
}

#[test]
fn test_struct_with_field_level_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct FieldMagicStruct {
        #[deku(magic = b"HDR")]
        header: u8,
        data: u16,
    }

    assert_eq!(FieldMagicStruct::SIZE_BYTES, Some(6));
    assert_eq!(FieldMagicStruct::SIZE_BITS, 48);
}

#[test]
fn test_struct_with_both_magic_types() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"TOP")]
    struct BothMagicStruct {
        #[deku(magic = b"FLD")]
        field: u8,
        data: u32,
    }

    assert_eq!(BothMagicStruct::SIZE_BYTES, Some(11));
    assert_eq!(BothMagicStruct::SIZE_BITS, 88);
}

#[test]
#[cfg(feature = "bits")]
fn test_enum_with_magic_and_bit_discriminant() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"AB", id_type = "u8", bits = 4)]
    enum BitMagicEnum {
        #[deku(id = 0)]
        A {
            #[deku(bits = 4)]
            val: u8,
        },
        #[deku(id = 1)]
        B { x: u8 },
    }

    assert_eq!(BitMagicEnum::SIZE_BITS, 28);
    assert_eq!(BitMagicEnum::SIZE_BYTES, None);
}

#[test]
fn test_multiple_field_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct MultiFieldMagic {
        #[deku(magic = b"A")]
        field_a: u8,
        #[deku(magic = b"BB")]
        field_b: u16,
        #[deku(magic = b"CCC")]
        field_c: u32,
    }

    assert_eq!(MultiFieldMagic::SIZE_BYTES, Some(13));
}

#[test]
fn test_empty_struct_with_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"EMPTY")]
    struct EmptyMagicStruct;

    assert_eq!(EmptyMagicStruct::SIZE_BYTES, Some(5));
}

#[test]
fn test_nested_struct_with_magic() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"IN")]
    struct InnerWithMagic {
        value: u16,
    }

    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(magic = b"OUT")]
    struct OuterWithMagic {
        inner: InnerWithMagic,
        footer: u8,
    }

    assert_eq!(InnerWithMagic::SIZE_BYTES, Some(4));
    assert_eq!(OuterWithMagic::SIZE_BYTES, Some(8));
}

#[test]
fn test_struct_with_pad_bytes_before() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct PadBeforeStruct {
        field_a: u8,
        #[deku(pad_bytes_before = "2")]
        field_b: u8,
    }

    assert_eq!(PadBeforeStruct::SIZE_BYTES, Some(4));
    assert_eq!(PadBeforeStruct::SIZE_BITS, 32);
}

#[test]
fn test_struct_with_pad_bytes_after() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct PadAfterStruct {
        #[deku(pad_bytes_after = "3")]
        field_a: u8,
        field_b: u8,
    }

    assert_eq!(PadAfterStruct::SIZE_BYTES, Some(5));
    assert_eq!(PadAfterStruct::SIZE_BITS, 40);
}

#[test]
fn test_struct_with_pad_bytes_before_and_after() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct PadBothStruct {
        #[deku(pad_bytes_before = "1", pad_bytes_after = "2")]
        field_a: u8,
        field_b: u16,
    }

    assert_eq!(PadBothStruct::SIZE_BYTES, Some(6));
    assert_eq!(PadBothStruct::SIZE_BITS, 48);
}

#[test]
#[cfg(feature = "bits")]
fn test_struct_with_pad_bits_before() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct PadBitsBefore {
        #[deku(bits = 4)]
        field_a: u8,
        #[deku(pad_bits_before = "4")]
        #[deku(bits = 4)]
        field_b: u8,
    }

    assert_eq!(PadBitsBefore::SIZE_BITS, 12);
    assert_eq!(PadBitsBefore::SIZE_BYTES, None);
}

#[test]
#[cfg(feature = "bits")]
fn test_struct_with_pad_bits_after() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct PadBitsAfter {
        #[deku(bits = 3, pad_bits_after = "5")]
        field_a: u8,
        field_b: u8,
    }

    assert_eq!(PadBitsAfter::SIZE_BITS, 16);
    assert_eq!(PadBitsAfter::SIZE_BYTES, Some(2));
}

#[test]
#[cfg(feature = "bits")]
fn test_struct_with_mixed_padding() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    #[deku(endian = "big")]
    struct MixedPadding {
        #[deku(pad_bits_before = "2", pad_bytes_before = "1")]
        #[deku(bits = 6)]
        field_a: u8,
        field_b: u8,
    }

    assert_eq!(MixedPadding::SIZE_BITS, 24);
    assert_eq!(MixedPadding::SIZE_BYTES, Some(3));
}

#[test]
fn test_struct_without_padding() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct NoPaddingStruct {
        field_a: u8,
        field_b: u16,
    }

    assert_eq!(NoPaddingStruct::SIZE_BYTES, Some(3));
    assert_eq!(NoPaddingStruct::SIZE_BITS, 24);
}

#[test]
fn test_multiple_fields_with_padding() {
    #[derive(DekuRead, DekuWrite, DekuSize)]
    struct MultiplePadding {
        #[deku(pad_bytes_before = "1")]
        field_a: u8,
        #[deku(pad_bytes_after = "2")]
        field_b: u8,
        #[deku(pad_bytes_before = "1", pad_bytes_after = "1")]
        field_c: u8,
    }

    assert_eq!(MultiplePadding::SIZE_BYTES, Some(8));
    assert_eq!(MultiplePadding::SIZE_BITS, 64);
}
