// SPDX-License-Identifier: MIT

use anyhow::Context;
use byteorder::{ByteOrder, NativeEndian};
use std::mem::size_of;

use crate::constants::*;

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

#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum Metrics {
    Unspec(Vec<u8>),
    Lock(u32),
    Mtu(u32),
    Window(u32),
    Rtt(u32),
    RttVar(u32),
    SsThresh(u32),
    Cwnd(u32),
    Advmss(u32),
    Reordering(u32),
    Hoplimit(u32),
    InitCwnd(u32),
    Features(u32),
    RtoMin(u32),
    InitRwnd(u32),
    QuickAck(u32),
    CcAlgo(u32),
    FastopenNoCookie(u32),
    Other(DefaultNla),
}

impl Nla for Metrics {
    #[rustfmt::skip]
    fn value_len(&self) -> usize {
        use self::Metrics::*;
        match *self {
            Unspec(ref bytes) => bytes.len(),
            Lock(_)
                | Mtu(_)
                | Window(_)
                | Rtt(_)
                | RttVar(_)
                | SsThresh(_)
                | Cwnd(_)
                | Advmss(_)
                | Reordering(_)
                | Hoplimit(_)
                | InitCwnd(_)
                | Features(_)
                | RtoMin(_)
                | InitRwnd(_)
                | QuickAck(_)
                | CcAlgo(_)
                | FastopenNoCookie(_)
                => size_of::<u32>(),
            Other(ref attr) => attr.value_len(),
        }
    }

    #[rustfmt::skip]
    fn emit_value(&self, buffer: &mut [u8]) {
        use self::Metrics::*;
        match *self {
            Unspec(ref bytes) => buffer.copy_from_slice(bytes.as_slice()),

            Lock(value)
                | Mtu(value)
                | Window(value)
                | Rtt(value)
                | RttVar(value)
                | SsThresh(value)
                | Cwnd(value)
                | Advmss(value)
                | Reordering(value)
                | Hoplimit(value)
                | InitCwnd(value)
                | Features(value)
                | RtoMin(value)
                | InitRwnd(value)
                | QuickAck(value)
                | CcAlgo(value)
                | FastopenNoCookie(value)
                => NativeEndian::write_u32(buffer, value),

            Other(ref attr) => attr.emit_value(buffer),
        }
    }

    fn kind(&self) -> u16 {
        use self::Metrics::*;
        match *self {
            Unspec(_) => RTAX_UNSPEC,
            Lock(_) => RTAX_LOCK,
            Mtu(_) => RTAX_MTU,
            Window(_) => RTAX_WINDOW,
            Rtt(_) => RTAX_RTT,
            RttVar(_) => RTAX_RTTVAR,
            SsThresh(_) => RTAX_SSTHRESH,
            Cwnd(_) => RTAX_CWND,
            Advmss(_) => RTAX_ADVMSS,
            Reordering(_) => RTAX_REORDERING,
            Hoplimit(_) => RTAX_HOPLIMIT,
            InitCwnd(_) => RTAX_INITCWND,
            Features(_) => RTAX_FEATURES,
            RtoMin(_) => RTAX_RTO_MIN,
            InitRwnd(_) => RTAX_INITRWND,
            QuickAck(_) => RTAX_QUICKACK,
            CcAlgo(_) => RTAX_CC_ALGO,
            FastopenNoCookie(_) => RTAX_FASTOPEN_NO_COOKIE,
            Other(ref attr) => attr.kind(),
        }
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for Metrics {
    fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
        use self::Metrics::*;
        let payload = buf.value();
        Ok(match buf.kind() {
            RTAX_UNSPEC => Unspec(payload.to_vec()),
            RTAX_LOCK => {
                Lock(parse_u32(payload).context("invalid RTAX_LOCK value")?)
            }
            RTAX_MTU => {
                Mtu(parse_u32(payload).context("invalid RTAX_MTU value")?)
            }
            RTAX_WINDOW => {
                Window(parse_u32(payload).context("invalid RTAX_WINDOW value")?)
            }
            RTAX_RTT => {
                Rtt(parse_u32(payload).context("invalid RTAX_RTT value")?)
            }
            RTAX_RTTVAR => {
                RttVar(parse_u32(payload).context("invalid RTAX_RTTVAR value")?)
            }
            RTAX_SSTHRESH => SsThresh(
                parse_u32(payload).context("invalid RTAX_SSTHRESH value")?,
            ),
            RTAX_CWND => {
                Cwnd(parse_u32(payload).context("invalid RTAX_CWND value")?)
            }
            RTAX_ADVMSS => {
                Advmss(parse_u32(payload).context("invalid RTAX_ADVMSS value")?)
            }
            RTAX_REORDERING => Reordering(
                parse_u32(payload).context("invalid RTAX_REORDERING value")?,
            ),
            RTAX_HOPLIMIT => Hoplimit(
                parse_u32(payload).context("invalid RTAX_HOPLIMIT value")?,
            ),
            RTAX_INITCWND => InitCwnd(
                parse_u32(payload).context("invalid RTAX_INITCWND value")?,
            ),
            RTAX_FEATURES => Features(
                parse_u32(payload).context("invalid RTAX_FEATURES value")?,
            ),
            RTAX_RTO_MIN => RtoMin(
                parse_u32(payload).context("invalid RTAX_RTO_MIN value")?,
            ),
            RTAX_INITRWND => InitRwnd(
                parse_u32(payload).context("invalid RTAX_INITRWND value")?,
            ),
            RTAX_QUICKACK => QuickAck(
                parse_u32(payload).context("invalid RTAX_QUICKACK value")?,
            ),
            RTAX_CC_ALGO => CcAlgo(
                parse_u32(payload).context("invalid RTAX_CC_ALGO value")?,
            ),
            RTAX_FASTOPEN_NO_COOKIE => FastopenNoCookie(
                parse_u32(payload)
                    .context("invalid RTAX_FASTOPEN_NO_COOKIE value")?,
            ),
            _ => Other(
                DefaultNla::parse(buf)
                    .context("invalid NLA value (unknown type) value")?,
            ),
        })
    }
}
