//! Types for handling network interface link state information

use core::fmt;

use abi_serde::{
    des::{DeserializeError, Deserializer},
    ser::Serializer,
    Deserialize, Serialize,
};

use crate::primitive_enum;

primitive_enum! {
    /// Defines an Ethernet link speed
    pub enum EthernetSpeed: u8 {
        /// 1Gbps link
        Speed1000 = 3,
        /// 100Mbps link
        Speed100 = 2,
        /// 10Mbps link
        Speed10 = 1,
        /// Link speed not available/unknown
        Unknown = 0,
    }
    [with_serde]
}

primitive_enum! {
    /// Defines whether an Ethernet link is capable of transmiting data both ways simultaneously
    pub enum Duplex: u32 {
        /// Half-duplex mode
        Half = 1,
        /// Full-duplex mode
        Full = 2,
        /// Duplex mode information not available/unknown
        Unknown = 0,
    }
    [with_serde]
}

/// Represents the state of an Ethernet link
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EthernetLinkState {
    /// Link is up, fields indicate the link configuration
    Up(EthernetSpeed, Duplex),
    /// Link is down
    Down,
}

/// Represents the state of network interface's link
#[derive(Debug, Clone, PartialEq)]
pub enum LinkState {
    /// Ethernet link state
    Ethernet(EthernetLinkState),
    /// Any other device, just indicates whether the link is up or down
    Other {
        /// If `true`, the link is up
        up: bool,
    },
}

impl Serialize for EthernetLinkState {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        match self {
            Self::Up(speed, duplex) => {
                serializer.write_u8(0x01)?;
                speed.serialize(serializer)?;
                duplex.serialize(serializer)
            }
            Self::Down => serializer.write_u8(0x00),
        }
    }
}

impl Serialize for LinkState {
    fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
        match self {
            Self::Ethernet(state) => {
                serializer.write_u8(0xEE)?;
                state.serialize(serializer)
            }
            Self::Other { up } => {
                serializer.write_u8(0xFF)?;
                up.serialize(serializer)
            }
        }
    }
}

impl<'de> Deserialize<'de> for EthernetLinkState {
    fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
        match deserializer.read_u8()? {
            0x01 => {
                let speed = EthernetSpeed::deserialize(deserializer)?;
                let duplex = Duplex::deserialize(deserializer)?;
                Ok(Self::Up(speed, duplex))
            }
            0x00 => Ok(Self::Down),
            _ => Err(D::Error::INVALID_ENUM_VARIANT),
        }
    }
}

impl<'de> Deserialize<'de> for LinkState {
    fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
        match deserializer.read_u8()? {
            0xEE => EthernetLinkState::deserialize(deserializer).map(Self::Ethernet),
            0xFF => {
                let up = bool::deserialize(deserializer)?;
                Ok(Self::Other { up })
            }
            _ => Err(D::Error::INVALID_ENUM_VARIANT),
        }
    }
}

impl fmt::Display for EthernetSpeed {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let text = match self {
            Self::Speed10 => "10Mbps",
            Self::Speed100 => "100Mbps",
            Self::Speed1000 => "1Gbps",
            Self::Unknown => "N/A",
        };
        f.write_str(text)
    }
}

impl fmt::Display for Duplex {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let text = match self {
            Self::Half => "half-duplex",
            Self::Full => "full-duplex",
            Self::Unknown => "N/A duplex mode",
        };
        f.write_str(text)
    }
}

impl fmt::Display for EthernetLinkState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Up(speed, duplex) => {
                write!(f, "up, speed {speed}, {duplex}")
            }
            Self::Down => {
                write!(f, "down")
            }
        }
    }
}

impl fmt::Display for LinkState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Other { up: true } => write!(f, "up"),
            Self::Other { up: false } => write!(f, "down"),
            Self::Ethernet(ethernet) => fmt::Display::fmt(ethernet, f),
        }
    }
}
