// SPDX-License-Identifier: MIT

/// Nat action
///
/// The nat action maps one IP prefix to another
use std::net::Ipv4Addr;

use netlink_packet_utils::{
    nla::{DefaultNla, Nla, NlaBuffer},
    traits::{Emitable, Parseable},
    DecodeError,
};

use super::{
    nat_flag::VecTcNatFlag, TcActionGeneric, TcActionGenericBuffer, TcNatFlag,
};

const TCA_NAT_PARMS: u16 = 1;
const TCA_NAT_TM: u16 = 2;

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct TcActionNat {}

impl TcActionNat {
    pub(crate) const KIND: &'static str = "nat";
}

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum TcActionNatOption {
    Tm(Vec<u8>),
    Parms(TcNat),
    Other(DefaultNla),
}

impl Nla for TcActionNatOption {
    fn value_len(&self) -> usize {
        match self {
            Self::Tm(bytes) => bytes.len(),
            Self::Parms(v) => v.buffer_len(),
            Self::Other(attr) => attr.value_len(),
        }
    }

    fn emit_value(&self, buffer: &mut [u8]) {
        match self {
            Self::Tm(bytes) => buffer.copy_from_slice(bytes.as_slice()),
            Self::Parms(p) => p.emit(buffer),
            Self::Other(attr) => attr.emit_value(buffer),
        }
    }
    fn kind(&self) -> u16 {
        match self {
            Self::Tm(_) => TCA_NAT_TM,
            Self::Parms(_) => TCA_NAT_PARMS,
            Self::Other(nla) => nla.kind(),
        }
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
    for TcActionNatOption
{
    fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
        let payload = buf.value();
        Ok(match buf.kind() {
            TCA_NAT_TM => Self::Tm(payload.to_vec()),
            TCA_NAT_PARMS => {
                Self::Parms(TcNat::parse(&TcNatBuffer::new_checked(payload)?)?)
            }
            _ => Self::Other(DefaultNla::parse(buf)?),
        })
    }
}

const TC_NAT_BUF_LEN: usize = TcActionGeneric::BUF_LEN + 16;

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub struct TcNat {
    pub generic: TcActionGeneric,
    pub old_addr: Ipv4Addr,
    pub new_addr: Ipv4Addr,
    pub mask: Ipv4Addr,
    pub flags: Vec<TcNatFlag>,
}

impl Default for TcNat {
    fn default() -> Self {
        Self {
            generic: TcActionGeneric::default(),
            old_addr: Ipv4Addr::UNSPECIFIED,
            new_addr: Ipv4Addr::UNSPECIFIED,
            mask: Ipv4Addr::UNSPECIFIED,
            flags: vec![],
        }
    }
}

buffer!(TcNatBuffer(TC_NAT_BUF_LEN) {
    generic: (slice, 0..TcActionGeneric::BUF_LEN),
    old_addr: (slice, TcActionGeneric::BUF_LEN..(TcActionGeneric::BUF_LEN+4)),
    new_addr: (slice, (TcActionGeneric::BUF_LEN+4)..(TcActionGeneric::BUF_LEN+8)),
    mask: (slice, (TcActionGeneric::BUF_LEN+8)..(TcActionGeneric::BUF_LEN+12)),
    flags: (u32, (TcActionGeneric::BUF_LEN+12)..TC_NAT_BUF_LEN),
});

impl Emitable for TcNat {
    fn buffer_len(&self) -> usize {
        TC_NAT_BUF_LEN
    }

    fn emit(&self, buffer: &mut [u8]) {
        let mut packet = TcNatBuffer::new(buffer);
        self.generic.emit(packet.generic_mut());
        packet
            .old_addr_mut()
            .copy_from_slice(&self.old_addr.octets());
        packet
            .new_addr_mut()
            .copy_from_slice(&self.new_addr.octets());
        packet.mask_mut().copy_from_slice(&self.mask.octets());
        packet.set_flags(u32::from(&VecTcNatFlag(self.flags.to_vec())));
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<TcNatBuffer<&'a T>> for TcNat {
    fn parse(buf: &TcNatBuffer<&T>) -> Result<Self, DecodeError> {
        Ok(Self {
            generic: TcActionGeneric::parse(&TcActionGenericBuffer::new(
                buf.generic(),
            ))?,
            old_addr: parse_ipv4(buf.old_addr())?,
            new_addr: parse_ipv4(buf.new_addr())?,
            mask: parse_ipv4(buf.mask())?,
            flags: VecTcNatFlag::from(buf.flags()).0,
        })
    }
}

fn parse_ipv4(data: &[u8]) -> Result<Ipv4Addr, DecodeError> {
    if data.len() != 4 {
        Err(DecodeError::from(format!(
            "Invalid length of IPv4 Address, expecting 4 bytes, but got {:?}",
            data
        )))
    } else {
        Ok(Ipv4Addr::new(data[0], data[1], data[2], data[3]))
    }
}
