mod address;
mod address_list;
mod file;
mod max_message;
mod mtypes;
mod opaque;
mod overload_options;
mod sname;
mod string;
mod time;
mod time_offset;

pub use self::address::*;
pub use self::address_list::*;
pub use self::file::*;
pub use self::max_message::*;
pub use self::mtypes::*;
pub use self::opaque::*;
pub use self::overload_options::*;
pub use self::sname::*;
pub use self::string::*;
pub use self::time::*;
pub use self::time_offset::*;

use super::super::Slicer;
use serde::{Deserialize, Serialize};
use tracing::{trace, warn};

/// The amount of bytes in a message used for the magic portion of the options field.
pub const MAGIC_SIZE: usize = 4;
/// The amount of bytes in a message used for the options field minus the magic bytes.
pub const NON_MAGIC_SIZE: usize = 308; // aside: does the word "magic" have an antonym?

/// "A DHCP client must be prepared to receive DHCP messages with an options
/// field of at least length 312 octets"
pub const MIN_OPTIONS_SIZE: usize = MAGIC_SIZE + NON_MAGIC_SIZE;

/// A type alias for an array of magic bytes
pub type MagicBuffer = [u8; MAGIC_SIZE];

/// The first four octets of options field with values 99, 130, 83, 99
pub const MAGIC: MagicBuffer = [99, 130, 83, 99];

/// A type alias for an array of option bytes (minus magic)
pub type OptionsBuffer = [u8; NON_MAGIC_SIZE];

/// Variants of an options field in [Message](crate::v4::Message).
/// Options are also referred to as "configuration parameters".
///
/// Most options are documented in [RFC-2132](https://datatracker.ietf.org/doc/html/rfc2132) unless noted otherwise.
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub enum MessageOptions {
    /// 0
    Pad,
    /// 1
    Netmask(AddressOption),
    /// 2, Deprecated by options 100 and 101 in [RFC-4833](https://datatracker.ietf.org/doc/html/rfc4833)
    TimeOffset(TimeOffset),
    /// 3
    Router(AddressListOption),
    /// 6
    DnsServer(AddressListOption),
    /// 12
    /// See RFC 1035 for character set restrictions.
    HostName(StringOption),
    /// 15
    DomainName(StringOption),
    /// 28
    Broadcast(AddressOption),
    // /// 43
    // VendorClassOpt(Vec<u8>), // TODO this is a subformat in a subformat
    /// 50
    RequestedIp(AddressOption),
    /// 51
    ///
    /// Number of seconds an ip assignment is valid for.
    /// A client may use this option in a Discover or Request MessageType.
    /// A server may use this option in a Offer MessageType.
    LeaseTime(TimeOption),
    /// 52
    Overload(OverloadOptions),
    /// 53
    MessageType(MessageTypes),
    /// 54
    ServerIdentifier(AddressOption),
    /// 55
    RequestedOptions(Vec<u8>),
    /// 56
    Message(StringOption),
    /// 57
    MaxMessage(MaxMessage),
    /// 58
    T1(TimeOption),
    /// 59
    T2(TimeOption),
    /// 60
    VendorId(OpaqueOption),
    /// 61
    ClientId(OpaqueOption),
    /// 66, variable length unlike the fixed Message field
    SName(SNameOption),
    /// 67, variable length unlike the fixed Message field
    FileName(FileOption),
    /// 80, [RFC-4039](https://datatracker.ietf.org/doc/html/rfc4039)
    ///
    /// Used to assign an IP through a faster 2-message (Discover-Ack) exchange
    /// instead of the traditional 4-message (Discover-Offer-Request-Ack) exchange.
    ///
    /// Warning: A client broadcasts its Discover message, so multiple DHCP servers
    /// on the same subnet might receive it. In such a scenario:
    /// - A 4-message exchange Requests an IP address from one of the DHCP servers.
    /// - A 2-message exchange would commit an IP address from each DHCP server.
    ///
    /// A client may use this option only in a Discover MessageType.
    /// A server may use this option only in an Ack MessageType.
    RapidCommit,
    /// 118, [RFC-3011](https://datatracker.ietf.org/doc/rfc3011/)
    SubnetSelect(AddressOption),
    /// 255
    End,
    /// Unknown option
    Other(u8, u8, Vec<u8>),
}

