// 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.
///
/// 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,
/// };
///
/// IPv4Range::from_str("10.0.0.[2,99]")?;
///
/// # Ok::<_, dia_ip_range::Error>(())
/// ```
///
/// [struct::IPv4RangeIter]: struct.IPv4RangeIter.html
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct IPv4Range {
    a: Component,
    b: Component,
    c: Component,
    d: Component,
}

impl FromStr for IPv4Range {

    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        fn parse_component_value(s: &str) -> Result<ComponentValue, Error> {
            ComponentValue::from_str(s).map_err(|e| err!("{}", e))
        }

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

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

            let result = match {
                let mut parts = c[1 .. c.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)) => {
                            let component = Component::new(start, end);
                            if component.is_empty() {
                                None
                            } else {
                                Some(component)
                            }
                        },
                        _ => None,
                    }
                },
                _ => None,
            };
            result.ok_or_else(|| err!("Invalid component: {:?}", c))
        }

        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) => Ok(Self {
                a: parse_one_component(a)?,
                b: parse_one_component(b)?,
                c: parse_one_component(c)?,
                d: parse_one_component(d)?,
            }),
            _ => Err(err!("Invalid IPv4 range: {:?}", s)),
        }
    }

}

#[test]
fn test_ip_ranges() -> crate::Result<()> {
    for s in &["0.0.0.0", "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(())
}
