//! Implementation of [RFC 6282] which specifies a compression format for IPv6 datagrams over
//! IEEE802.154-based networks.
//!
//! [RFC 6282]: https://datatracker.ietf.org/doc/html/rfc6282

use super::{Error, Result};
use crate::wire::ieee802154::Address as LlAddress;
use crate::wire::ipv6;
use crate::wire::IpProtocol;

pub mod frag;
pub mod iphc;
pub mod nhc;

const ADDRESS_CONTEXT_LENGTH: usize = 8;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AddressContext(pub [u8; ADDRESS_CONTEXT_LENGTH]);

/// The representation of an unresolved address. 6LoWPAN compression of IPv6 addresses can be with
/// and without context information. The decompression with context information is not yet
/// implemented.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UnresolvedAddress<'a> {
    WithoutContext(AddressMode<'a>),
    WithContext((usize, AddressMode<'a>)),
    Reserved,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddressMode<'a> {
    /// The full address is carried in-line.
    FullInline(&'a [u8]),
    /// The first 64-bits of the address are elided. The value of those bits
    /// is the link-local prefix padded with zeros. The remaining 64 bits are
    /// carried in-line.
    InLine64bits(&'a [u8]),
    /// The first 112 bits of the address are elided. The value of the first
    /// 64 bits is the link-local prefix padded with zeros. The following 64 bits
    /// are 0000:00ff:fe00:XXXX, where XXXX are the 16 bits carried in-line.
    InLine16bits(&'a [u8]),
    /// The address is fully elided. The first 64 bits of the address are
    /// the link-local prefix padded with zeros. The remaining 64 bits are
    /// computed from the encapsulating header (e.g., 802.15.4 or IPv6 source address)
    /// as specified in Section 3.2.2.
    FullyElided,
    /// The address takes the form ffXX::00XX:XXXX:XXXX
    Multicast48bits(&'a [u8]),
    /// The address takes the form ffXX::00XX:XXXX.
    Multicast32bits(&'a [u8]),
    /// The address takes the form ff02::00XX.
    Multicast8bits(&'a [u8]),
    /// The unspecified address.
    Unspecified,
    NotSupported,
}

const LINK_LOCAL_PREFIX: [u8; 2] = [0xfe, 0x80];
const EUI64_MIDDLE_VALUE: [u8; 2] = [0xff, 0xfe];

impl<'a> UnresolvedAddress<'a> {
    pub fn resolve(
        self,
        ll_address: Option<LlAddress>,
        addr_context: &[AddressContext],
    ) -> Result<ipv6::Address> {
        let mut bytes = [0; 16];

        let copy_context = |index: usize, bytes: &mut [u8]| -> Result<()> {
            if index >= addr_context.len() {
                return Err(Error);
            }

            let context = addr_context[index];
            bytes[..ADDRESS_CONTEXT_LENGTH].copy_from_slice(&context.0);

            Ok(())
        };

        match self {
            UnresolvedAddress::WithoutContext(mode) => match mode {
                AddressMode::FullInline(addr) => Ok(ipv6::Address::from_bytes(addr)),
                AddressMode::InLine64bits(inline) => {
                    bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
                    bytes[8..].copy_from_slice(inline);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                AddressMode::InLine16bits(inline) => {
                    bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
                    bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
                    bytes[14..].copy_from_slice(inline);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                AddressMode::FullyElided => {
                    bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
                    match ll_address {
                        Some(LlAddress::Short(ll)) => {
                            bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
                            bytes[14..].copy_from_slice(&ll);
                        }
                        Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
                            Some(addr) => bytes[8..].copy_from_slice(&addr),
                            None => return Err(Error),
                        },
                        Some(LlAddress::Absent) => return Err(Error),
                        None => return Err(Error),
                    }
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                AddressMode::Multicast48bits(inline) => {
                    bytes[0] = 0xff;
                    bytes[1] = inline[0];
                    bytes[11..].copy_from_slice(&inline[1..][..5]);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                AddressMode::Multicast32bits(inline) => {
                    bytes[0] = 0xff;
                    bytes[1] = inline[0];
                    bytes[13..].copy_from_slice(&inline[1..][..3]);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                AddressMode::Multicast8bits(inline) => {
                    bytes[0] = 0xff;
                    bytes[1] = 0x02;
                    bytes[15] = inline[0];
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                _ => Err(Error),
            },
            UnresolvedAddress::WithContext(mode) => match mode {
                (_, AddressMode::Unspecified) => Ok(ipv6::Address::UNSPECIFIED),
                (index, AddressMode::InLine64bits(inline)) => {
                    copy_context(index, &mut bytes[..])?;
                    bytes[16 - inline.len()..].copy_from_slice(inline);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                (index, AddressMode::InLine16bits(inline)) => {
                    copy_context(index, &mut bytes[..])?;
                    bytes[16 - inline.len()..].copy_from_slice(inline);
                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                (index, AddressMode::FullyElided) => {
                    match ll_address {
                        Some(LlAddress::Short(ll)) => {
                            bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
                            bytes[14..].copy_from_slice(&ll);
                        }
                        Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
                            Some(addr) => bytes[8..].copy_from_slice(&addr),
                            None => return Err(Error),
                        },
                        Some(LlAddress::Absent) => return Err(Error),
                        None => return Err(Error),
                    }

                    copy_context(index, &mut bytes[..])?;

                    Ok(ipv6::Address::from_bytes(&bytes[..]))
                }
                _ => Err(Error),
            },
            UnresolvedAddress::Reserved => Err(Error),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SixlowpanPacket {
    FragmentHeader,
    IphcHeader,
}

const DISPATCH_FIRST_FRAGMENT_HEADER: u8 = 0b11000;
const DISPATCH_FRAGMENT_HEADER: u8 = 0b11100;
const DISPATCH_IPHC_HEADER: u8 = 0b011;
const DISPATCH_UDP_HEADER: u8 = 0b11110;
const DISPATCH_EXT_HEADER: u8 = 0b1110;

impl SixlowpanPacket {
    /// Returns the type of the 6LoWPAN header.
    /// This can either be a fragment header or an IPHC header.
    ///
    /// # Errors
    /// Returns `[Error::Unrecognized]` when neither the Fragment Header dispatch or the IPHC
    /// dispatch is recognized.
    pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result<Self> {
        let raw = buffer.as_ref();

        if raw.is_empty() {
            return Err(Error);
        }

        if raw[0] >> 3 == DISPATCH_FIRST_FRAGMENT_HEADER || raw[0] >> 3 == DISPATCH_FRAGMENT_HEADER
        {
            Ok(Self::FragmentHeader)
        } else if raw[0] >> 5 == DISPATCH_IPHC_HEADER {
            Ok(Self::IphcHeader)
        } else {
            Err(Error)
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum NextHeader {
    Compressed,
    Uncompressed(IpProtocol),
}

impl core::fmt::Display for NextHeader {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            NextHeader::Compressed => write!(f, "compressed"),
            NextHeader::Uncompressed(protocol) => write!(f, "{protocol}"),
        }
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for NextHeader {
    fn format(&self, fmt: defmt::Formatter) {
        match self {
            NextHeader::Compressed => defmt::write!(fmt, "compressed"),
            NextHeader::Uncompressed(protocol) => defmt::write!(fmt, "{}", protocol),
        }
    }
}

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

    #[test]
    fn sixlowpan_fragment_emit() {
        let repr = frag::Repr::FirstFragment {
            size: 0xff,
            tag: 0xabcd,
        };
        let buffer = [0u8; 4];
        let mut packet = frag::Packet::new_unchecked(buffer);

        assert_eq!(repr.buffer_len(), 4);
        repr.emit(&mut packet);

        assert_eq!(packet.datagram_size(), 0xff);
        assert_eq!(packet.datagram_tag(), 0xabcd);
        assert_eq!(packet.into_inner(), [0xc0, 0xff, 0xab, 0xcd]);

        let repr = frag::Repr::Fragment {
            size: 0xff,
            tag: 0xabcd,
            offset: 0xcc,
        };
        let buffer = [0u8; 5];
        let mut packet = frag::Packet::new_unchecked(buffer);

        assert_eq!(repr.buffer_len(), 5);
        repr.emit(&mut packet);

        assert_eq!(packet.datagram_size(), 0xff);
        assert_eq!(packet.datagram_tag(), 0xabcd);
        assert_eq!(packet.into_inner(), [0xe0, 0xff, 0xab, 0xcd, 0xcc]);
    }

    #[test]
    fn sixlowpan_three_fragments() {
        use crate::wire::ieee802154::Frame as Ieee802154Frame;
        use crate::wire::ieee802154::Repr as Ieee802154Repr;
        use crate::wire::Ieee802154Address;

        let key = frag::Key {
            ll_src_addr: Ieee802154Address::Extended([50, 147, 130, 47, 40, 8, 62, 217]),
            ll_dst_addr: Ieee802154Address::Extended([26, 11, 66, 66, 66, 66, 66, 66]),
            datagram_size: 307,
            datagram_tag: 63,
        };

        let frame1: &[u8] = &[
            0x41, 0xcc, 0x92, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
            0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xc1, 0x33, 0x00, 0x3f, 0x6e, 0x33, 0x02,
            0x35, 0x3d, 0xf0, 0xd2, 0x5f, 0x1b, 0x39, 0xb4, 0x6b, 0x4c, 0x6f, 0x72, 0x65, 0x6d,
            0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73,
            0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65,
            0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63,
            0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x41, 0x6c, 0x69, 0x71,
            0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64, 0x69, 0x6f, 0x2c, 0x20,
            0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65, 0x6c, 0x20, 0x72,
        ];

        let ieee802154_frame = Ieee802154Frame::new_checked(frame1).unwrap();
        let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();

        let sixlowpan_frame =
            SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();

        let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
            frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
        } else {
            unreachable!()
        };

        assert_eq!(frag.datagram_size(), 307);
        assert_eq!(frag.datagram_tag(), 0x003f);
        assert_eq!(frag.datagram_offset(), 0);

        assert_eq!(frag.get_key(&ieee802154_repr), key);

        let frame2: &[u8] = &[
            0x41, 0xcc, 0x93, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
            0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x11, 0x75, 0x74,
            0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74, 0x72, 0x69, 0x73, 0x74, 0x69,
            0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6e, 0x63, 0x20, 0x65,
            0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65, 0x2e, 0x20, 0x4c, 0x6f, 0x72,
            0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72,
            0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e,
            0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69,
            0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74,
        ];

        let ieee802154_frame = Ieee802154Frame::new_checked(frame2).unwrap();
        let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();

        let sixlowpan_frame =
            SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();

        let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
            frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
        } else {
            unreachable!()
        };

        assert_eq!(frag.datagram_size(), 307);
        assert_eq!(frag.datagram_tag(), 0x003f);
        assert_eq!(frag.datagram_offset(), 136 / 8);

        assert_eq!(frag.get_key(&ieee802154_repr), key);

        let frame3: &[u8] = &[
            0x41, 0xcc, 0x94, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
            0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x1d, 0x2e, 0x20,
            0x41, 0x6c, 0x69, 0x71, 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64,
            0x69, 0x6f, 0x2c, 0x20, 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65,
            0x6c, 0x20, 0x72, 0x75, 0x74, 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74,
            0x72, 0x69, 0x73, 0x74, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e,
            0x75, 0x6e, 0x63, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65,
            0x2e, 0x20, 0x0a,
        ];

        let ieee802154_frame = Ieee802154Frame::new_checked(frame3).unwrap();
        let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();

        let sixlowpan_frame =
            SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();

        let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
            frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
        } else {
            unreachable!()
        };

        assert_eq!(frag.datagram_size(), 307);
        assert_eq!(frag.datagram_tag(), 0x003f);
        assert_eq!(frag.datagram_offset(), 232 / 8);

        assert_eq!(frag.get_key(&ieee802154_repr), key);
    }
}