impl MessageOptions {
    /// Converts a MessageOption's enum to a u8
    /// that describes the "T" (tag) in "TLV" (tag-length-value)
    pub fn to_tag(&self) -> u8 {
        match self {
            Self::Pad => 0,
            Self::Netmask(_) => 1,
            Self::TimeOffset(_) => 2,
            Self::Router(_) => 3,
            Self::DnsServer(_) => 6,
            Self::HostName(_) => 12,
            Self::DomainName(_) => 15,
            Self::Broadcast(_) => 28,
            Self::RequestedIp(_) => 50,
            Self::LeaseTime(_) => 51,
            Self::Overload(_) => 52,
            Self::MessageType(_) => 53,
            Self::ServerIdentifier(_) => 54,
            Self::RequestedOptions(_) => 55,
            Self::Message(_) => 56,
            Self::MaxMessage(_) => 57,
            Self::T1(_) => 58,
            Self::T2(_) => 59,
            Self::VendorId(_) => 60,
            Self::ClientId(_) => 61,
            Self::SName(_) => 66,
            Self::FileName(_) => 67,
            Self::RapidCommit => 80,
            Self::SubnetSelect(_) => 118,
            Self::End => 255,
            Self::Other(x, _, _) => *x,
        }
    }

    /// Convert tag number to enum for options with a value.
    /// Pad, End, and bools like RapidCommit can be handled specially.
    fn from_tag_value(tag: u8, value: &[u8]) -> Self {
        match tag {
            1 => Self::Netmask(value.into()),
            2 => Self::TimeOffset(value.into()),
            3 => Self::Router(value.into()),
            6 => Self::DnsServer(value.into()),
            12 => Self::HostName(value.into()),
            15 => Self::DomainName(value.into()),
            28 => Self::Broadcast(value.into()),
            50 => Self::RequestedIp(value.into()),
            51 => Self::LeaseTime(value.into()),
            52 => Self::Overload(value.into()),
            53 => Self::MessageType(MessageTypes::from(value)),
            54 => Self::ServerIdentifier(value.into()),
            55 => Self::RequestedOptions(value.into()),
            56 => Self::Message(value.into()),
            57 => Self::MaxMessage(value.into()),
            58 => Self::T1(value.into()),
            59 => Self::T2(value.into()),
            60 => Self::VendorId(value.into()),
            61 => Self::ClientId(value.into()),
            66 => Self::SName(value.into()),
            67 => Self::FileName(value.into()),
            118 => Self::SubnetSelect(value.into()),
            _ => Self::Other(tag, value.len() as u8, value.into()),
        }
    }

