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

//! # Range

#![deprecated(note = "Beta")]

mod impls;
mod kind;

use {
    core::{
        fmt::{self, Display, Formatter},
        ops::RangeInclusive,
    },

    crate::Semver,

    self::kind::Kind,
};

/// # 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 4096 bytes. It's for protection against flood attack.
///
/// ## Notes
///
/// Large ranges, such as `[,]`, `(,)`, [`start,`], [`,end`]... are not supported. Technically it's doable, but using such wild ranges in
/// software development is not recommended.
///
/// ## Examples
///
/// ```
/// use core::str::FromStr;
/// use dia_semver::{Range, Semver};
///
/// // An empty range
/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 0, 0));
/// assert!(range.is_empty());
///
/// // Only one single semver
/// let range = Range::from(Semver::new(0, 1, 2));
/// assert!(range.contains(&Semver::new(0, 1, 2)));
/// assert!(range.contains(&Semver::new(0, 1, 3)) == false);
///
/// // Inclusive range
/// let range = Range::from_str("[0.1.2, 0.2.0-beta]")?;
/// assert!(range.contains(&Semver::new(0, 1, 3)));
/// assert!(range.contains(&Semver::from_str("0.2.0-alpha")?));
/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
///
/// // Exclusive range
/// let range = Range::from(Semver::new(0, 1, 2)..Semver::new(0, 2, 0));
/// assert!(range.contains(&Semver::new(0, 2, 0)) == false);
///
/// # dia_semver::Result::Ok(())
/// ```
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct Range {
    range: RangeInclusive<Semver>,
    kind: Kind,
}

impl Range {

    /// # Start of the range
    pub fn start(&self) -> &Semver {
        self.range.start()
    }

    /// # End of the range
    pub fn end(&self) -> &Semver {
        self.range.end()
    }

    /// # Checks if this range is empty
    pub fn is_empty(&self) -> bool {
        match &self.kind {
            Kind::InclusiveInclusive => self.range.is_empty(),
            Kind::InclusiveExclusive => self.range.start() >= self.range.end(),
            Kind::ExclusiveExclusive => self.range.start() >= self.range.end(),
            Kind::ExclusiveInclusive => self.range.start() >= self.range.end(),
        }
    }

    /// # Checks if this range contains a semver
    pub fn contains(&self, semver: &Semver) -> bool {
        match &self.kind {
            Kind::InclusiveInclusive => self.range.contains(semver),
            Kind::InclusiveExclusive => semver >= self.range.start() && semver < self.range.end(),
            Kind::ExclusiveExclusive => semver > self.range.start() && semver < self.range.end(),
            Kind::ExclusiveInclusive => semver > self.range.start() && semver <= self.range.end(),
        }
    }

}

impl From<&Semver> for Range {

    fn from(semver: &Semver) -> Self {
        Self {
            range: RangeInclusive::new(semver.clone(), semver.clone()),
            kind: Kind::InclusiveInclusive,
        }
    }

}

impl From<Semver> for Range {

    fn from(semver: Semver) -> Self {
        Self::from(&semver)
    }

}

impl From<core::ops::Range<Semver>> for Range {

    fn from(range: core::ops::Range<Semver>) -> Self {
        Self {
            range: RangeInclusive::new(range.start, range.end),
            kind: Kind::InclusiveExclusive,
        }
    }

}

impl From<RangeInclusive<Semver>> for Range {

    fn from(range: RangeInclusive<Semver>) -> Self {
        Self {
            range,
            kind: Kind::InclusiveInclusive,
        }
    }

}

impl Display for Range {

    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        const INCLUSIVE_OPEN: char = '[';
        const INCLUSIVE_CLOSE: char = ']';
        const EXCLUSIVE_OPEN: char = '(';
        const EXCLUSIVE_CLOSE: char = ')';

        let (open, close) = match &self.kind {
            Kind::InclusiveInclusive => (INCLUSIVE_OPEN, INCLUSIVE_CLOSE),
            Kind::InclusiveExclusive => (INCLUSIVE_OPEN, EXCLUSIVE_CLOSE),
            Kind::ExclusiveExclusive => (EXCLUSIVE_OPEN, EXCLUSIVE_CLOSE),
            Kind::ExclusiveInclusive => (EXCLUSIVE_OPEN, INCLUSIVE_CLOSE),
        };

        write!(
            f,
            concat!("{open}", "{start}", ',', ' ', "{end}", "{close}"),
            open=open,
            start=self.range.start().to_short_format(),
            end=self.range.end().to_short_format(),
            close=close,
        )
    }

}
