// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Common definitions for the IP range output tool.

use std::error::Error as StdError;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::str::FromStr;

use eyre::{Error as AnyError, bail};

/// An error that most likely occurred while parsing the IP address specifications.
#[expect(clippy::error_impl_error, reason = "common enough convention")]
#[derive(Debug)]
pub enum Error {
    /// Invalid start address for a CIDR range.
    CidrBadStart,

    /// Invalid offset for a CIDR range.
    CidrPrefixTooLarge,

    /// Something went really, really wrong.
    Internal(AnyError),

    /// Could not parse an IP address.
    ParseAddress(String, AnyError),

    /// Could not parse an IP address / prefix length CIDR spec.
    ParseCidr(String, AnyError),

    /// Could not parse an exclusion mask.
    ParseExclude(String, AnyError),

    /// Invalid IP range specified.
    StartBeforeEnd,
}

impl Display for Error {
    /// Describe the error that occurred.
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match *self {
            Self::CidrBadStart => write!(f, "CIDR base address didn't start at subnet boundary"),

            Self::CidrPrefixTooLarge => write!(f, "CIDR offsets are between 0 and 32"),

            Self::Internal(_) => write!(f, "prips internal error"),

            Self::ParseAddress(ref value, _) => {
                write!(f, "Could not parse '{value}' as a valid IP address")
            }

            Self::ParseCidr(ref value, _) => {
                write!(f, "Could not parse '{value}' as a valid CIDR specification")
            }

            Self::ParseExclude(ref value, _) => {
                write!(f, "Could not parse '{value}' as a 0-255 integer value")
            }

            Self::StartBeforeEnd => {
                write!(f, "The start address must be smaller than the end address")
            }
        }
    }
}

impl StdError for Error {
    #[inline]
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        match *self {
            Self::CidrBadStart | Self::CidrPrefixTooLarge | Self::StartBeforeEnd => None,
            Self::Internal(ref err)
            | Self::ParseAddress(_, ref err)
            | Self::ParseCidr(_, ref err)
            | Self::ParseExclude(_, ref err) => Some(err.as_ref()),
        }
    }
}

/// An inclusive IPv4 address range.
#[derive(Debug, Eq, PartialEq)]
pub struct AddrRange {
    /// The first address in the range.
    pub start: u32,
    /// The last address in the range.
    pub end: u32,
}

/// The address formatting mode.
#[derive(Debug, Clone, Copy)]
pub enum AddrFormat {
    /// Dotted-quad.
    Dot,
    /// A single decimal number.
    Dec,
    /// A single hexadecimal number.
    Hex,
}

impl FromStr for AddrFormat {
    type Err = AnyError;
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "dot" => Ok(Self::Dot),
            "dec" => Ok(Self::Dec),
            "hex" => Ok(Self::Hex),
            _ => bail!("Unrecognized address format specified"),
        }
    }
}

/// Exclude a set of IP addresses with the specified values for the specified octets.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AddrExclude {
    /// The mask to apply a boolean "and" operation with.
    mask: u32,

    /// The octets to match after the boolean "and".
    value: u32,
}

impl AddrExclude {
    /// Construct an [`AddrExclude`] object with the specified mask and value.
    #[inline]
    #[must_use]
    pub const fn new(mask: u32, value: u32) -> Self {
        Self { mask, value }
    }

    /// The mask to apply a boolean "and" operation with.
    #[inline]
    #[must_use]
    pub const fn mask(self) -> u32 {
        self.mask
    }

    /// The octets to match after the boolean "and".
    #[inline]
    #[must_use]
    pub const fn value(self) -> u32 {
        self.value
    }
}

/// Runtime configuration for the IP range output tool.
#[derive(Debug)]
pub struct Config {
    /// The delimiter between two successive IP addresses.
    pub delim: char,
    /// The patterns of IP addresses to exclude when outputting a range.
    pub exclude: Option<Vec<AddrExclude>>,
    /// The address formatting mode.
    pub format: AddrFormat,
    /// The address range to process.
    pub range: AddrRange,
    /// The loop step.
    pub step: usize,
}
