///|
/// Localized CSL terms. The table is intentionally small but extensible: user
/// supplied locale XML can replace any term without changing style code.
pub(all) struct Locale {
  mut language : String
  terms : Map[String, String]
  months : Map[Int, String]
}

///|
pub fn Locale::english() -> Locale {
  let terms : Map[String, String] = Map([])
  terms["and"] = "and"
  terms["et-al"] = "et al."
  terms["page"] = "page"
  terms["pages"] = "pages"
  terms["chapter"] = "chapter"
  terms["volume"] = "volume"
  terms["issue"] = "issue"
  terms["no date"] = "n.d."
  let months : Map[Int, String] = Map([])
  months[1] = "January"
  months[2] = "February"
  months[3] = "March"
  months[4] = "April"
  months[5] = "May"
  months[6] = "June"
  months[7] = "July"
  months[8] = "August"
  months[9] = "September"
  months[10] = "October"
  months[11] = "November"
  months[12] = "December"
  { language: "en-US", terms, months, }
}

///|
pub fn Locale::chinese() -> Locale {
  let locale = Locale::english()
  locale.language = "zh-CN"
  locale.terms["and"] = "和"
  locale.terms["et-al"] = "等"
  locale.terms["page"] = "页"
  locale.terms["pages"] = "页"
  locale.terms["chapter"] = "章"
  locale.terms["volume"] = "卷"
  locale.terms["issue"] = "期"
  locale.terms["no date"] = "无日期"
  locale.months[1] = "1月"
  locale.months[2] = "2月"
  locale.months[3] = "3月"
  locale.months[4] = "4月"
  locale.months[5] = "5月"
  locale.months[6] = "6月"
  locale.months[7] = "7月"
  locale.months[8] = "8月"
  locale.months[9] = "9月"
  locale.months[10] = "10月"
  locale.months[11] = "11月"
  locale.months[12] = "12月"
  locale
}

///|
pub fn Locale::term(
  self : Locale,
  name : String,
  form? : String = "long",
) -> String {
  let key = if form == "short" && name == "page" {
    "page"
  } else if form == "short" && name == "pages" {
    "pages"
  } else {
    name
  }
  self.terms.get(key).unwrap_or(name)
}

///|
fn short_month(value : String) -> String {
  let out = StringBuilder()
  let chars = value.to_array()
  for i = 0; i < 3 && i < chars.length(); i = i + 1 {
    out.write_char(chars[i])
  }
  out.to_string()
}

///|
pub fn Locale::month(
  self : Locale,
  number : Int,
  form? : String = "long",
) -> String {
  match self.months.get(number) {
    Some(long) =>
      if form == "numeric" {
        number.to_string()
      } else if form == "short" && long.length() > 3 {
        short_month(long)
      } else {
        long
      }
    None => number.to_string()
  }
}

///|
pub fn parse_locale_xml(xml : String) -> Result[Locale, CslError] {
  let root = match parse_xml(xml) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  guard root.name == "locale" else {
    return Err(BadStyle("locale root element must be locale"))
  }
  let locale = Locale::english()
  locale.language = root.attr("xml:lang")
  if locale.language.is_empty() {
    locale.language = root.attr("lang")
  }
  for term in root.kids_named("terms") {
    for child in term.kids_named("term") {
      let name = child.attr("name")
      if !name.is_empty() {
        locale.terms[name] = child.text()
      }
    }
  }
  Ok(locale)
}

///|
pub fn locale_from_language(language : String) -> Locale {
  if language.has_prefix("zh") {
    Locale::chinese()
  } else {
    Locale::english()
  }
}

///|
pub fn format_localized_month(
  locale : Locale,
  date : CslDate,
  form : String,
) -> String {
  if date.month == 0 {
    return ""
  }
  locale.month(date.month, form~)
}

///|
pub fn format_localized_date(
  locale : Locale,
  date : CslDate,
  form? : String = "long",
) -> String {
  if date.literal != "" {
    return date.literal
  }
  if date.year == 0 {
    return locale.term("no date")
  }
  let month = if date.month == 0 { "" } else { locale.month(date.month, form~) }
  let day = if date.day == 0 { "" } else { date.day.to_string() }
  if locale.language.has_prefix("zh") {
    date.year.to_string() + "年" + month + day + "日"
  } else {
    join_nonempty([month, day, date.year.to_string()], " ")
  }
}

///|
pub fn localize_name_delimiter(locale : Locale, count : Int) -> String {
  if locale.language.has_prefix("zh") {
    "、"
  } else if count > 1 {
    ", "
  } else {
    ""
  }
}

///|
pub fn localize_and(locale : Locale) -> String {
  locale.term("and")
}

///|
pub fn localize_et_al(locale : Locale) -> String {
  locale.term("et-al")
}

///|
pub fn localize_label(
  locale : Locale,
  variable : String,
  count : Int,
) -> String {
  if variable == "page" {
    if count == 1 {
      locale.term("page")
    } else {
      locale.term("pages")
    }
  } else {
    locale.term(variable)
  }
}

///|
pub fn locale_terms(locale : Locale) -> Array[String] {
  let out = []
  locale.terms.each(fn(key, value) { out.push(key + "=" + value) })
  out.sort_by(fn(a, b) { cmp_str(a, b) })
  out
}

///|
pub fn locale_months(locale : Locale) -> Array[String] {
  let out = []
  for i = 1; i <= 12; i = i + 1 {
    out.push(i.to_string() + "=" + locale.month(i))
  }
  out
}

///|
pub fn merge_locales(base : Locale, replacement : Locale) -> Locale {
  let out = base
  if !replacement.language.is_empty() {
    out.language = replacement.language
  }
  replacement.terms.each(fn(key, value) { out.terms[key] = value })
  replacement.months.each(fn(key, value) { out.months[key] = value })
  out
}

///|
pub fn locale_debug(locale : Locale) -> String {
  locale.language + " {" + join_nonempty(locale_terms(locale), "; ") + "}"
}