use super::MAGIC;
use crate::v4::{MAX_MESSAGE_SIZE, MagicBuffer};
use std::fmt::Debug;
use tracing::trace;

/// A supertrait that defines everything a custom message
/// needs to be encoded before being sent by a socket.
pub trait Encodable: EncodeMessage + Debug {}

/// Define how your custom Message type encodes
/// itself into the bytes of a DHCP message's fields.
///
/// ...or don't and use our homemade [Message](crate::v4::message::Message)
/// with this already implemented.
///
/// If your type is in a different module you can define
/// [From or Into](https://doc.rust-lang.org/std/convert/index.html)
/// in that module and call that method in one of this trait's `encode_*`
/// methods. See Message's implementation for details.
pub trait EncodeMessage {
    /// Represent any data type as the correct amount of op field bytes.
    fn encode_op(&self) -> u8;
    /// Represent any data type as the correct amount of htype field bytes.
    fn encode_htype(&self) -> u8;
    /// Represent any data type as the correct amount of hlen field bytes.
    fn encode_hlen(&self) -> u8;
    /// Represent any data type as the correct amount of hops field bytes.
    fn encode_hops(&self) -> u8;
    /// Represent any data type as the correct amount of xid field bytes.
    fn encode_xid(&self) -> [u8; 4];
    /// Represent any data type as the correct amount of secs field bytes.
    fn encode_secs(&self) -> [u8; 2];
    /// Represent any data type as the correct amount of flags field bytes.
    fn encode_flags(&self) -> [u8; 2];
    /// Represent any data type as the correct amount of ciaddr field bytes.
    fn encode_ciaddr(&self) -> [u8; 4];
    /// Represent any data type as the correct amount of yiaddr field bytes.
    fn encode_yiaddr(&self) -> [u8; 4];
    /// Represent any data type as the correct amount of siaddr field bytes.
    fn encode_siaddr(&self) -> [u8; 4];
    /// Represent any data type as the correct amount of giaddr field bytes.
    fn encode_giaddr(&self) -> [u8; 4];
    /// Represent any data type as the correct amount of chaddr field bytes.
    fn encode_chaddr(&self) -> [u8; 16];
    /// Represent any data type as the correct amount of sname field bytes.
    fn encode_sname(&self) -> [u8; 64];
    /// Represent any data type as the correct amount of file field bytes.
    fn encode_file(&self) -> [u8; 128];
    /// Represent any data type as a variable amount of options field bytes.
    fn encode_options(&self) -> Vec<u8>;

    /// Automagically defined because its a constant
    fn encode_magic(&self) -> MagicBuffer {
        MAGIC
    }

    /// Encodes a [Message](crate::v4::Message) into a Vec of bytes
    /// by calling the encode_* methods defined in EncodeMessage,
    /// and uses a Encoder to store the output.
    fn to_bytes(&self) -> Vec<u8> {
        trace!("to_bytes");

        let mut encoder = Encoder {
            output: Vec::with_capacity(MAX_MESSAGE_SIZE),
        };

        trace!("encode op");
        encoder.encode_byte(self.encode_op());
        trace!("encode htype");
        encoder.encode_byte(self.encode_htype());
        trace!("encode hlen");
        encoder.encode_byte(self.encode_hlen());
        trace!("encode hops");
        encoder.encode_byte(self.encode_hops());
        trace!("encode xid");
        encoder.encode_bytes(&self.encode_xid());
        trace!("encode secs");
        encoder.encode_bytes(&self.encode_secs());
        trace!("encode flags");
        encoder.encode_bytes(&self.encode_flags());
        trace!("encode ciaddr");
        encoder.encode_bytes(&self.encode_ciaddr());
        trace!("encode yiaddr");
        encoder.encode_bytes(&self.encode_yiaddr());
        trace!("encode siaddr");
        encoder.encode_bytes(&self.encode_siaddr());
        trace!("encode giaddr");
        encoder.encode_bytes(&self.encode_giaddr());
        trace!("encode chaddr");
        encoder.encode_bytes(&self.encode_chaddr());
        trace!("encode sname");
        encoder.encode_bytes(&self.encode_sname());
        trace!("encode file");
        encoder.encode_bytes(&self.encode_file());
        trace!("encode magic");
        encoder.encode_bytes(&self.encode_magic());
        trace!("encode options");
        encoder.encode_bytes(self.encode_options().as_slice());

        // Automatically add the "End" option.
        encoder.encode_byte(255);

        encoder.output
    }
}

/// Keeps track of the state of the output and provides methods to add data to the output.
#[derive(Debug)]
pub struct Encoder {
    // This Vec starts empty and bytes are pushed as values are encoded.
    output: Vec<u8>,
}

// Helpers for serializing in encode methods
impl Encoder {
    #[inline]
    fn encode_bytes(&mut self, v: &[u8]) {
        trace!("encode_bytes");
        self.output.extend_from_slice(v);
    }

    #[inline]
    fn encode_byte(&mut self, v: u8) {
        trace!("encode_byte");
        self.output.push(v);
    }
}

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

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

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

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

        let message = 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(),
        };

        let expected = vec![
            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, 53, 1, 1, 255,
        ];

        assert_eq!(message.to_bytes(), expected);
    }
}
