use crate::v4::UndecodedMessage;
use std::fmt::Debug;

/// A supertrait that defines everything a custom message
/// needs to be decoded after being received by a socket.
pub trait Decodable: DecodeMessage + Debug {}

/// Define how the bytes of a DHCP message's fields
/// decode into your custom Message's types.
///
/// ...or don't and use our homemade [Message](crate::v4::message::Message)
/// with this already implemented.
pub trait DecodeMessage {
    /// The type your custom Message uses to represent the op field.
    type Op;
    /// The type your custom Message uses to represent the htype field.
    type Htype;
    /// The type your custom Message uses to represent the hlen field.
    type Hlen;
    /// The type your custom Message uses to represent the hops field.
    type Hops;
    /// The type your custom Message uses to represent the xid field.
    type Xid;
    /// The type your custom Message uses to represent the secs field.
    type Secs;
    /// The type your custom Message uses to represent the flags field.
    type Flags;
    /// The type your custom Message uses to represent the ciaddr field.
    type Ciaddr;
    /// The type your custom Message uses to represent the yiaddr field.
    type Yiaddr;
    /// The type your custom Message uses to represent the siaddr field.
    type Siaddr;
    /// The type your custom Message uses to represent the giaddr field.
    type Giaddr;
    /// The type your custom Message uses to represent the chaddr field.
    type Chaddr;
    /// The type your custom Message uses to represent the sname field.
    type Sname;
    /// The type your custom Message uses to represent the file field.
    type File;
    /// The type your custom Message uses to represent the magic field.
    type Magic;
    /// The type your custom Message uses to represent the options field.
    type Options;

    /// Your custom Message.
    type Output;

    /// Convert an op field's byte into your custom Message's type.
    fn decode_op(op: &UndecodedMessage) -> Self::Op;
    /// Convert an htype field's byte into your custom Message's type.
    fn decode_htype(htype: &UndecodedMessage) -> Self::Htype;
    /// Convert an hlen field's byte into your custom Message's type.
    fn decode_hlen(hlen: &UndecodedMessage) -> Self::Hlen;
    /// Convert an hops field's byte into your custom Message's type.
    fn decode_hops(hops: &UndecodedMessage) -> Self::Hops;
    /// Convert an xid field's bytes into your custom Message's type.
    fn decode_xid(xid: &UndecodedMessage) -> Self::Xid;
    /// Convert an secs field's bytes into your custom Message's type.
    fn decode_secs(secs: &UndecodedMessage) -> Self::Secs;
    /// Convert an flags field's bytes into your custom Message's type.
    fn decode_flags(flags: &UndecodedMessage) -> Self::Flags;
    /// Convert an ciaddr field's bytes into your custom Message's type.
    fn decode_ciaddr(ciaddr: &UndecodedMessage) -> Self::Ciaddr;
    /// Convert an yiaddr field's bytes into your custom Message's type.
    fn decode_yiaddr(yiaddr: &UndecodedMessage) -> Self::Yiaddr;
    /// Convert an siaddr field's bytes into your custom Message's type.
    fn decode_siaddr(siaddr: &UndecodedMessage) -> Self::Siaddr;
    /// Convert an giaddr field's bytes into your custom Message's type.
    fn decode_giaddr(giaddr: &UndecodedMessage) -> Self::Giaddr;
    /// Convert an chaddr field's bytes into your custom Message's type.
    fn decode_chaddr(chaddr: &UndecodedMessage) -> Self::Chaddr;
    /// Convert an sname field's bytes into your custom Message's type.
    fn decode_sname(sname: &UndecodedMessage) -> Self::Sname;
    /// Convert an file field's bytes into your custom Message's type.
    fn decode_file(file: &UndecodedMessage) -> Self::File;
    /// Convert an magic field's bytes into your custom Message's type.
    fn decode_magic(magic: &UndecodedMessage) -> Self::Magic;
    /// Convert an options field's bytes into your custom Message's type.
    fn decode_options(options: &UndecodedMessage) -> Self::Options;

    /// decodes a byte array into a [Message](crate::v4::Message) or other type that implements decodeMessage.
    fn from_bytes(undecoded: &UndecodedMessage) -> Self::Output;
}

// ------------------------------------------------

#[cfg(test)]
mod tests {
    use std::net::Ipv4Addr;

    use super::*;
    use crate::v4::*;

    #[test]
    fn test_from_bytes() {
        init_logger(true);

        let bytes = Socket::receive_mock(&[
            1, 1, 6, 1, 0, 0, 0, 4, 0, 2, 128, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
            48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
            48, 48, 48, 48, 48, 48, 48, 48, 48, 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
            64, 64, 64, 64, 64, 0, 99, 130, 83, 99, 0, 53, 1, 1, 255,
        ]);
        let test: Message = Message::from_bytes(&bytes);
        let expected = Message {
            op: Ops::Request,
            htype: HTypes::Ethernet,
            hlen: 6,
            hops: 1,
            xid: 4,
            secs: 2,
            flags: Flags { broadcast: true },
            ciaddr: Ipv4Addr::new(1, 1, 1, 1),
            yiaddr: Ipv4Addr::new(1, 1, 1, 1),
            siaddr: Ipv4Addr::new(1, 1, 1, 1),
            giaddr: Ipv4Addr::new(1, 1, 1, 1),
            chaddr: [3; 16],
            sname: SName::new(c"000000000000000000000000000000000000000000000000000000000000000")
                .unwrap(),
            file: File::new(c"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@").unwrap(),
            magic: [99, 130, 83, 99],
            options: vec![
                MessageOptions::MessageType(MessageTypes::Discover),
            ].into(),
        };

        assert_eq!(expected, test);
    }
}
