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

mod parser;

use {
    alloc::string::{String, ToString},
    core::{
        cmp::Ordering,
        fmt::{self, Display, Formatter},
        hash::{Hash, Hasher},
        str::FromStr,
    },

    crate::{ParseSemverError, PreRelease, Result},

    self::parser::Parser,
};

#[cfg(feature="std")]
use {
    std::ffi::{CStr, OsStr},

    crate::SemverErrorKind,
};

/// # Max string length of a version number
const MAX_STR_LEN_OF_A_VERSION_NUMBER: usize = 20;

/// # Starter for pre-release
const PRE_RELEASE_STARTER: char = '-';

/// # Starter for build metadata
const BUILD_METADATA_STARTER: char = '+';

/// # Semver.
///
/// ## Concepts
///
/// ### Strict parser
///
/// - Does not allow leading/trailing white spaces.
/// - All 3 version numbers are required: major, minor, patch.
///
/// ### Tolerant parser
///
/// - Ignores leading/trailing white spaces.
/// - Minor and patch version numbers are optional.
///
/// This mode is added by the crate author, it's not described in official specification.
///
/// ### Usage
///
/// A semver instance should always be valid. For this reason, it does not expose internal fields (where the user can make new instance
/// directly). Instead, it provides helper functions:
///
/// - [`new()`][fn:new] makes new instance from raw input version numbers.
/// - [`parse()`][fn:parse] and `::parse_*()` use strict parser, while `::from_*()` are more tolerant.
///
/// Since Rust encourages the use of [immutable variables][book::ch03-01], a semver instance should _not_ be changed. Once made, there are no
/// functions which help you change its fields. However there are useful functions to help you make new semver from an existing one:
/// [`new_major()`][fn:new_major], [`new_minor()`][fn:new_minor], [`new_patch()`][fn:new_patch]...
///
/// Others might also come in handy: [`is_stable()`][fn:is_stable], [`is_early()`][fn:is_early].
///
/// [book::ch03-01]: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html
/// [fn:new]: #method.new
/// [fn:parse]: #method.parse
/// [fn:is_stable]: #method.is_stable
/// [fn:is_early]: #method.is_early
/// [fn:parse_pre_release]: #method.parse_pre_release
/// [fn:new_major]: #method.new_major
/// [fn:new_minor]: #method.new_minor
/// [fn:new_patch]: #method.new_patch
#[derive(Debug, Default, Clone, Eq)]
pub struct Semver {

    /// # Major
    major: u64,

    /// # Minor
    minor: u64,

    /// # Patch
    patch: u64,

    /// # Pre-release
    pre_release: Option<String>,

    /// # Build metadata
    build_metadata: Option<String>,

}

impl Semver {

    /// # Max input string length allowed to be parsed
    pub const MAX_INPUT_STR_LEN: u16 = 2048;

