mod decode;
mod encode;
mod file;
mod flags;
mod helpers;
mod htype;
mod op;
mod options;
mod slicer;
mod sname;
mod socket;
mod undecoded;

pub use self::decode::*;
pub use self::encode::*;
pub use self::file::*;
pub use self::flags::*;
pub use self::helpers::*;
pub use self::htype::*;
pub use self::op::*;
pub use self::options::*;
pub use self::slicer::*;
pub use self::sname::*;
pub use self::socket::*;
pub use self::undecoded::*;

use mac_address::MacAddress;
use std::net::Ipv4Addr;

/// All non-variable (not options) fields add to length 236
pub const MESSAGE_FIXED_FIELDS_SIZE: usize = 236;

/// "A DHCP client must be prepared to receive DHCP messages with an options
/// field of at least length 312 octets"
///
/// All other fields add to length 236, so 236 + 312 = 548
pub const MAX_MESSAGE_SIZE: usize = MESSAGE_FIXED_FIELDS_SIZE + MIN_OPTIONS_SIZE;

/// Wraps all the data sent between DHCP clients and servers.
///
/// Formally defined in [RFC-2131](https://datatracker.ietf.org/doc/html/rfc2131#section-2) as:
///
/// ```text
///                     1                   2                   3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |     op (1)    |   htype (1)   |   hlen (1)    |   hops (1)    |
/// +---------------+---------------+---------------+---------------+
/// |                            xid (4)                            |
/// +-------------------------------+-------------------------------+
/// |           secs (2)            |           flags (2)           |
/// +-------------------------------+-------------------------------+
/// |                          ciaddr  (4)                          |
/// +---------------------------------------------------------------+
/// |                          yiaddr  (4)                          |
/// +---------------------------------------------------------------+
/// |                          siaddr  (4)                          |
/// +---------------------------------------------------------------+
/// |                          giaddr  (4)                          |
/// +---------------------------------------------------------------+
/// |                          chaddr  (16)                         |
/// +---------------------------------------------------------------+
/// |                          sname   (64)                         |
/// +---------------------------------------------------------------+
/// |                          file    (128)                        |
/// +---------------------------------------------------------------+
/// |                          options (variable)                   |
/// +---------------------------------------------------------------+
/// ```
#[derive(Debug, PartialEq)]
pub struct Message {
    /// Message type (not to be confused with a [MessageOptions]'s [MessageTypes])
    pub op: Ops,
    /// Hardware address type
    pub htype: HTypes,
    /// Hardware address length
    pub hlen: u8,
    /// Optionally used by relay agents when booting via a relay agent. Client sets to zero.
    pub hops: u8,
    /// The transaction id. A random number chosen by the client. Used by the client and server to associate messages and responses between a client and a server.
    pub xid: u32,
    /// Seconds elapsed since client started address acquisition/renewal process. Filled in by client.
    pub secs: u16,
    /// The leftmost bit is defined as the Broadcast flag. The remaining bits are reserved for future use, and must be set to zero by clients and ignored by servers and relay agents.
    pub flags: Flags,
    /// Client IP address. Only filled in if the client is in BOUND, RENEW, or REBINDING state and can respond to ARP requests.
    pub ciaddr: Ipv4Addr,
    /// Your IP address
    pub yiaddr: Ipv4Addr,
    /// IP address of the next server to use in bootstrap. Returned in DHCPOFFER, DHCPACK by server.
    pub siaddr: Ipv4Addr,
    /// Relay agent IP address. Used in booting via a relay agent.
    pub giaddr: Ipv4Addr,
    /// Client hardware address
    ///
    /// If using an ethernet hardware type you can get the value for this field with the [mac_address](https://crates.io/crates/mac_address) crate. A [Client](crate::v4::client::Client) will automatically use mac_address.
    pub chaddr: [u8; 16],
    /// Server host name (optional). Null terminated string. 64 bytes in length.
    pub sname: SName,
    /// Boot file name. Null terminated string. Generic name or null in DHCPDISCOVER. Fully qualified directory path name in DHCPOFFER. 128 bytes in length.
    pub file: File,
    /// The first four octets of options field with values 99, 130, 83, 99
    pub magic: MagicBuffer,
    /// A variable length field with a minimum of 312 octets.
    /// Options use [Tag-Length-Value](https://en.wikipedia.org/wiki/Type-length-value) (TLV) encoding, where multi-byte quantities are in network byte order.
    /// The last option must be MessageOptions::End
    pub options: MessageOptionsList,
}

impl Encodable for Message {}
impl Decodable for Message {}

impl MessageHelpers for Message {
    // TODO consider utilizing a HashMap like data structure here.
    fn find_option(&self, tag: u8) -> Option<&MessageOptions> {
        self.options.find_option(tag)
    }

    fn add_option(&mut self, option: MessageOptions) {
        self.options.add_option(option);
    }

    fn get_mac_address(&self) -> MacAddress {
        MacAddress::new([
            self.chaddr[0],
            self.chaddr[1],
            self.chaddr[2],
            self.chaddr[3],
            self.chaddr[4],
            self.chaddr[5],
        ])
    }
}

impl DecodeMessage for Message {
    type Op = Ops;
    type Htype = HTypes;
    type Hlen = u8;
    type Hops = u8;
    type Xid = u32;
    type Secs = u16;
    type Flags = Flags;
    type Ciaddr = Ipv4Addr;
    type Yiaddr = Ipv4Addr;
    type Siaddr = Ipv4Addr;
    type Giaddr = Ipv4Addr;
    type Chaddr = [u8; 16];
    type Sname = SName;
    type File = File;
    type Magic = MagicBuffer;
    type Options = MessageOptionsList;

