use std::fmt::{self, Display, Formatter, Write};

use serde_derive::{Deserialize, Serialize};

/// Operating system version including version number and optional edition.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Version {
    pub(crate) version: VersionType,
    pub(crate) edition: Option<String>,
}

/// Operating system version.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum VersionType {
    /// Unknown version.
    Unknown,
    /// Semantic version (major.minor.patch).
    Semantic(u64, u64, u64),
    /// Custom version format.
    Custom(String),
}

impl Version {
    /// Constructs a new `Version` instance with unknown version and `None` edition.
    ///
    /// # Examples
    ///
    /// ```
    /// use os_info::{Version, VersionType};
    ///
    /// let version = Version::unknown();
    /// assert_eq!(VersionType::Unknown, *version.version());
    /// assert_eq!(None, version.edition());
    /// ```
    pub fn unknown() -> Self {
        Self {
            version: VersionType::Unknown,
            edition: None,
        }
    }

    /// Constructs a new `Version` instance with semantic version and given edition.
    ///
    /// # Examples
    ///
    /// ```
    /// use os_info::{Version, VersionType};
    ///
    /// let version = Version::semantic(0, 1, 2, None);
    /// assert_eq!(VersionType::Semantic(0, 1, 2), *version.version());
    /// assert_eq!(None, version.edition());
    /// ```
    pub fn semantic(major: u64, minor: u64, patch: u64, edition: Option<String>) -> Self {
        Self {
            version: VersionType::Semantic(major, minor, patch),
            edition,
        }
    }

    /// Construct a new `Version` instance with "custom" (non semantic) version and given edition.
    ///
    /// # Examples
    ///
    /// ```
    /// use os_info::{Version, VersionType};
    ///
    /// let ver = "version".to_string();
    /// let edition = "edition".to_string();
    /// let version = Version::custom(ver.clone(), Some(edition.clone()));
    /// assert_eq!(VersionType::Custom(ver), *version.version());
    /// assert_eq!(Some(edition.as_ref()), version.edition());
    /// ```
    pub fn custom<T: Into<String>>(version: T, edition: Option<String>) -> Self {
        Self {
            version: VersionType::Custom(version.into()),
            edition,
        }
    }

    /// Returns operating system version. See `VersionType` for details.
    ///
    /// # Examples
    ///
    /// ```
    /// use os_info::{Version, VersionType};
    ///
    /// let version = Version::unknown();
    /// assert_eq!(VersionType::Unknown, *version.version());
    pub fn version(&self) -> &VersionType {
        &self.version
    }

    /// Returns optional (can be absent) operation system edition.
    ///
    /// # Examples
    ///
    /// ```
    /// use os_info::Version;
    ///
    /// let version = Version::unknown();
    /// assert_eq!(None, version.edition());
    pub fn edition(&self) -> Option<&str> {
        self.edition.as_ref().map(String::as_ref)
    }
}

impl Display for Version {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if let Some(ref edition) = self.edition {
            write!(f, "{}", edition)?;
        }
        write!(f, "{}", self.version)
    }
}

impl Display for VersionType {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            VersionType::Unknown => f.write_char('?'),
            VersionType::Semantic(major, minor, patch) => {
                write!(f, "{}.{}.{}", major, minor, patch)
            }
            VersionType::Custom(ref version) => write!(f, "{}", version),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn unknown() {
        let version = Version::unknown();
        assert_eq!(VersionType::Unknown, *version.version());
        assert_eq!(None, version.edition());
    }

    #[test]
    fn semantic() {
        let data = [
            ((0, 0, 0), None),
            ((10, 20, 30), Some("edition".to_string())),
            ((3, 2, 1), None),
            ((1, 0, 0), Some("different edition".to_string())),
        ];

        for &(v, ref edition) in &data {
            let version = Version::semantic(v.0, v.1, v.2, edition.clone());
            assert_eq!(VersionType::Semantic(v.0, v.1, v.2), *version.version());
            assert_eq!(edition.as_ref().map(String::as_ref), version.edition());
        }
    }

    #[test]
    fn custom() {
        let data = [
            ("OS", None),
            ("Another OS", Some("edition".to_string())),
            ("", None),
            ("Future OS", Some("e".to_string())),
        ];

        for &(ref v, ref edition) in &data {
            let version = Version::custom(*v, edition.clone());
            assert_eq!(VersionType::Custom(v.to_string()), *version.version());
            assert_eq!(edition.as_ref().map(String::as_ref), version.edition());
        }
    }
}
