use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::string::ParseError;

/// Languages supported by MangaDex.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize)]
#[repr(u8)]
#[serde(from = "&str")]
pub enum Language {
    English = 1,
    Japanese = 2,
    Polish = 3,
    SerboCroatian = 4,
    Dutch = 5,
    Italian = 6,
    Russian = 7,
    German = 8,
    Hungarian = 9,
    French = 10,
    Finnish = 11,
    Vietnamese = 12,
    Greek = 13,
    Bulgarian = 14,
    SpanishSpain = 15,
    PortugueseBrazil = 16,
    PortuguesePortugal = 17,
    Swedish = 18,
    Arabic = 19,
    Danish = 20,
    ChineseSimplified = 21,
    Bengali = 22,
    Romanian = 23,
    Czech = 24,
    Mongolian = 25,
    Turkish = 26,
    Indonesian = 27,
    Korean = 28,
    SpanishLatinAmerican = 29,
    Persian = 30,
    Malay = 31,
    Thai = 32,
    Catalan = 33,
    Filipino = 34,
    ChineseTraditional = 35,
    Ukrainian = 36,
    Burmese = 37,
    Lithuanian = 38,
    Hebrew = 39,
    Hindi = 40,
    #[serde(rename = "any")]
    All = 63,
    #[serde(other)]
    Other = 41,
}

impl Language {
    /// Get the MangaDex string code representation.
    pub fn flag(&self) -> &str {
        match self {
            Self::English => "gb",
            Self::Japanese => "jp",
            Self::Polish => "pl",
            Self::SerboCroatian => "rs",
            Self::Dutch => "nl",
            Self::Italian => "it",
            Self::Russian => "ru",
            Self::German => "de",
            Self::Hungarian => "hu",
            Self::French => "fr",
            Self::Finnish => "fi",
            Self::Vietnamese => "vn",
            Self::Greek => "gr",
            Self::Bulgarian => "bg",
            Self::SpanishSpain => "es",
            Self::PortugueseBrazil => "br",
            Self::PortuguesePortugal => "pt",
            Self::Swedish => "se",
            Self::Arabic => "sa",
            Self::Danish => "dk",
            Self::ChineseSimplified => "cn",
            Self::Bengali => "bd",
            Self::Romanian => "ro",
            Self::Czech => "cz",
            Self::Mongolian => "mn",
            Self::Turkish => "tr",
            Self::Indonesian => "id",
            Self::Korean => "kr",
            Self::SpanishLatinAmerican => "mx",
            Self::Persian => "ir",
            Self::Malay => "my",
            Self::Thai => "th",
            Self::Catalan => "ct",
            Self::Filipino => "ph",
            Self::ChineseTraditional => "hk",
            Self::Ukrainian => "ua",
            Self::Burmese => "mm",
            Self::Lithuanian => "lt",
            Self::Hebrew => "il",
            Self::Hindi => "in",
            Self::All => "all",
            _ => "other",
        }
    }
}

impl Default for Language {
    fn default() -> Self {
        1u8.into()
    }
}

impl std::fmt::Display for Language {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        let name = match self {
            Self::English => "English",
            Self::Japanese => "Japanese",
            Self::Polish => "Polish",
            Self::SerboCroatian => "Serbo-Croatian",
            Self::Dutch => "Dutch",
            Self::Italian => "Italian",
            Self::Russian => "Russian",
            Self::German => "German",
            Self::Hungarian => "Hungarian",
            Self::French => "French",
            Self::Finnish => "Finnish",
            Self::Vietnamese => "Vietnamese",
            Self::Greek => "Greek",
            Self::Bulgarian => "Bulgarian",
            Self::SpanishSpain => "Spanish (Es)",
            Self::PortugueseBrazil => "Portuguese (Br)",
            Self::PortuguesePortugal => "Portuguese (Pt)",
            Self::Swedish => "Swedish",
            Self::Arabic => "Arabic",
            Self::Danish => "Danish",
            Self::ChineseSimplified => "Chinese (Simp)",
            Self::Bengali => "Bengali",
            Self::Romanian => "Romanian",
            Self::Czech => "Czech",
            Self::Mongolian => "Mongolian",
            Self::Turkish => "Turkish",
            Self::Indonesian => "Indonesian",
            Self::Korean => "Korean",
            Self::SpanishLatinAmerican => "Spanish (LATAM)",
            Self::Persian => "Persian",
            Self::Malay => "Malay",
            Self::Thai => "Thai",
            Self::Catalan => "Catalan",
            Self::Filipino => "Filipino",
            Self::ChineseTraditional => "Chinese (Trad)",
            Self::Ukrainian => "Ukrainian",
            Self::Burmese => "Burmese",
            Self::Lithuanian => "Lithuanian",
            Self::Hebrew => "Hebrew",
            Self::Hindi => "Hindi",
            Self::All => "All",
            _ => "Other",
        };
        fmt.write_str(name)
    }
}

