use {
    serde::{Deserialize, Serialize},
    std::{error::Error, fmt, str::FromStr},
};

mod gitrev;
mod rapid;
mod semver;

pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};

#[derive(Clone, Debug, Serialize, Deserialize)]
/// An enum representing the most common versioning schemes.
/// Each scheme must implement `Eq` and `Ord` with itself, and
/// may optionally implement `PartialEq` and `PartialOrd` with
/// other schemes. `Number`, `Rapid`, and `SemVer` do this, while
/// Git can only compare with itself.
pub enum Version {
    /// A single replease number, as in Firefox 102
    Number(u32),
    /// Rapid versioning consists of two numbers separated by
    /// a dot (.) character, and is used notably by Gnome
    Rapid(Rapid),
    /// SemVer is the most common versioning scheme and consists
    /// of three dot (.) separated numbers. The numbers are major,
    /// minor, and patch respctively.
    SemVer(SemVer),
    /// A git revision. Use of this scheme is to be avoided in
    /// official packaging as it cannot be readily compared with
    /// other versioning schemes for dependency resolution.
    Git(GitRev),
}

impl Default for Version {
    fn default() -> Self {
        Self::SemVer(SemVer {
            major: 0,
            minor: 1,
            patch: 0,
        })
    }
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Number(n) => write!(f, "{n}"),
            Self::SemVer(s) => write!(f, "{s}"),
            Self::Rapid(r) => write!(f, "{r}"),
            Self::Git(g) => write!(f, "{g}"),
        }
    }
}

impl From<SemVer> for Version {
    fn from(value: SemVer) -> Self {
        Self::SemVer(value)
    }
}

impl From<Rapid> for Version {
    fn from(value: Rapid) -> Self {
        Self::Rapid(value)
    }
}

impl From<GitRev> for Version {
    fn from(value: GitRev) -> Self {
        Self::Git(value)
    }
}

impl From<u32> for Version {
    fn from(value: u32) -> Self {
        Self::Number(value)
    }
}

impl PartialOrd for Version {
    #[allow(clippy::many_single_char_names)]
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match (self, other) {
            (Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
            (Self::Number(s), Self::Rapid(o)) => s.partial_cmp(o),
            (Self::Number(s), Self::SemVer(o)) => s.partial_cmp(o),
            (Self::Rapid(s), Self::Number(o)) => s.partial_cmp(o),
            (Self::Rapid(s), Self::Rapid(o)) => s.partial_cmp(o),
            (Self::Rapid(s), Self::SemVer(o)) => s.partial_cmp(o),
            (Self::SemVer(s), Self::Number(o)) => s.partial_cmp(o),
            (Self::SemVer(s), Self::Rapid(o)) => s.partial_cmp(o),
            (Self::SemVer(s), Self::SemVer(o)) => s.partial_cmp(o),
            (Self::Git(s), Self::Git(o)) => s.partial_cmp(o),
            _ => None,
        }
    }
}

impl PartialEq for Version {
    #[allow(clippy::many_single_char_names)]
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Number(s), Self::Number(o)) => s.eq(o),
            (Self::Number(s), Self::Rapid(o)) => s.eq(o),
            (Self::Number(s), Self::SemVer(o)) => s.eq(o),
            (Self::Rapid(s), Self::Number(o)) => s.eq(o),
            (Self::Rapid(s), Self::Rapid(o)) => s.eq(o),
            (Self::Rapid(s), Self::SemVer(o)) => s.eq(o),
            (Self::SemVer(s), Self::Number(o)) => s.eq(o),
            (Self::SemVer(s), Self::Rapid(o)) => s.eq(o),
            (Self::SemVer(s), Self::SemVer(o)) => s.eq(o),
            (Self::Git(s), Self::Git(o)) => s.eq(o),
            _ => false,
        }
    }
}

#[derive(Debug)]
pub struct ParseVersionError;

impl fmt::Display for ParseVersionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "error parsing version")
    }
}

impl Error for ParseVersionError {}

impl FromStr for Version {
    type Err = ParseVersionError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(v) = s.parse::<SemVer>() {
            Ok(v.into())
        } else if let Ok(v) = s.parse::<Rapid>() {
            Ok(v.into())
        } else if let Ok(v) = s.parse::<GitRev>() {
            Ok(v.into())
        } else if let Ok(v) = s.parse::<u32>() {
            Ok(v.into())
        } else {
            Err(ParseVersionError)
        }
    }
}

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

    #[test]
    fn cmp_semver_rapid_gt() {
        let sem = "1.42.1".parse::<SemVer>().unwrap();
        let rpd = "1.42".parse::<Rapid>().unwrap();
        assert!(sem > rpd);
    }

    #[test]
    fn cmp_semver_rapid_eq() {
        let sem = "1.42.0".parse::<SemVer>().unwrap();
        let rpd = "1.42".parse::<Rapid>().unwrap();
        assert!(sem == rpd);
    }

    #[test]
    fn cmp_semver_num_gt() {
        let sem = Version::SemVer("42.69.0".parse().unwrap());
        let num = Version::Number(42);
        assert!(sem > num);
    }

    #[test]
    fn cmp_semver_num_eq() {
        let sem = Version::SemVer("42.0.0".parse().unwrap());
        let num = Version::Number(42);
        assert_eq!(sem, num);
    }
}
