//! Emoji versions covered by this crate.

use std::fmt;
use std::str::FromStr;

/// A supported Emoji version.
///
/// Emoji versions follow Unicode Technical Standard #51. They can differ from
/// the Unicode Standard version. For example Emoji 13.1 ships with Unicode 13.0
/// data. This crate keys assets by Emoji version.
///
/// [`EmojiVersion::Latest`] is an alias for the highest numbered version. It
/// maps to the same assets as that version.
///
/// This enum is `#[non_exhaustive]`. A new Emoji version arrives roughly once a
/// year and adds a variant in a minor release. Match on it with a wildcard arm,
/// or iterate [`EmojiVersion::ALL`] for the concrete versions.
///
/// # Parsing
///
/// Parse a version from its number or the literal `"latest"`:
///
/// ```
/// use emoji_test_regex_pattern::EmojiVersion;
///
/// let v: EmojiVersion = "15.1".parse().unwrap();
/// assert_eq!(v, EmojiVersion::V15_1);
///
/// assert_eq!("latest".parse(), Ok(EmojiVersion::Latest));
///
/// // Unknown input reports the offending string.
/// let err = "12.0".parse::<EmojiVersion>().unwrap_err();
/// assert_eq!(err.to_string(), "unknown emoji version: 12.0");
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum EmojiVersion {
    /// Emoji 13.0.
    V13_0,
    /// Emoji 13.1.
    V13_1,
    /// Emoji 14.0.
    V14_0,
    /// Emoji 15.0.
    V15_0,
    /// Emoji 15.1.
    V15_1,
    /// Emoji 16.0.
    V16_0,
    /// Emoji 17.0.
    V17_0,
    /// Alias for the highest numbered version.
    Latest,
}

impl EmojiVersion {
    /// Every concrete version, in ascending order.
    ///
    /// [`EmojiVersion::Latest`] is not included. It is an alias, not a distinct
    /// data set.
    pub const ALL: [EmojiVersion; 7] = [
        EmojiVersion::V13_0,
        EmojiVersion::V13_1,
        EmojiVersion::V14_0,
        EmojiVersion::V15_0,
        EmojiVersion::V15_1,
        EmojiVersion::V16_0,
        EmojiVersion::V17_0,
    ];

    /// The highest numbered version. [`EmojiVersion::Latest`] resolves to this.
    pub const HIGHEST: EmojiVersion = EmojiVersion::V17_0;

    /// The version number as it appears in asset paths, such as `"13.0"`.
    ///
    /// [`EmojiVersion::Latest`] returns `"latest"`.
    #[must_use]
    pub const fn number(self) -> &'static str {
        match self {
            EmojiVersion::V13_0 => "13.0",
            EmojiVersion::V13_1 => "13.1",
            EmojiVersion::V14_0 => "14.0",
            EmojiVersion::V15_0 => "15.0",
            EmojiVersion::V15_1 => "15.1",
            EmojiVersion::V16_0 => "16.0",
            EmojiVersion::V17_0 => "17.0",
            EmojiVersion::Latest => "latest",
        }
    }

    /// The directory name for this version's assets, such as `"emoji-13.0"`.
    ///
    /// [`EmojiVersion::Latest`] returns `"latest"`.
    #[must_use]
    pub const fn dir_name(self) -> &'static str {
        match self {
            EmojiVersion::V13_0 => "emoji-13.0",
            EmojiVersion::V13_1 => "emoji-13.1",
            EmojiVersion::V14_0 => "emoji-14.0",
            EmojiVersion::V15_0 => "emoji-15.0",
            EmojiVersion::V15_1 => "emoji-15.1",
            EmojiVersion::V16_0 => "emoji-16.0",
            EmojiVersion::V17_0 => "emoji-17.0",
            EmojiVersion::Latest => "latest",
        }
    }

    /// Resolve [`EmojiVersion::Latest`] to the concrete highest version.
    ///
    /// Every other variant returns itself.
    #[must_use]
    pub const fn resolve(self) -> EmojiVersion {
        match self {
            EmojiVersion::Latest => EmojiVersion::HIGHEST,
            other => other,
        }
    }
}

impl fmt::Display for EmojiVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.number())
    }
}

impl FromStr for EmojiVersion {
    type Err = ParseVersionError;

    /// Parse a version number such as `"13.0"` or the literal `"latest"`.
    ///
    /// Matching is exact. Whitespace, wrong casing, and directory-style names
    /// are rejected. `"latest"` parses to [`EmojiVersion::Latest`], not to the
    /// resolved highest version.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "13.0" => Ok(EmojiVersion::V13_0),
            "13.1" => Ok(EmojiVersion::V13_1),
            "14.0" => Ok(EmojiVersion::V14_0),
            "15.0" => Ok(EmojiVersion::V15_0),
            "15.1" => Ok(EmojiVersion::V15_1),
            "16.0" => Ok(EmojiVersion::V16_0),
            "17.0" => Ok(EmojiVersion::V17_0),
            "latest" => Ok(EmojiVersion::Latest),
            _ => Err(ParseVersionError {
                input: s.to_owned(),
            }),
        }
    }
}

/// Error returned when a string is not a known Emoji version.
///
/// Carries the offending input so the message names it.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseVersionError {
    input: String,
}

impl ParseVersionError {
    /// The string that failed to parse.
    #[must_use]
    pub fn input(&self) -> &str {
        &self.input
    }
}

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

impl std::error::Error for ParseVersionError {}