impl From<u8> for Language {
    fn from(value: u8) -> Self {
        match value {
            1 => Self::English,
            2 => Self::Japanese,
            3 => Self::Polish,
            4 => Self::SerboCroatian,
            5 => Self::Dutch,
            6 => Self::Italian,
            7 => Self::Russian,
            8 => Self::German,
            9 => Self::Hungarian,
            10 => Self::French,
            11 => Self::Finnish,
            12 => Self::Vietnamese,
            13 => Self::Greek,
            14 => Self::Bulgarian,
            15 => Self::SpanishSpain,
            16 => Self::PortugueseBrazil,
            17 => Self::PortuguesePortugal,
            18 => Self::Swedish,
            19 => Self::Arabic,
            20 => Self::Danish,
            21 => Self::ChineseSimplified,
            22 => Self::Bengali,
            23 => Self::Romanian,
            24 => Self::Czech,
            25 => Self::Mongolian,
            26 => Self::Turkish,
            27 => Self::Indonesian,
            28 => Self::Korean,
            29 => Self::SpanishLatinAmerican,
            30 => Self::Persian,
            31 => Self::Malay,
            32 => Self::Thai,
            33 => Self::Catalan,
            34 => Self::Filipino,
            35 => Self::ChineseTraditional,
            36 => Self::Ukrainian,
            37 => Self::Burmese,
            38 => Self::Lithuanian,
            39 => Self::Hebrew,
            40 => Self::Hindi,
            41 => Self::Other,
            _ => Self::Other,
        }
    }
}

impl From<&str> for Language {
    /// Parse a `Language` type from a string.
    ///
    /// This function's value parameter is case-insensitive.
    fn from(value: &str) -> Self {
        match value.to_lowercase().as_str() {
            "gb" => Self::English,
            "jp" => Self::Japanese,
            "pl" => Self::Polish,
            "rs" => Self::SerboCroatian,
            "nl" => Self::Dutch,
            "it" => Self::Italian,
            "ru" => Self::Russian,
            "de" => Self::German,
            "hu" => Self::Hungarian,
            "fr" => Self::French,
            "fi" => Self::Finnish,
            "vn" => Self::Vietnamese,
            "gr" => Self::Greek,
            "bg" => Self::Bulgarian,
            "es" => Self::SpanishSpain,
            "br" => Self::PortugueseBrazil,
            "pt" => Self::PortuguesePortugal,
            "se" => Self::Swedish,
            "sa" => Self::Arabic,
            "dk" => Self::Danish,
            "cn" => Self::ChineseSimplified,
            "bd" => Self::Bengali,
            "ro" => Self::Romanian,
            "cz" => Self::Czech,
            "mn" => Self::Mongolian,
            "tr" => Self::Turkish,
            "id" => Self::Indonesian,
            "kr" => Self::Korean,
            "mx" => Self::SpanishLatinAmerican,
            "ir" => Self::Persian,
            "my" => Self::Malay,
            "th" => Self::Thai,
            "ct" => Self::Catalan,
            "ph" => Self::Filipino,
            "hk" => Self::ChineseTraditional,
            "ua" => Self::Ukrainian,
            "mm" => Self::Burmese,
            "lt" => Self::Lithuanian,
            "il" => Self::Hebrew,
            "in" => Self::Hindi,
            "all" => Self::All,
            _ => Self::Other,
        }
    }
}

impl FromStr for Language {
    type Err = ParseError;

    /// Parse a `Language` type from a string.
    ///
    /// This function's value parameter is case-insensitive.
    fn from_str(value: &str) -> Result<Self, ParseError> {
        Ok(match value.to_lowercase().as_str() {
            "gb" => Self::English,
            "jp" => Self::Japanese,
            "pl" => Self::Polish,
            "rs" => Self::SerboCroatian,
            "nl" => Self::Dutch,
            "it" => Self::Italian,
            "ru" => Self::Russian,
            "de" => Self::German,
            "hu" => Self::Hungarian,
            "fr" => Self::French,
            "fi" => Self::Finnish,
            "vn" => Self::Vietnamese,
            "gr" => Self::Greek,
            "bg" => Self::Bulgarian,
            "es" => Self::SpanishSpain,
            "br" => Self::PortugueseBrazil,
            "pt" => Self::PortuguesePortugal,
            "se" => Self::Swedish,
            "sa" => Self::Arabic,
            "dk" => Self::Danish,
            "cn" => Self::ChineseSimplified,
            "bd" => Self::Bengali,
            "ro" => Self::Romanian,
            "cz" => Self::Czech,
            "mn" => Self::Mongolian,
            "tr" => Self::Turkish,
            "id" => Self::Indonesian,
            "kr" => Self::Korean,
            "mx" => Self::SpanishLatinAmerican,
            "ir" => Self::Persian,
            "my" => Self::Malay,
            "th" => Self::Thai,
            "ct" => Self::Catalan,
            "ph" => Self::Filipino,
            "hk" => Self::ChineseTraditional,
            "ua" => Self::Ukrainian,
            "mm" => Self::Burmese,
            "lt" => Self::Lithuanian,
            "il" => Self::Hebrew,
            "in" => Self::Hindi,
            "all" => Self::All,
            _ => Self::Other,
        })
    }
}

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

    #[test]
    fn language_produces_english_from_default() {
        let lang = Language::default();
        assert_eq!(lang, Language::English);
    }

    #[test]
    fn language_produces_other_from_0() {
        let lang = Language::from(0u8);
        assert_eq!(lang, Language::Other);
    }

    #[test]
    fn language_produces_english_from_1() {
        let lang = Language::from(1u8);
        assert_eq!(lang, Language::English);
    }

    #[test]
    fn string_produces_english_from_gb() {
        let lang = Language::from("gb");
        assert_eq!(lang, Language::English);
    }

    #[test]
    fn string_produces_japanese_from_capitalized_jp() {
        let lang = Language::from("JP");
        assert_eq!(lang, Language::Japanese);
    }
}