    type Output = Self;

    fn decode_op(undecoded: &UndecodedMessage) -> Self::Op {
        Self::Op::from(undecoded.slice_op())
    }

    fn decode_htype(undecoded: &UndecodedMessage) -> Self::Htype {
        Self::Htype::from(undecoded.slice_htype())
    }

    fn decode_hlen(undecoded: &UndecodedMessage) -> Self::Hlen {
        undecoded.slice_hlen()
    }

    fn decode_hops(undecoded: &UndecodedMessage) -> Self::Hops {
        undecoded.slice_hops()
    }

    fn decode_xid(undecoded: &UndecodedMessage) -> Self::Xid {
        let bytes = undecoded.slice_xid();
        u32::from_be_bytes(bytes)
    }

    fn decode_secs(undecoded: &UndecodedMessage) -> Self::Secs {
        let bytes = undecoded.slice_secs();
        u16::from_be_bytes(bytes)
    }

    fn decode_flags(undecoded: &UndecodedMessage) -> Self::Flags {
        Self::Flags::from(undecoded.slice_flags_temporary())
    }

    fn decode_ciaddr(undecoded: &UndecodedMessage) -> Self::Ciaddr {
        let bytes = undecoded.slice_ciaddr();
        Ipv4Addr::from(bytes)
    }

    fn decode_yiaddr(undecoded: &UndecodedMessage) -> Self::Yiaddr {
        let bytes = undecoded.slice_yiaddr();
        Ipv4Addr::from(bytes)
    }

    fn decode_siaddr(undecoded: &UndecodedMessage) -> Self::Siaddr {
        let bytes = undecoded.slice_siaddr();
        Ipv4Addr::from(bytes)
    }

    fn decode_giaddr(undecoded: &UndecodedMessage) -> Self::Giaddr {
        let bytes = undecoded.slice_giaddr();
        Ipv4Addr::from(bytes)
    }

    fn decode_chaddr(undecoded: &UndecodedMessage) -> Self::Chaddr {
        undecoded.slice_chaddr()
    }

    fn decode_sname(undecoded: &UndecodedMessage) -> Self::Sname {
        SName::from_array(undecoded.slice_sname())
    }

    fn decode_file(undecoded: &UndecodedMessage) -> Self::File {
        File::from_array(undecoded.slice_file())
    }

    fn decode_magic(_magic: &UndecodedMessage) -> Self::Magic {
        MAGIC
    }

    fn decode_options(undecoded: &UndecodedMessage) -> Self::Options {
        MessageOptionsList::from_bytes(&undecoded.slice_options())
    }

    fn from_bytes(undecoded: &UndecodedMessage) -> Self::Output {
        Message {
            op: Self::decode_op(undecoded),
            htype: Self::decode_htype(undecoded),
            hlen: Self::decode_hlen(undecoded),
            hops: Self::decode_hops(undecoded),
            xid: Self::decode_xid(undecoded),
            secs: Self::decode_secs(undecoded),
            flags: Self::decode_flags(undecoded),
            ciaddr: Self::decode_ciaddr(undecoded),
            yiaddr: Self::decode_yiaddr(undecoded),
            siaddr: Self::decode_siaddr(undecoded),
            giaddr: Self::decode_giaddr(undecoded),
            chaddr: Self::decode_chaddr(undecoded),
            sname: Self::decode_sname(undecoded),
            file: Self::decode_file(undecoded),
            magic: Self::decode_magic(undecoded),
            options: Self::decode_options(undecoded),
        }
    }
}

impl EncodeMessage for Message {
    #[inline]
    fn encode_op(&self) -> u8 {
        (&self.op).into()
    }

    #[inline]
    fn encode_htype(&self) -> u8 {
        (&self.htype).into()
    }

    #[inline]
    fn encode_hlen(&self) -> u8 {
        self.hlen
    }

    #[inline]
    fn encode_hops(&self) -> u8 {
        self.hops
    }

    #[inline]
    fn encode_xid(&self) -> [u8; 4] {
        self.xid.to_be_bytes()
    }

    #[inline]
    fn encode_secs(&self) -> [u8; 2] {
        self.secs.to_be_bytes()
    }

    #[inline]
    fn encode_flags(&self) -> [u8; 2] {
        self.flags.temporary_to_bytes()
    }

    #[inline]
    fn encode_ciaddr(&self) -> [u8; 4] {
        self.ciaddr.octets()
    }

    #[inline]
    fn encode_yiaddr(&self) -> [u8; 4] {
        self.yiaddr.octets()
    }

    #[inline]
    fn encode_siaddr(&self) -> [u8; 4] {
        self.siaddr.octets()
    }

    #[inline]
    fn encode_giaddr(&self) -> [u8; 4] {
        self.giaddr.octets()
    }

    #[inline]
    fn encode_chaddr(&self) -> [u8; 16] {
        self.chaddr
    }

    #[inline]
    fn encode_sname(&self) -> [u8; 64] {
        (&self.sname).into()
    }

    #[inline]
    fn encode_file(&self) -> [u8; 128] {
        (&self.file).into()
    }

    #[inline]
    fn encode_options(&self) -> Vec<u8> {
        (&self.options).into()
    }
}

// ---

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_max_message_size_is_large_enough() {
        // Otherwise other code will not satisfy RFC
        // requirements and may cause panics.
        assert!(MAX_MESSAGE_SIZE >= 548);
    }
}