    /// # Makes new semver from raw input version numbers
    ///
    /// This function does not support pre-release and build metadata -- which are required to be parsed. For convenience, it returns a direct
    /// instance, not a `Result<Semver>`. If you need pre-release and/or build metadata, you can use [`parse()`][fn:parse].
    ///
    /// [fn:parse]: #method.parse
    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
        Semver {
            major, minor, patch,
            pre_release: None, build_metadata: None,
        }
    }

    /// # Parses input string to make a new semver
    ///
    /// - Minor and patch version numbers are **required**.
    /// - Leading/trailing white spaces are **not** allowed.
    ///
    /// For convenience, you can use implementation of [`FromStr`][trait:FromStr] trait. It uses _tolerant_ parser, where:
    ///
    /// - Minor and patch version numbers are optional.
    /// - Leading/trailing white spaces are ignored.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(Semver::parse("1.2.3").unwrap().to_string(), "1.2.3");
    /// assert_eq!(
    ///     Semver::from_str("\t     1-a.b.c     \r\n").unwrap().to_string(),
    ///     "1.0.0-a.b.c",
    /// );
    /// ```
    ///
    /// [trait:FromStr]: https://doc.rust-lang.org/core/str/trait.FromStr.html
    pub fn parse<S>(s: S) -> Result<Self> where S: AsRef<str> {
        Parser::parse(s.as_ref(), true)
    }

    /// # Parses an [`&OsStr`][struct:OsStr]
    ///
    /// _See [`Parser`][struct:Parser] for details_.
    ///
    /// [struct:Parser]: struct.Parser.html
    /// [struct:OsStr]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
    #[cfg(feature="std")]
    fn parse_os_str_with_options<S>(s: S, strict: bool) -> Result<Self> where S: AsRef<OsStr> {
        match s.as_ref().to_str() {
            Some(s) => Parser::parse(s.as_ref(), strict),
            None => Err(ParseSemverError { kind: SemverErrorKind::InvalidToken }),
        }
    }

    /// # Parses an [`&OsStr`][struct:OsStr]
    ///
    /// This function uses strict parser.
    ///
    /// [struct:OsStr]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
    #[cfg(feature="std")]
    pub fn parse_os_str<S>(s: S) -> Result<Self> where S: AsRef<OsStr> {
        Self::parse_os_str_with_options(s, true)
    }

    /// # Parses an [`&OsStr`][struct:OsStr]
    ///
    /// This function uses tolerant parser.
    ///
    /// [struct:OsStr]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html
    #[cfg(feature="std")]
    pub fn from_os_str<S>(s: S) -> Result<Self> where S: AsRef<OsStr> {
        Self::parse_os_str_with_options(s, false)
    }

    /// # Parses a [`&CStr`][struct:CStr]
    ///
    /// _See [`Parser`][struct:Parser] for details_.
    ///
    /// [struct:Parser]: struct.Parser.html
    /// [struct:CStr]: https://doc.rust-lang.org/std/ffi/struct.CStr.html
    #[cfg(feature="std")]
    fn parse_c_str_with_options<S>(s: S, strict: bool) -> Result<Self> where S: AsRef<CStr> {
        match s.as_ref().to_str() {
            Ok(s) => Parser::parse(s.as_ref(), strict),
            _ => Err(ParseSemverError { kind: SemverErrorKind::InvalidToken }),
        }
    }

    /// # Parses a [`&CStr`][struct:CStr]
    ///
    /// This function uses strict parser.
    ///
    /// [struct:CStr]: https://doc.rust-lang.org/std/ffi/struct.CStr.html
    #[cfg(feature="std")]
    pub fn parse_c_str<S>(s: S) -> Result<Self> where S: AsRef<CStr> {
        Self::parse_c_str_with_options(s, true)
    }

    /// # Parses a [`&CStr`][struct:CStr]
    ///
    /// This function uses tolerant parser.
    ///
    /// [struct:CStr]: https://doc.rust-lang.org/std/ffi/struct.CStr.html
    #[cfg(feature="std")]
    pub fn from_c_str<S>(s: S) -> Result<Self> where S: AsRef<CStr> {
        Self::parse_c_str_with_options(s, false)
    }

    /// # Major
    pub fn major(&self) -> u64 {
        self.major
    }

    /// # Minor
    pub fn minor(&self) -> u64 {
        self.minor
    }

    /// # Patch
    pub fn patch(&self) -> u64 {
        self.patch
    }

    /// # Pre-release
    ///
    /// This does **not** contain the leading character `-`. However calling `to_string()` will always have that character displayed.
    pub fn pre_release(&self) -> Option<&str> {
        self.pre_release.as_ref().map(|pr| pr.as_str())
    }

    /// # Build metadata
    ///
    /// This does **not** contain the leading character `+`. However calling `to_string()` will always have that character displayed.
    pub fn build_metadata(&self) -> Option<&str> {
        self.build_metadata.as_ref().map(|bm| bm.as_str())
    }

    /// # Checks to see if the semver is stable
    ///
    /// A semver is stable if:
    ///
    /// - It's not a pre-release.
    /// - Its major version is larger than zero.
    pub fn is_stable(&self) -> bool {
        self.major > 0 && self.pre_release.is_none()
    }

    /// # Checks to see if the semver is in early development state, in which major version is zero
    pub fn is_early(&self) -> bool {
        self.major == 0
    }

    /// # Parses pre-release to see if it starts with some common phrases
    ///
    /// Notes:
    ///
    /// - Case is ignored.
    /// - If it returns `None`, it means the semver doesn't have a pre-release.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::{Semver, PreRelease};
    ///
    /// match Semver::from_str("1.2.3-hola").unwrap().parse_pre_release() {
    ///     Some(PreRelease::Alpha) => println!("Alpha"),
    ///     Some(PreRelease::Beta) => println!("Beta"),
    ///     Some(PreRelease::RC) => println!("RC"),
    ///     Some(PreRelease::Other) => println!("Other"),
    ///     None => println!("Not available"),
    /// };
    /// ```
    pub fn parse_pre_release(&self) -> Option<PreRelease> {
        self.pre_release.as_ref().map(|pre_release| PreRelease::parse(pre_release))
    }

    /// # Checks to see if this semver is compatible with one other
    ///
    /// Two semvers are compatible with each other if one of these conditions is true:
    ///
    /// - They have same major version, which must be larger than `0`.
    /// - If both major versions are `0`, they must have _exactly_ these same components: major, minor, patch, pre-release.
    ///
    /// # Examples
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert!(Semver::new(1, 0, 1).compatible_with(&Semver::new(1, 2, 3)));
    /// assert!(Semver::new(1, 0, 1).compatible_with(&Semver::new(1, 99, 9)));
    /// assert!(Semver::new(1, 0, 1).compatible_with(&Semver::new(2, 0, 0)) == false);
    /// assert!(Semver::new(0, 0, 1).compatible_with(&Semver::new(0, 2, 0)) == false);
    /// assert!(Semver::new(0, 0, 1).compatible_with(&Semver::from_str("0.0.1-abc").unwrap()) == false);
    /// assert!(Semver::new(0, 0, 1).compatible_with(&Semver::from_str("0.0.1+abc").unwrap()));
    /// ```
    pub fn compatible_with(&self, other: &Self) -> bool {
        if self.major != other.major { return false; }
        if self.major > 0 { return true; }
        if self.minor != other.minor || self.patch != other.patch { return false; }

        match (self.pre_release.as_ref(), other.pre_release.as_ref()) {
            (Some(self_pre_release), Some(other_pre_release)) => self_pre_release == other_pre_release,
            (None, None) => true,
            _ => false,
        }
    }

    /// # Makes new semver with major version incremented by `1`
    ///
    /// ## Notes
    ///
    /// - Pre-release and build metadata will be dropped.
    /// - `checked_add()` will be used. So if overflow happens, `None` is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(Semver::from_str("1.2.3-abc+xyz").unwrap().new_major().unwrap().to_string(), "2.0.0");
    /// ```
    pub fn new_major(&self) -> Option<Self> {
        match self.major.checked_add(1) {
            Some(new_major) => Some(Self::new(new_major, 0, 0)),
            None => None,
        }
    }

    /// # Makes new semver with minor version incremented by `1`
    ///
    /// ## Notes
    ///
    /// - Pre-release and build metadata will be dropped.
    /// - `checked_add()` will be used. So if overflow happens, `None` is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(Semver::from_str("1.2.3-abc+xyz").unwrap().new_minor().unwrap().to_string(), "1.3.0");
    /// ```
    pub fn new_minor(&self) -> Option<Self> {
        match self.minor.checked_add(1) {
            Some(new_minor) => Some(Self::new(self.major, new_minor, 0)),
            None => None,
        }
    }

    /// # Makes new semver with patch version incremented by `1`
    ///
    /// ## Notes
    ///
    /// - Pre-release and build metadata will be dropped.
    /// - `checked_add()` will be used. So if overflow happens, `None` is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(Semver::from_str("1.2.3-abc+xyz").unwrap().new_patch().unwrap().to_string(), "1.2.4");
    /// ```
    pub fn new_patch(&self) -> Option<Self> {
        match self.patch.checked_add(1) {
            Some(new_patch) => Some(Self::new(self.major, self.minor, new_patch)),
            None => None,
        }
    }

    /// # Makes new semver with same version numbers, but new pre-release and/or same build metadata
    ///
    /// `pre_release` should _not_ include leading character `-`; it will be added automatically.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(
    ///     Semver::from_str("1.2.3-alpha.1+abc").unwrap()
    ///         .new_pre_release("alpha.2", false).unwrap()
    ///         .to_string(),
    ///     "1.2.3-alpha.2"
    /// );
    /// ```
    pub fn new_pre_release<S>(&self, pre_release: S, keep_build_metadata: bool) -> Result<Self> where S: AsRef<str> {
        let pre_release = pre_release.as_ref();

        // Pre-release
        let mut s = alloc::format!(
            "{major}.{minor}.{patch}{pre_release_starter}{pre_release}",
            major=self.major, minor=self.minor, patch=self.patch, pre_release_starter=self::PRE_RELEASE_STARTER, pre_release=pre_release,
        );

        // Build metadata
        if keep_build_metadata {
            if let Some(build_metadata) = self.build_metadata.as_ref() {
                s.push(self::BUILD_METADATA_STARTER);
                s.push_str(build_metadata);
            }
        }

        Self::parse(s)
    }

    /// # Drops pre-release
    ///
    /// ## Examples
    ///
    /// ```
    /// use {
    ///     core::str::FromStr,
    ///     dia_semver::Semver,
    /// };
    ///
    /// assert_eq!(
    ///     Semver::from_str("1.2.3-alpha")?.drop_pre_release(),
    ///     Semver::new(1, 2, 3),
    /// );
    /// assert_eq!(
    ///     Semver::from_str("1.2.3-some+rigel")?.drop_pre_release().to_string(),
    ///     Semver::from_str("1.2.3+rigel")?.to_string(),
    /// );
    ///
    /// # Ok::<_, dia_semver::ParseSemverError>(())
    /// ```
    pub fn drop_pre_release(&self) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            build_metadata: self.build_metadata.clone(),
            pre_release: None,
        }
    }

    /// # Makes new semver with same version numbers, but new build metadata and/or same pre-release
    ///
    /// `build_metadata` should _not_ include leading character `+`; it will be added automatically.
    ///
    /// # Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// assert_eq!(
    ///     Semver::from_str("1.2.3-alpha.1+abc").unwrap()
    ///         .new_build_metadata("xyz", false).unwrap()
    ///         .to_string(),
    ///     "1.2.3+xyz"
    /// );
    /// ```
    pub fn new_build_metadata<S>(&self, build_metadata: S, keep_pre_release: bool) -> Result<Self> where S: AsRef<str> {
        let build_metadata = build_metadata.as_ref();

        let mut s = alloc::format!("{major}.{minor}.{patch}", major=self.major, minor=self.minor, patch=self.patch);

        // Pre-release
        if keep_pre_release {
            if let Some(pre_release) = self.pre_release.as_ref() {
                s.push(self::PRE_RELEASE_STARTER);
                s.push_str(pre_release);
            }
        }

        // Build metadata
        s.push(self::BUILD_METADATA_STARTER);
        s.push_str(build_metadata);

        Self::parse(s)
    }

    /// # Drops build metadata
    ///
    /// ## Examples
    ///
    /// ```
    /// use {
    ///     core::str::FromStr,
    ///     dia_semver::Semver,
    /// };
    ///
    /// assert_eq!(
    ///     Semver::from_str("1.2.3+bsd")?.drop_build_metadata().to_string(),
    ///     Semver::new(1, 2, 3).to_string(),
    /// );
    /// assert_eq!(
    ///     Semver::from_str("1.2.3-beta+artix")?.drop_build_metadata().to_string(),
    ///     Semver::from_str("1.2.3-beta")?.to_string(),
    /// );
    ///
    /// # Ok::<_, dia_semver::ParseSemverError>(())
    /// ```
    pub fn drop_build_metadata(&self) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            build_metadata: None,
            pre_release: self.pre_release.clone(),
        }
    }

    /// # Makes this version in short format
    ///
    /// Short format means the last version number(s) will be left out, if they're zeros.
    ///
    /// ## Examples
    ///
    /// ```
    /// use core::str::FromStr;
    /// use dia_semver::Semver;
    ///
    /// for (sample, expected) in &[
    ///     ("1.0.0-alpha+bsd", "1-alpha+bsd"),
    ///     ("1.1.0", "1.1"),
    ///     ("2.0.1-beta", "2.0.1-beta"),
    /// ] {
    ///     assert_eq!(Semver::from_str(sample)?.to_short_format(), *expected);
    /// }
    ///
    /// # Ok::<_, dia_semver::ParseSemverError>(())
    /// ```
    pub fn to_short_format(&self) -> String {
        let mut result = match self.patch {
            0 => match self.minor {
                0 => self.major.to_string(),
                _ => alloc::format!("{}.{}", self.major, self.minor),
            },
            _ => return self.to_string(),
        };

        if let Some(pre_release) = self.pre_release.as_ref() {
            result.push(self::PRE_RELEASE_STARTER);
            result.push_str(pre_release);
        }

        if let Some(build_metadata) = self.build_metadata.as_ref() {
            result.push(self::BUILD_METADATA_STARTER);
            result.push_str(build_metadata);
        }

        result
    }

}

