///|
/// The ISO 639-1 two-letter code for the language. A language that ISO
/// 639-3 counts as the main member of a macrolanguage gets the
/// macrolanguage's code, as it is commonly tagged: Mandarin `zh`, Standard
/// Arabic `ar`, Iranian Persian `fa`, Malay `ms`, Northern Uzbek `uz`,
/// North Azerbaijani `az`, Plateau Malagasy `mg`, Swahili `sw`, Northern
/// Pashto `ps` and Nepali `ne`. `None` for languages without one, such as
/// Cebuano or Bhojpuri, and for members that are not the main form of
/// their macrolanguage, such as Nigerian Fulfulde (`ff`) or Central
/// Kurdish (`ku`).
pub fn Lang::iso639_1(self : Lang) -> String? {
  let code = match self {
    Spa => "es"
    Eng => "en"
    Por => "pt"
    Ind => "id"
    Fra => "fr"
    Deu => "de"
    Jav => "jv"
    Vie => "vi"
    Ita => "it"
    Tur => "tr"
    Pol => "pl"
    Swh => "sw"
    Sun => "su"
    Ron => "ro"
    Hau => "ha"
    Bos => "bs"
    Hrv => "hr"
    Nld => "nl"
    Srp => "sr"
    Yor => "yo"
    Uzn => "uz"
    Zlm => "ms"
    Ibo => "ig"
    Tgl => "tl"
    Hun => "hu"
    Azj => "az"
    Ces => "cs"
    Run => "rn"
    Plt => "mg"
    Nya => "ny"
    Kin => "rw"
    Zul => "zu"
    Swe => "sv"
    Lin => "ln"
    Som => "so"
    Rus => "ru"
    Ukr => "uk"
    Bel => "be"
    Bul => "bg"
    Kaz => "kk"
    Arb => "ar"
    Urd => "ur"
    Pes => "fa"
    Pbu => "ps"
    Hin => "hi"
    Mar => "mr"
    Npi => "ne"
    Cmn => "zh"
    Ben => "bn"
    Jpn => "ja"
    Kor => "ko"
    Tel => "te"
    Tam => "ta"
    Guj => "gu"
    Kan => "kn"
    Mal => "ml"
    Mya => "my"
    Pan => "pa"
    Amh => "am"
    Tha => "th"
    Sin => "si"
    Ell => "el"
    Fuv
    | Ckb
    | Ceb
    | Qug
    | Mad
    | Zyb
    | Hms
    | Hnj
    | Ilo
    | Koi
    | Skr
    | Mai
    | Bho
    | Mag => return None
  }
  Some(code)
}

///|
/// A BCP 47 language tag for the language: its ISO 639-1 code (see
/// `iso639_1`) when it has one, otherwise its ISO 639-3 code. Both are
/// valid primary language subtags.
pub fn Lang::bcp47(self : Lang) -> String {
  self.iso639_1().unwrap_or(self.code())
}

///|
/// Macrolanguages whose main member is supported, by their ISO 639-1 and
/// ISO 639-3 codes.
let macrolanguages : Array[(String, Lang)] = [
  ("zh", Cmn),
  ("zho", Cmn),
  ("ar", Arb),
  ("ara", Arb),
  ("fa", Pes),
  ("fas", Pes),
  ("ms", Zlm),
  ("msa", Zlm),
  ("uz", Uzn),
  ("uzb", Uzn),
  ("az", Azj),
  ("aze", Azj),
  ("mg", Plt),
  ("mlg", Plt),
  ("sw", Swh),
  ("swa", Swh),
  ("ps", Pbu),
  ("pus", Pbu),
  ("ne", Npi),
  ("nep", Npi),
]

///|
/// ISO 639-2/B codes and other codes still in use that name a supported
/// language. Filipino is standardised Tagalog; `in` is the withdrawn code
/// for Indonesian that some systems still send.
let other_codes : Array[(String, Lang)] = [
  ("chi", Cmn),
  ("per", Pes),
  ("may", Zlm),
  ("fre", Fra),
  ("ger", Deu),
  ("dut", Nld),
  ("cze", Ces),
  ("rum", Ron),
  ("gre", Ell),
  ("bur", Mya),
  ("fil", Tgl),
  ("in", Ind),
]

///|
fn is_letters(s : StringView) -> Bool {
  s.iter().all(c => c >= 'a' && c <= 'z')
}

