// SPDX-License-Identifier: MIT

use netlink_packet_core::{
    emit_u32, parse_u32, parse_u8, DecodeError, DefaultNla, Emitable,
    ErrorContext, Nla, NlaBuffer, NlasIterator, Parseable, NLA_F_NESTED,
};

use crate::{EthtoolAttr, EthtoolHeader};

const ETHTOOL_A_COALESCE_HEADER: u16 = 1;
const ETHTOOL_A_COALESCE_RX_USECS: u16 = 2;
const ETHTOOL_A_COALESCE_RX_MAX_FRAMES: u16 = 3;
const ETHTOOL_A_COALESCE_RX_USECS_IRQ: u16 = 4;
const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ: u16 = 5;
const ETHTOOL_A_COALESCE_TX_USECS: u16 = 6;
const ETHTOOL_A_COALESCE_TX_MAX_FRAMES: u16 = 7;
const ETHTOOL_A_COALESCE_TX_USECS_IRQ: u16 = 8;
const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ: u16 = 9;
const ETHTOOL_A_COALESCE_STATS_BLOCK_USECS: u16 = 10;
const ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX: u16 = 11;
const ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX: u16 = 12;
const ETHTOOL_A_COALESCE_PKT_RATE_LOW: u16 = 13;
const ETHTOOL_A_COALESCE_RX_USECS_LOW: u16 = 14;
const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW: u16 = 15;
const ETHTOOL_A_COALESCE_TX_USECS_LOW: u16 = 16;
const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW: u16 = 17;
const ETHTOOL_A_COALESCE_PKT_RATE_HIGH: u16 = 18;
const ETHTOOL_A_COALESCE_RX_USECS_HIGH: u16 = 19;
const ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH: u16 = 20;
const ETHTOOL_A_COALESCE_TX_USECS_HIGH: u16 = 21;
const ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH: u16 = 22;
const ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL: u16 = 23;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum EthtoolCoalesceAttr {
    Header(Vec<EthtoolHeader>),
    RxUsecs(u32),
    RxMaxFrames(u32),
    RxUsecsIrq(u32),
    RxMaxFramesIrq(u32),
    TxUsecs(u32),
    TxMaxFrames(u32),
    TxUsecsIrq(u32),
    TxMaxFramesIrq(u32),
    StatsBlockUsecs(u32),
    UseAdaptiveRx(bool),
    UseAdaptiveTx(bool),
    PktRateLow(u32),
    RxUsecsLow(u32),
    RxMaxFramesLow(u32),
    TxUsecsLow(u32),
    TxMaxFramesLow(u32),
    PktRateHigh(u32),
    RxUsecsHigh(u32),
    RxMaxFramesHigh(u32),
    TxUsecsHigh(u32),
    TxMaxFramesHigh(u32),
    RateSampleInterval(u32),
    Other(DefaultNla),
}

impl Nla for EthtoolCoalesceAttr {
    fn value_len(&self) -> usize {
        match self {
            Self::Header(hdrs) => hdrs.as_slice().buffer_len(),
            Self::RxUsecs(_)
            | Self::RxMaxFrames(_)
            | Self::RxUsecsIrq(_)
            | Self::RxMaxFramesIrq(_)
            | Self::TxUsecs(_)
            | Self::TxMaxFrames(_)
            | Self::TxUsecsIrq(_)
            | Self::TxMaxFramesIrq(_)
            | Self::StatsBlockUsecs(_)
            | Self::PktRateLow(_)
            | Self::RxUsecsLow(_)
            | Self::RxMaxFramesLow(_)
            | Self::TxUsecsLow(_)
            | Self::TxMaxFramesLow(_)
            | Self::PktRateHigh(_)
            | Self::RxUsecsHigh(_)
            | Self::RxMaxFramesHigh(_)
            | Self::TxUsecsHigh(_)
            | Self::TxMaxFramesHigh(_)
            | Self::RateSampleInterval(_) => 4,
            Self::UseAdaptiveRx(_) | Self::UseAdaptiveTx(_) => 1,
            Self::Other(attr) => attr.value_len(),
        }
    }

