// License: see LICENSE file at root directory of `master` branch

//! # IPv4 range

use {
    core::{
        ops::RangeInclusive,
        str::FromStr,
    },

    crate::Error,
};

#[cfg(feature="iter")]
mod ip_v4_range_iter;

#[cfg(feature="iter")]
pub use self::ip_v4_range_iter::*;

type ComponentValue = u8;
type Component = RangeInclusive<ComponentValue>;

/// # IPv4 range
///
/// A range can be respresented in string, following these rules:
///
/// - Start and end are placed inside one of `[]`, `[)`..., separated by a comma.
/// - `[` and `]` are inclusive.
/// - `(` and `)` are exclusive.
/// - White spaces can be included. They will be ignored by parser.
/// - Length of the string must be equal to or smaller than 64 bytes. It's for protection against flood attack.
///
/// Currently this struct is itself useless. You need to enable feature `iter`, in order to use [`IPv4RangeIter`][struct::IPv4RangeIter].
///
/// ## Examples
///
/// ```
/// use {
///     core::str::FromStr,
///     dia_ip_range::IPv4Range,
/// };
///
/// assert_eq!(
///     IPv4Range::from_str("10.0.0.[2,100)")?,
///     IPv4Range::make([10..=10, 0..=0, 0..=0, 2..=99])?,
/// );
///
/// # Ok::<_, dia_ip_range::Error>(())
/// ```
///
/// [struct::IPv4RangeIter]: struct.IPv4RangeIter.html
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct IPv4Range {
    a: Component,
    b: Component,
    c: Component,
    d: Component,
}

impl IPv4Range {

    /// # Makes new instance
    ///
    /// An error is returned if any component is empty.
    pub fn make(components: [RangeInclusive<u8>; 4]) -> crate::Result<Self> {
        if let Some(c) = components.iter().find(|c| c.is_empty()) {
            return Err(err!("Invalid range: {components:?} -> {c:?}", components=components, c=c));
        }

        let [a, b, c, d] = components;
        Ok(Self { a, b, c, d })
    }

}

impl FromStr for IPv4Range {

    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        const MAX_LEN: usize = 64;

        if s.len() > MAX_LEN {
            return Err(err!("String is too long, max length supported: {} bytes", MAX_LEN));
        }

        let mut parts = s.trim().split('.');
        match (parts.next(), parts.next(), parts.next(), parts.next(), parts.next()) {
            (Some(a), Some(b), Some(c), Some(d), None) => Self::make([
                parse_one_component(a)?,
                parse_one_component(b)?,
                parse_one_component(c)?,
                parse_one_component(d)?,
            ]),
            _ => Err(err!("Invalid IPv4 range: {:?}", s)),
        }
    }

}

/// # Parses one component
fn parse_one_component(component: &str) -> Result<Component, Error> {
    #[derive(Debug)]
    enum Mark {
        Inclusive,
        Exclusive,
    }

    let open_mark = match component.chars().next() {
        Some('[') => Mark::Inclusive,
        Some('(') => Mark::Exclusive,
        Some(_) => {
            let n = ComponentValue::from_str(component).map_err(|e| err!("{}", e))?;
            return Ok(Component::new(n, n));
        },
        None => return Err(err!("Empty string")),
    };
    let close_mark = if component.ends_with(']') {
        Mark::Inclusive
    } else if component.ends_with(')') {
        Mark::Exclusive
    } else {
        return Err(err!("Invalid close mark: {:?}", component));
    };

    let result = match {
        let mut parts = component[1 .. component.len() - 1].split(',');
        (
            parts.next().map(|p| ComponentValue::from_str(p.trim())),
            parts.next().map(|p| ComponentValue::from_str(p.trim())),
            parts.next(),
        )
    } {
        (Some(Ok(start)), Some(Ok(end)), None) => {
            let start = match open_mark {
                Mark::Inclusive => Some(start),
                Mark::Exclusive => start.checked_add(1),
            };
            let end = match close_mark {
                Mark::Inclusive => Some(end),
                Mark::Exclusive => end.checked_sub(1),
            };
            match (start, end) {
                (Some(start), Some(end)) if end >= start => Some(Component::new(start, end)),
                _ => None,
            }
        },
        _ => None,
    };
    result.ok_or_else(|| err!("Invalid component: {:?}", component))
}

#[test]
fn test_ip_ranges() -> crate::Result<()> {
    for s in &["0.0.0.0", "[255,255].[255,255].[255,255].[255,255]", "192.168.[0,9].[9,10)", "192.168.[    0   ,9].[9  ,    10)"] {
        IPv4Range::from_str(s)?;
    }
    for s in &["0.0.0", "192.168.[09].[9-10)", "192.168.[    0   ,9.9  ,    10)"] {
        IPv4Range::from_str(s).unwrap_err();
    }

    Ok(())
}
