//! Subnetwork address types
use core::{
    fmt,
    net::{IpAddr, Ipv4Addr},
    str::FromStr,
};

use crate::error::Error;

/// Describes an IPv4 subnetwork, e.g. 11.0.0.0/24
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct SubnetV4Addr {
    /// "Root" of the subnetwork, e.g. the "11.0.0.0" part in "11.0.0.0/24"
    pub root: Ipv4Addr,
    /// Number of masked bits, e.g. the "/24" part in "11.0.0.0/24"
    pub mask_bits: u32,
    mask: u32,
}
abi_serde::impl_struct_serde!(SubnetV4Addr: [
    root,
    mask_bits,
    mask
]);

/// Describes an IPv4/IPv6 subnetwork
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SubnetAddr {
    /// IPv4 subnetwork
    V4(SubnetV4Addr),
}
abi_serde::impl_enum1_serde!(SubnetAddr: [
    V4 => 4
]);

// SubnetV4

impl SubnetV4Addr {
    /// "Unspecified" subnetwork address: 0.0.0.0/0
    pub const UNSPECIFIED: Self = Self {
        root: Ipv4Addr::UNSPECIFIED,
        mask_bits: 0,
        mask: 0,
    };

    /// Constructs a subnetwork from its components
    pub fn from_address_mask(address: Ipv4Addr, mask_bits: u8) -> Self {
        let root = u32::from(address);
        let mask = Self::make_mask(mask_bits);
        let root = root & Self::make_root_mask(mask_bits);

        Self {
            root: Ipv4Addr::from(root),
            mask_bits: mask_bits as _,
            mask,
        }
    }

    /// Returns `true` if the subnet contains given address
    pub fn contains(&self, address: &Ipv4Addr) -> bool {
        let root = u32::from(self.root);
        let address = u32::from(*address);

        (address & self.mask) == root
    }

    /// Returns the base address of the subnet
    pub fn base(&self) -> Ipv4Addr {
        self.root
    }

    fn make_root_mask(bits: u8) -> u32 {
        !((1 << (32 - bits)) - 1)
    }

    fn make_mask(bits: u8) -> u32 {
        ((1 << bits) - 1) << (32 - bits)
    }
}

impl fmt::Display for SubnetV4Addr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}/{}", self.root, self.mask_bits)
    }
}

impl FromStr for SubnetV4Addr {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut it = s.split('/');
        let network = it.next().ok_or(Error::InvalidArgument)?;
        let mask_bits = it.next().ok_or(Error::InvalidArgument)?;
        if it.next().is_some() {
            return Err(Error::InvalidArgument);
        }
        let network = Ipv4Addr::from_str(network).map_err(|_| Error::InvalidArgument)?;
        let mask_bits = u8::from_str(mask_bits).map_err(|_| Error::InvalidArgument)?;

        Ok(Self::from_address_mask(network, mask_bits))
    }
}

// Subnet

impl SubnetAddr {
    /// Returns `true` if the subnet contains given address
    pub fn contains(&self, address: &IpAddr) -> bool {
        match (self, address) {
            (Self::V4(subnet), IpAddr::V4(addr)) => subnet.contains(addr),
            (_, _) => false,
        }
    }

    /// Returns the base address of this subnet
    pub fn base(&self) -> IpAddr {
        match self {
            Self::V4(v4) => IpAddr::V4(v4.base()),
        }
    }
}

impl fmt::Display for SubnetAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::V4(subnet) => fmt::Display::fmt(subnet, f),
        }
    }
}

impl FromStr for SubnetAddr {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(v4) = SubnetV4Addr::from_str(s) {
            return Ok(Self::V4(v4));
        }
        Err(Error::InvalidArgument)
    }
}