///|
/// The language of a BCP 47 tag such as `pt-BR`, `zh-Hant-TW`, `sr-Latn`
/// or `es_419`, from its primary language subtag, matched without regard
/// to ASCII case: ISO 639-1 codes (see `iso639_1`), ISO 639-3 codes (see
/// `code`), the macrolanguages named in `iso639_1`, and ISO 639-2/B codes
/// such as `ger`. After a macrolanguage, an extended language subtag
/// decides instead (`zh-cmn` is Mandarin, `zh-yue` Cantonese and so
/// `None`). Script, region and other subtags are ignored, and `_` is
/// accepted as a separator. `None` for other languages, including
/// macrolanguages whose main member is not supported (`ku`, `ff`), and for
/// malformed tags.
pub fn Lang::from_bcp47(tag : String) -> Lang? {
  let subtags = tag
    .to_lower()
    .replace_all(old="_", new="-")
    .split("-")
    .collect()
  let primary = subtags[0].to_owned()
  if primary.length() < 2 || primary.length() > 3 || !is_letters(primary) {
    return None
  }
  if macrolanguages.search_by(e => e.0 == primary) is Some(i) {
    if subtags.length() > 1 &&
      subtags[1].length() == 3 &&
      is_letters(subtags[1]) {
      return Lang::from_code(subtags[1].to_owned())
    }
    return Some(macrolanguages[i].1)
  }
  if primary.length() == 3 && Lang::from_code(primary) is Some(lang) {
    return Some(lang)
  }
  for lang in all_langs {
    if lang.iso639_1() is Some(code) && code == primary {
      return Some(lang)
    }
  }
  match other_codes.search_by(e => e.0 == primary) {
    Some(i) => Some(other_codes[i].1)
    None => None
  }
}

///|
/// The name of the language in the language itself (its endonym). These
/// were written for mbglot; where a language has several common names,
/// the more widely used one is given.
pub fn Lang::native_name(self : Lang) -> String {
  match self {
    Spa => "español"
    Eng => "English"
    Por => "português"
    Ind => "Bahasa Indonesia"
    Fra => "français"
    Deu => "Deutsch"
    Jav => "Basa Jawa"
    Vie => "Tiếng Việt"
    Ita => "italiano"
    Tur => "Türkçe"
    Pol => "polski"
    Swh => "Kiswahili"
    Sun => "Basa Sunda"
    Ron => "română"
    Hau => "Hausa"
    Fuv => "Fulfulde"
    Bos => "bosanski"
    Hrv => "hrvatski"
    Nld => "Nederlands"
    Srp => "српски"
    Ckb => "کوردی"
    Yor => "Yorùbá"
    Uzn => "oʻzbekcha"
    Zlm => "Bahasa Melayu"
    Ibo => "Igbo"
    Ceb => "Sinugbuanong Binisayâ"
    Tgl => "Tagalog"
    Hun => "magyar"
    Azj => "azərbaycanca"
    Ces => "čeština"
    Run => "Ikirundi"
    Plt => "Malagasy"
    Qug => "Kichwa"
    Mad => "Basa Madhura"
    Nya => "Chichewa"
    Zyb => "Vahcuengh"
    Kin => "Ikinyarwanda"
    Zul => "isiZulu"
    Swe => "svenska"
    Lin => "lingála"
    Som => "Soomaali"
    Hms => "Hmub"
    Hnj => "Hmoob Ntsuab"
    Ilo => "Ilokano"
    Rus => "русский"
    Ukr => "українська"
    Koi => "перем коми"
    Bel => "беларуская"
    Bul => "български"
    Kaz => "қазақ тілі"
    Arb => "العربية"
    Urd => "اردو"
    Pes => "فارسی"
    Skr => "سرائیکی"
    Pbu => "پښتو"
    Hin => "हिन्दी"
    Mar => "मराठी"
    Mai => "मैथिली"
    Bho => "भोजपुरी"
    Npi => "नेपाली"
    Mag => "मगही"
    Cmn => "中文"
    Ben => "বাংলা"
    Jpn => "日本語"
    Kor => "한국어"
    Tel => "తెలుగు"
    Tam => "தமிழ்"
    Guj => "ગુજરાતી"
    Kan => "ಕನ್ನಡ"
    Mal => "മലയാളം"
    Mya => "မြန်မာဘာသာ"
    Pan => "ਪੰਜਾਬੀ"
    Amh => "አማርኛ"
    Tha => "ไทย"
    Sin => "සිංහල"
    Ell => "Ελληνικά"
  }
}