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

use std::marker::PhantomData;

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

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
pub struct Foobar {
    a: u8,
    b: u8,
    c: u8,
}

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
pub struct BizBong(u8, u8, pub u8);

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
pub struct PartyInTheFront;

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
#[bin_proto(ctx = ())]
pub struct NamedFieldsWithGenerics<A: BitDecode + BitEncode, D: BitDecode + BitEncode> {
    pub value: A,
    pub del: D,
}

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
#[bin_proto(ctx = Ctx)]
pub struct UnnamedFieldsWithGenerics<
    Ctx,
    A: BitDecode<Ctx> + BitEncode<Ctx>,
    D: BitDecode<Ctx> + BitEncode<Ctx>,
>(A, D, PhantomData<Ctx>);

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
#[bin_proto(ctx = ())]
pub struct StructWithExistingBoundedGenerics<
    A: ::std::fmt::Display + ::std::fmt::Debug + BitDecode + BitEncode,
> {
    foo: A,
}

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
pub struct WithDefault {
    a: u8,
    #[bin_proto(skip_decode)]
    b: u8,
}

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
#[bin_proto(pad_before = 1, pad_after = 12)]
pub struct Padded {
    a: u8,
    #[bin_proto(pad_before = 4, pad_after = 8)]
    b: u8,
    c: u8,
}

#[derive(BitDecode, BitEncode, Debug, PartialEq, Eq)]
#[bin_proto(magic = &[0x09u8])]
pub struct Magic {
    a: u8,
    #[bin_proto(magic = b"\x01\x02\x03")]
    b: u8,
}

#[test]
fn named_fields_are_correctly_written() {
    assert_eq!(
        vec![3, '2' as u8, 1],
        Foobar {
            a: 3,
            b: '2' as u8,
            c: 1,
        }
        .encode_bytes(BigEndian)
        .unwrap()
    );
}

#[test]
fn named_fields_are_correctly_decoded() {
    assert_eq!(
        (
            Foobar {
                a: 3,
                b: '2' as u8,
                c: 1,
            },
            24
        ),
        Foobar::decode_bytes(&[3, '2' as u8, 1], BigEndian).unwrap()
    );
}

#[test]
fn unnamed_fields_are_correctly_written() {
    assert_eq!(
        vec![6, 1, 9],
        BizBong(6, 1, 9).encode_bytes(BigEndian).unwrap()
    );
}

#[test]
fn unnamed_fields_are_correctly_decoded() {
    assert_eq!(
        (BizBong(3, 1, 7), 24),
        BizBong::decode_bytes(&[3, 1, 7], BigEndian).unwrap()
    );
}

#[test]
fn unit_structs_are_correctly_written() {
    assert_eq!(PartyInTheFront.encode_bytes(BigEndian).unwrap(), &[]);
}

#[test]
fn unit_structs_are_correctly_decoded() {
    assert_eq!(
        (PartyInTheFront, 0),
        PartyInTheFront::decode_bytes(&[], BigEndian).unwrap()
    );
}

#[test]
fn default_written_correctly() {
    assert_eq!(
        vec![1, 2],
        WithDefault { a: 1, b: 2 }.encode_bytes(BigEndian).unwrap()
    )
}

#[test]
fn default_read_correctly() {
    assert_eq!(
        (
            WithDefault {
                a: 1,
                b: Default::default()
            },
            8
        ),
        WithDefault::decode_bytes(&[1], BigEndian).unwrap()
    )
}

#[test]
fn pad_written_correctly() {
    assert_eq!(
        vec![0, 128, 16, 0, 24, 0, 0],
        Padded { a: 1, b: 2, c: 3 }.encode_bytes(BigEndian).unwrap()
    )
}

#[test]
fn pad_read_correctly() {
    assert_eq!(
        (Padded { a: 1, b: 2, c: 3 }, 49),
        Padded::decode_bytes(&[0, 128, 16, 0, 24, 0, 0], BigEndian).unwrap()
    )
}

#[test]
fn magic_written_correctly() {
    assert_eq!(
        vec![9, 4, 1, 2, 3, 5],
        Magic { a: 4, b: 5 }.encode_bytes(BigEndian).unwrap()
    )
}

#[test]
fn magic_read_correctly() {
    assert_eq!(
        (Magic { a: 4, b: 5 }, 48),
        Magic::decode_bytes(&[9, 4, 1, 2, 3, 5], BigEndian).unwrap()
    )
}

#[test]
fn incorrect_magic_fails() {
    assert!(matches!(
        &Magic::decode_bytes(&[10, 4, 1, 2, 3, 5], BigEndian),
        Err(Error::Magic(&[9]))
    ));
}

#[test]
fn ipv4() {
    #[derive(Debug, BitDecode, BitEncode, PartialEq, Eq)]
    struct IPv4Header {
        #[bin_proto(bits = 4)]
        version: u8,
    }

    assert_eq!(
        IPv4Header::decode_bytes(&[0x45], BigEndian).unwrap(),
        (IPv4Header { version: 4 }, 4)
    )
}