    fn kind(&self) -> u16 {
        match self {
            Self::Header(_) => ETHTOOL_A_COALESCE_HEADER | NLA_F_NESTED,
            Self::RxUsecs(_) => ETHTOOL_A_COALESCE_RX_USECS,
            Self::RxMaxFrames(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES,
            Self::RxUsecsIrq(_) => ETHTOOL_A_COALESCE_RX_USECS_IRQ,
            Self::RxMaxFramesIrq(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ,
            Self::TxUsecs(_) => ETHTOOL_A_COALESCE_TX_USECS,
            Self::TxMaxFrames(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES,
            Self::TxUsecsIrq(_) => ETHTOOL_A_COALESCE_TX_USECS_IRQ,
            Self::TxMaxFramesIrq(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ,
            Self::StatsBlockUsecs(_) => ETHTOOL_A_COALESCE_STATS_BLOCK_USECS,
            Self::UseAdaptiveRx(_) => ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX,
            Self::UseAdaptiveTx(_) => ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX,
            Self::PktRateLow(_) => ETHTOOL_A_COALESCE_PKT_RATE_LOW,
            Self::RxUsecsLow(_) => ETHTOOL_A_COALESCE_RX_USECS_LOW,
            Self::RxMaxFramesLow(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW,
            Self::TxUsecsLow(_) => ETHTOOL_A_COALESCE_TX_USECS_LOW,
            Self::TxMaxFramesLow(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW,
            Self::PktRateHigh(_) => ETHTOOL_A_COALESCE_PKT_RATE_HIGH,
            Self::RxUsecsHigh(_) => ETHTOOL_A_COALESCE_RX_USECS_HIGH,
            Self::RxMaxFramesHigh(_) => ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH,
            Self::TxUsecsHigh(_) => ETHTOOL_A_COALESCE_TX_USECS_HIGH,
            Self::TxMaxFramesHigh(_) => ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH,
            Self::RateSampleInterval(_) => {
                ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL
            }
            Self::Other(attr) => attr.kind(),
        }
    }

    fn emit_value(&self, buffer: &mut [u8]) {
        match self {
            Self::Header(ref nlas) => nlas.as_slice().emit(buffer),
            Self::Other(ref attr) => attr.emit(buffer),
            Self::RxUsecs(d)
            | Self::RxMaxFrames(d)
            | Self::RxUsecsIrq(d)
            | Self::RxMaxFramesIrq(d)
            | Self::TxUsecs(d)
            | Self::TxMaxFrames(d)
            | Self::TxUsecsIrq(d)
            | Self::TxMaxFramesIrq(d)
            | Self::StatsBlockUsecs(d)
            | Self::PktRateLow(d)
            | Self::RxUsecsLow(d)
            | Self::RxMaxFramesLow(d)
            | Self::TxUsecsLow(d)
            | Self::TxMaxFramesLow(d)
            | Self::PktRateHigh(d)
            | Self::RxUsecsHigh(d)
            | Self::RxMaxFramesHigh(d)
            | Self::TxUsecsHigh(d)
            | Self::TxMaxFramesHigh(d)
            | Self::RateSampleInterval(d) => emit_u32(buffer, *d).unwrap(),
            Self::UseAdaptiveRx(d) | Self::UseAdaptiveTx(d) => {
                buffer[0] = (*d).into()
            }
        }
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>>
    for EthtoolCoalesceAttr
{
    fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
        let payload = buf.value();
        Ok(match buf.kind() {
            ETHTOOL_A_COALESCE_HEADER => {
                let mut nlas = Vec::new();
                let error_msg = "failed to parse coalesce header attributes";
                for nla in NlasIterator::new(payload) {
                    let nla = &nla.context(error_msg)?;
                    let parsed =
                        EthtoolHeader::parse(nla).context(error_msg)?;
                    nlas.push(parsed);
                }
                Self::Header(nlas)
            }
            ETHTOOL_A_COALESCE_RX_USECS => Self::RxUsecs(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_RX_USECS value")?,
            ),
            ETHTOOL_A_COALESCE_RX_MAX_FRAMES => {
                Self::RxMaxFrames(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES value",
                )?)
            }

            ETHTOOL_A_COALESCE_RX_USECS_IRQ => Self::RxUsecsIrq(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_RX_USECS_IRQ value")?,
            ),

            ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ => {
                Self::RxMaxFramesIrq(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ value",
                )?)
            }

            ETHTOOL_A_COALESCE_TX_USECS => Self::TxUsecs(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_TX_USECS value")?,
            ),

            ETHTOOL_A_COALESCE_TX_MAX_FRAMES => {
                Self::TxMaxFrames(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES value",
                )?)
            }

            ETHTOOL_A_COALESCE_TX_USECS_IRQ => Self::TxUsecsIrq(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_TX_USECS_IRQ value")?,
            ),

            ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ => {
                Self::TxMaxFramesIrq(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ value",
                )?)
            }

            ETHTOOL_A_COALESCE_STATS_BLOCK_USECS => {
                Self::StatsBlockUsecs(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_STATS_BLOCK_USECS value",
                )?)
            }

            ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX => Self::UseAdaptiveRx(
                parse_u8(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX value",
                )? == 1,
            ),

            ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX => Self::UseAdaptiveTx(
                parse_u8(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX value",
                )? == 1,
            ),

            ETHTOOL_A_COALESCE_PKT_RATE_LOW => Self::PktRateLow(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_PKT_RATE_LOW value")?,
            ),

            ETHTOOL_A_COALESCE_RX_USECS_LOW => Self::RxUsecsLow(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_RX_USECS_LOW value")?,
            ),

            ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW => {
                Self::RxMaxFramesLow(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW value",
                )?)
            }

            ETHTOOL_A_COALESCE_TX_USECS_LOW => Self::TxUsecsLow(
                parse_u32(payload)
                    .context("Invalid ETHTOOL_A_COALESCE_TX_USECS_LOW value")?,
            ),

            ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW => {
                Self::TxMaxFramesLow(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW value",
                )?)
            }

            ETHTOOL_A_COALESCE_PKT_RATE_HIGH => {
                Self::PktRateHigh(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_PKT_RATE_HIGH value",
                )?)
            }

            ETHTOOL_A_COALESCE_RX_USECS_HIGH => {
                Self::RxUsecsHigh(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RX_USECS_HIGH value",
                )?)
            }

            ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH => {
                Self::RxMaxFramesHigh(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH value",
                )?)
            }

            ETHTOOL_A_COALESCE_TX_USECS_HIGH => {
                Self::TxUsecsHigh(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_TX_USECS_HIGH value",
                )?)
            }

            ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH => {
                Self::TxMaxFramesHigh(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH value",
                )?)
            }

            ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL => {
                Self::RateSampleInterval(parse_u32(payload).context(
                    "Invalid ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL value",
                )?)
            }

            _ => Self::Other(
                DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?,
            ),
        })
    }
}

pub(crate) fn parse_coalesce_nlas(
    buffer: &[u8],
) -> Result<Vec<EthtoolAttr>, DecodeError> {
    let mut nlas = Vec::new();
    for nla in NlasIterator::new(buffer) {
        let error_msg = format!(
            "Failed to parse ethtool coalesce message attribute {nla:?}"
        );
        let nla = &nla.context(error_msg.clone())?;
        let parsed = EthtoolCoalesceAttr::parse(nla).context(error_msg)?;
        nlas.push(EthtoolAttr::Coalesce(parsed));
    }
    Ok(nlas)
}
