use crate::v4::{MAX_MESSAGE_SIZE, MagicBuffer, MessageBuffer, OptionsBuffer};
use tracing::trace;

/// UndecodedMessage takes a message's byte array from the socket. It can return smaller slices that
/// can be later decoded into a Message's field via `DecodeMessage`.
///
/// An advantage to using this over `Slicer` is that this doesn't process input from start to end,
/// so you may call its methods in any order and as many times as desired without needing to reset.
#[derive(Debug)]
pub struct UndecodedMessage(MessageBuffer);

impl UndecodedMessage {
    /// `input` must be at least 240 bytes,
    /// where 240 is the sum of all fixed Message field's bytes
    /// and the first 4 ("magic") bytes of the variable options field.
    #[inline]
    pub fn new(buffer: MessageBuffer) -> Self {
        Self(buffer)
    }

    /// Slices 1 byte to be used for the op field
    #[inline]
    pub fn slice_op(&self) -> u8 {
        trace!("slice_op");
        self.0[0] // PANIC: this won't panic because it is always within the fixed-size array.
    }

    /// Slices 1 byte to be used for the htype field
    #[inline]
    pub fn slice_htype(&self) -> u8 {
        trace!("slice_htypes");
        self.0[1] // PANIC: this won't panic because it is always within the fixed-size array.
    }

    /// Slices 1 byte to be used for the hlen field
    #[inline]
    pub fn slice_hlen(&self) -> u8 {
        trace!("slice_hlen");
        self.0[2] // PANIC: this won't panic because it is always within the fixed-size array.
    }

    /// Slices 1 byte to be used for the hops field
    #[inline]
    pub fn slice_hops(&self) -> u8 {
        trace!("slice_hops");
        self.0[3] // PANIC: this won't panic because it is always within the fixed-size array.
    }

    /// Slices 4 bytes to be used for the xid field
    #[inline]
    pub fn slice_xid(&self) -> [u8; 4] {
        trace!("slice_xid");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[4..8].try_into().unwrap()
    }

    /// Slices 2 bytes to be used for the secs field
    #[inline]
    pub fn slice_secs(&self) -> [u8; 2] {
        trace!("slice_secs");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[8..10].try_into().unwrap()
    }

    /// Slices 2 bytes to be used for the flags field
    #[inline]
    pub fn slice_flags(&self) -> [u8; 2] {
        trace!("slice_flags");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[10..12].try_into().unwrap()
    }

    /// Slices the only byte currently used by the flags field.
    ///
    /// Warning: If more bit flags are added via RFC then this may
    /// need to change or be removed.
    #[inline]
    pub fn slice_flags_temporary(&self) -> u8 {
        trace!("slice_flags_temporary");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[10]
    }

    /// Slices 4 bytes to be used for the ciaddr field
    #[inline]
    pub fn slice_ciaddr(&self) -> [u8; 4] {
        trace!("slice_ciaddr");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[12..16].try_into().unwrap()
    }

    /// Slices 4 bytes to be used for the yiaddr field
    #[inline]
    pub fn slice_yiaddr(&self) -> [u8; 4] {
        trace!("slice_yiaddr");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[16..20].try_into().unwrap()
    }

    /// Slices 4 bytes to be used for the siaddr field
    #[inline]
    pub fn slice_siaddr(&self) -> [u8; 4] {
        trace!("slice_siaddr");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[20..24].try_into().unwrap()
    }

    /// Slices 4 bytes to be used for the giaddr field
    #[inline]
    pub fn slice_giaddr(&self) -> [u8; 4] {
        trace!("slice_giaddr");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[24..28].try_into().unwrap()
    }

    /// Slices 16 bytes to be used for the chaddr field
    #[inline]
    pub fn slice_chaddr(&self) -> [u8; 16] {
        trace!("slice_chaddr");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[28..44].try_into().unwrap()
    }

    /// Slices 64 bytes to be used for the sname field
    #[inline]
    pub fn slice_sname(&self) -> [u8; 64] {
        trace!("slice_sname");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[44..108].try_into().unwrap()
    }

    /// Slices 128 bytes to be used for the file field
    #[inline]
    pub fn slice_file(&self) -> [u8; 128] {
        trace!("slice_file");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[108..236].try_into().unwrap()
    }

    /// Slices 4 bytes to be used as the magic field
    #[inline]
    pub fn slice_magic(&self) -> MagicBuffer {
        trace!("slice_magic");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[236..240].try_into().unwrap()
    }

    /// Slices remaining bytes in the input to be used as the options field
    #[inline]
    pub fn slice_options(&self) -> OptionsBuffer {
        trace!("slice_options");

        // PANIC: this won't panic because it is always within the fixed-size array.
        self.0[240..MAX_MESSAGE_SIZE].try_into().unwrap()
    }
}