    /// for use when encoding a collection of options into bytes
    #[inline]
    pub fn extend_into(&self, bytes: &mut Vec<u8>) {
        match self {
            Self::Pad => bytes.push(0),
            Self::End => {
                warn!("The End option is automatically handled during encode/decode. Don't use it.")
            }
            Self::RapidCommit => bytes.push(80),
            Self::LeaseTime(x) => x.extend_into(bytes, 51),
            Self::T1(x) => x.extend_into(bytes, 58),
            Self::T2(x) => x.extend_into(bytes, 59),
            Self::RequestedIp(x) => x.extend_into(bytes, 50),
            Self::ServerIdentifier(x) => x.extend_into(bytes, 54),
            Self::MessageType(x) => x.extend_into(bytes, 53),
            Self::Netmask(x) => x.extend_into(bytes, 1),
            Self::Broadcast(x) => x.extend_into(bytes, 28),
            Self::Overload(x) => x.extend_into(bytes, 52),
            Self::SName(x) => x.extend_into(bytes, 66),
            Self::FileName(x) => x.extend_into(bytes, 67),
            Self::Router(x) => x.extend_into(bytes, 3),
            Self::DnsServer(x) => x.extend_into(bytes, 6),
            Self::MaxMessage(x) => x.extend_into(bytes, 57),
            Self::HostName(x) => x.extend_into(bytes, 12),
            Self::DomainName(x) => x.extend_into(bytes, 15),
            Self::Message(x) => x.extend_into(bytes, 56),
            Self::SubnetSelect(x) => x.extend_into(bytes, 118),
            Self::VendorId(x) => x.extend_into(bytes, 60),
            Self::ClientId(x) => x.extend_into(bytes, 61),
            Self::TimeOffset(x) => x.extend_into(bytes, 2),

            Self::RequestedOptions(v) => {
                bytes.push(55);
                bytes.push(v.len() as u8);
                bytes.extend(v.iter());
            }

            Self::Other(t, l, v) => {
                bytes.push(*t);
                bytes.push(*l);
                bytes.extend(v);
            }
        };
    }
}

/// A collection of `MessageOptions`.
#[derive(Debug, PartialEq)]
pub struct MessageOptionsList(Vec<MessageOptions>);

impl MessageOptionsList {
    /// Returns the `MessageOptions` that has the passed tag within the `MessageOptionsList` if it exists.
    #[inline]
    pub fn find_option(&self, tag: u8) -> Option<&MessageOptions> {
        self.0.iter().find(|&option| option.to_tag() == tag)
    }

    /// Appends a `MessageOptions` onto the `MessageOptionsList`.
    #[inline]
    pub fn add_option(&mut self, option: MessageOptions) {
        self.0.push(option);
    }

    /// Returns the `MessageOptions` with the passed `index` in the list.
    /// WARNING: be careful using this as it might not always be available
    #[deprecated]
    pub fn get(&self, index: usize) -> Option<&MessageOptions> {
        self.0.get(index)
    }

    /// Changes the `MessageOptions` at the passed `index` in the list.
    /// WARNING: be careful using this as it might not always be available
    #[deprecated]
    pub fn set(&mut self, index: usize, option: MessageOptions) {
        self.0[index] = option;
    }

    /// Takes an array slice and parses the TLV sub-format
    #[inline]
    pub fn from_bytes(bytes: &[u8]) -> Self {
        let mut slicer = Slicer::new(bytes);
        let mut options: Vec<MessageOptions> = Vec::with_capacity(NON_MAGIC_SIZE);

        // TODO: https://datatracker.ietf.org/doc/rfc3396/
        while let Some(tag) = slicer.slice(1) {
            trace!("parsing option {:?}", tag);
            match tag[0] {
                0 => {} // discard Pad bytes
                80 => {
                    options.push(MessageOptions::RapidCommit);
                }
                255 => {
                    // no length to parse
                    break; // End must be the last option
                }
                tag_with_length => {
                    // must parse length
                    match slicer.slice(1) {
                        Some(length) => match slicer.slice(length[0] as usize) {
                            Some(value) => {
                                options.push(MessageOptions::from_tag_value(tag_with_length, value))
                            }
                            None => break, // no more bytes (invalid option, length without value)
                        },
                        None => break, // no more bytes (invalid option, tag without length)
                    }
                }
            }
        }

        Self(options)
    }
}

impl From<Vec<MessageOptions>> for MessageOptionsList {
    fn from(options: Vec<MessageOptions>) -> Self {
        Self(options)
    }
}

impl From<&MessageOptionsList> for Vec<u8> {
    #[inline]
    fn from(list: &MessageOptionsList) -> Self {
        let mut encoded = Vec::with_capacity(MIN_OPTIONS_SIZE);

        list.0
            .iter()
            .for_each(|option| option.extend_into(&mut encoded));

        encoded
    }
}