impl PartialEq for Semver {

    fn eq(&self, other: &Self) -> bool {
        self.cmp(&other) == Ordering::Equal
    }

}

impl Ord for Semver {

    fn cmp(&self, other: &Self) -> Ordering {
        // Major, minor, patch
        match self.major.cmp(&other.major) {
            Ordering::Equal => match self.minor.cmp(&other.minor) {
                Ordering::Equal => match self.patch.cmp(&other.patch) {
                    Ordering::Equal => (),
                    x => return x,
                },
                x => return x,
            },
            x => return x,
        };

        // Comparing pre-releases: see section 11
        match (self.pre_release.as_ref(), other.pre_release.as_ref()) {
            (None, None) => return Ordering::Equal,
            (None, Some(_)) => return Ordering::Greater,
            (Some(_), None) => return Ordering::Less,
            (Some(self_pre_release), Some(other_pre_release)) => {
                let mut self_pre_release = self_pre_release.split('.');
                let mut other_pre_release = other_pre_release.split('.');
                loop {
                    match (self_pre_release.next(), other_pre_release.next()) {
                        (None, None) => return Ordering::Equal,
                        (None, Some(_)) => return Ordering::Less,
                        (Some(_), None) => return Ordering::Greater,
                        (Some(self_field), Some(other_field)) => {
                            let self_field_is_number = self_field.chars().any(|c| c < '0' || c > '9') == false;
                            let other_field_is_number = other_field.chars().any(|c| c < '0' || c > '9') == false;
                            match (self_field_is_number, other_field_is_number) {
                                // Specification just says "numeric identifiers". So they might be large, we don't parse them into integers.
                                // The fields only contain ASCII numbers, so it's ok to use `len()`
                                (true, true) => match self_field.len().cmp(&other_field.len()) {
                                    Ordering::Equal => match self_field.cmp(&other_field) {
                                        Ordering::Equal => (),
                                        x => return x,
                                    },
                                    x => return x,
                                },
                                (true, false) => return Ordering::Less,
                                (false, true) => return Ordering::Greater,
                                (false, false) => match self_field.cmp(&other_field) {
                                    Ordering::Equal => (),
                                    x => return x,
                                },
                            };
                        },
                    };
                }
            },
        };
    }

}

impl PartialOrd for Semver {

    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }

}

impl Hash for Semver {

    fn hash<H: Hasher>(&self, state: &mut H) {
        self.major.hash(state);
        self.minor.hash(state);
        self.patch.hash(state);

        if let Some(pre_release) = self.pre_release.as_ref() {
            pre_release.hash(state);
        }
    }

}

impl FromStr for Semver {

    type Err = ParseSemverError;

    /// # Parses input string via _tolerant_ parser to make a new semver
    ///
    /// - Minor and patch version numbers are optional.
    /// - Leading/trailing white spaces are ignored.
    ///
    /// For strict parser, see [`parse()`][fn:parse].
    ///
    /// [fn:parse]: #method.parse
    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
        Parser::parse(s, false)
    }

}

impl Display for Semver {

    fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;

        if let Some(pre_release) = self.pre_release.as_ref() {
            write!(f, "{}{}", self::PRE_RELEASE_STARTER, pre_release)?;
        }

        if let Some(build_metadata) = self.build_metadata.as_ref() {
            write!(f, "{}{}", self::BUILD_METADATA_STARTER, build_metadata)?;
        }

        Ok(())
    }

}

#[test]
fn test_semver_internal_stuff() {
    use alloc::string::ToString;

    assert_eq!(Semver::new(0, 0, 0).major.wrapping_sub(1).to_string().len(), MAX_STR_LEN_OF_A_VERSION_NUMBER);
}
