// Numbering-plan metadata: one row per region, describing how to dial into and
// out of it and which national-number lengths it uses.
//
// The numbers come from Google's libphonenumber metadata (Apache-2.0), which in
// turn tracks the national number plans. Only the length rules are reproduced
// here; the leading-digit patterns are not, so `possible` is a length test and
// not a proof that a line is assigned. See the README for that boundary.

///|
/// A numbering plan.
///
/// `idd` is the prefix dialled to reach another country from inside this one.
/// `national_prefix` is the trunk prefix dialled before a national number; it is
/// empty for plans (Italy, Portugal, Spain, ...) that have none. `types` is a
/// compact per-type length spec, expanded by `parse_type_spec`.
priv struct Region {
  code : String
  name : String
  calling_code : Int
  idd : String
  national_prefix : String
  types : String
  primary : Bool
}

///|
/// One table row. Keeping this a function keeps the table below to one line per
/// region and lets the field order read in the order the data is published.
fn row(
  code : String,
  name : String,
  calling_code : Int,
  idd : String,
  national_prefix : String,
  types : String,
) -> Region {
  { code, name, calling_code, idd, national_prefix, types, primary: false }
}

///|
/// A row for the region that owns a shared calling code. When several regions
/// share one code (`+1` covers both the United States and Canada here), the
/// primary region is the one a bare calling code resolves to.
fn row_primary(
  code : String,
  name : String,
  calling_code : Int,
  idd : String,
  national_prefix : String,
  types : String,
) -> Region {
  { code, name, calling_code, idd, national_prefix, types, primary: true }
}

///|
/// The whole table, sorted by region code.
let regions : Array[Region] = [
  row("AE", "United Arab Emirates", 971, "00", "0", "f=8 m=9 t=5|6|7|8|9|10|11|12 p=9 s=9 u=9"),
  row("AR", "Argentina", 54, "00", "0", "f=10 m=10|11 t=10|11 p=10 u=10"),
  row("AT", "Austria", 43, "00", "0", "f=4|5|6|7|8|9|10|11|12|13 m=7|8|9|10|11|12|13 t=9|10|11|12|13 p=9|10|11|12|13 s=8|9|10|11|12|13 v=5|6|7|8|9|10|11|12|13"),
  row("AU", "Australia", 61, "0011", "0", "f=9 m=9 t=7|10 p=10 s=6|8|10|12 v=9 g=5|6|7|8|9"),
  row("BD", "Bangladesh", 880, "00", "0", "f=6|7|8|9|10 m=10 t=10 v=10"),
  row("BE", "Belgium", 32, "00", "0", "f=8 m=9 t=8 p=8 s=8 u=8"),
  row("BR", "Brazil", 55, "00", "0", "f=10 m=10|11 t=9|10 p=9|10 s=8|10"),
  row("CA", "Canada", 1, "011", "1", "f=10 m=10 t=10 p=10 v=10 u=7 r=10"),
  row("CH", "Switzerland", 41, "00", "0", "f=9 m=9 t=9 p=9 s=9 u=9 r=9 g=9 o=12"),
  row("CL", "Chile", 56, "00", "", "f=9 m=9 t=9|11 s=10|11 v=9"),
  row("CN", "China", 86, "00", "0", "f=7|8|9|10|11 m=11 t=10|12 p=8 s=7|8|9|10|11"),
  row("CO", "Colombia", 57, "00", "0", "f=8|10 m=10 t=11 p=10|11"),
  row("CZ", "Czechia", 420, "00", "", "f=9 m=9 t=9 p=9 s=9 v=9 u=9 r=9 o=9|10|11|12"),
  row("DE", "Germany", 49, "00", "0", "f=5|6|7|8|9|10|11|12|13|14|15 m=10|11 t=10|11|12|13|14|15 p=10|11 s=7|8|9|10|11|12|13|14 u=8|9|10|11|12|13|14 r=11 g=4|5|6|7|8|9|10|11|12|13|14 o=12|13"),
  row("DK", "Denmark", 45, "00", "", "f=8 m=8 t=8 p=8"),
  row("EG", "Egypt", 20, "00", "0", "f=8|9 m=10 t=10 p=10"),
  row("ES", "Spain", 34, "00", "", "f=9 m=9 t=9 p=9 s=9 u=9 r=9"),
  row("FI", "Finland", 358, "00", "0", "f=5|6|7|8|9 m=6|7|8|9|10 t=7|8|9 p=8|9 u=5|6|7|8|9|10|11|12"),
  row("FR", "France", 33, "00", "0", "f=9 m=9 t=9 p=9 s=9 v=9 u=9"),
  row("GB", "United Kingdom", 44, "00", "0", "f=9|10 m=10 t=7|9|10 p=7|10 v=10 u=10 r=10 g=10"),
  row("GR", "Greece", 30, "00", "", "f=10 m=10 t=10|11|12 p=10 s=10 u=10 r=10"),
  row("HK", "Hong Kong", 852, "00", "", "f=8 m=8 t=9 p=5|6|7|8|11 u=8 r=8 g=8"),
  row("ID", "Indonesia", 62, "00", "0", "f=7|8|9|10|11 m=9|10|11|12 t=8|9|10|11|12|13|14|15|16|17 p=10 s=10 u=7|10"),
  row("IE", "Ireland", 353, "00", "0", "f=7|8|9|10 m=9 t=10 p=10 s=10 v=9 u=9 r=9 o=10"),
  row("IL", "Israel", 972, "00", "0", "f=8|11|12 m=9 t=7|10 p=8|10 s=10 v=9 u=10 o=11|12"),
  row("IN", "India", 91, "00", "0", "f=10 m=10 t=8|9|10|11|12|13 p=13 s=11 u=10"),
  row("IT", "Italy", 39, "00", "", "f=6|7|8|9|10|11|12 m=9|10 t=6|9 p=6|8|9|10 s=6|9 v=10 r=9|10 o=11|12"),
  row("JP", "Japan", 81, "010", "0", "f=9 m=10 t=8|9|10|11|12|13|14|15|16|17 p=9 v=10 u=9 r=9 g=10"),
  row("KE", "Kenya", 254, "000", "0", "f=7|8|9 m=9 t=9|10 p=9"),
  row("KR", "South Korea", 82, "00", "0", "f=5|6|8|9|10 m=9|10 t=9|11|12|13|14 p=9 v=10 u=8 r=10|11 g=9|10"),
  row("MX", "Mexico", 52, "00", "", "f=10 m=10 t=10 p=10 s=10 r=10"),
  row("MY", "Malaysia", 60, "00", "0", "f=8|9 m=9|10 t=10 p=10 v=10"),
  row("NG", "Nigeria", 234, "009", "0", "f=10 m=10 t=10|11|12|13|14 u=10|11|12|13|14"),
  row("NL", "Netherlands", 31, "00", "0", "f=9 m=9|11 t=7|8|9|10 p=7|8|9|10 v=9 u=5|6|9 g=9"),
  row("NO", "Norway", 47, "00", "", "f=8 m=8 t=8 p=8 s=8 v=8 u=5|8 r=8 o=8"),
  row("NZ", "New Zealand", 64, "00", "0", "f=8 m=8|9|10 t=8|9|10 p=7|8|9|10 u=5|6|7|8|9|10 r=9"),
  row("PH", "Philippines", 63, "00", "0", "f=6|8|9|10 m=10 t=11|12|13"),
  row("PK", "Pakistan", 92, "00", "0", "f=9|10 m=10 t=8|11 p=8 u=11|12 r=9"),
  row("PL", "Poland", 48, "00", "", "f=7|9 m=9 t=9|10 p=9 s=9 v=9 u=9 g=6|7|8|9"),
  row("PT", "Portugal", 351, "00", "", "f=9 m=9 t=9 p=9 s=9 v=9 u=9 r=9 g=9 o=9"),
  row("RU", "Russia", 7, "810", "8", "f=10 m=10 t=10|14 p=10 r=10"),
  row("SA", "Saudi Arabia", 966, "00", "0", "f=9 m=9 t=10 p=9 s=9"),
  row("SE", "Sweden", 46, "00", "0", "f=7|8|9 m=9 t=6|7|8|9 p=7|8|9|10 s=9 u=9 r=9 g=9 o=12"),
  row("SG", "Singapore", 65, "00", "", "f=8 m=8 t=10|11 p=11 v=8 u=11"),
  row("TH", "Thailand", 66, "00", "0", "f=8 m=9 t=10|13 p=10 v=9"),
  row("TR", "Turkey", 90, "00", "0", "f=10 m=10 t=10|12|13 p=10 v=10 u=7 r=10 g=10"),
  row("TW", "Taiwan", 886, "00", "0", "f=8|9 m=9 t=8|9 p=7|9 v=10|11 u=9 r=9"),
  row("UA", "Ukraine", 380, "00", "0", "f=9 m=9 t=9|10 p=9|10 v=9"),
  row_primary("US", "United States", 1, "011", "1", "f=10 m=10 t=10 p=10 r=10"),
  row("VN", "Vietnam", 84, "00", "0", "f=10 m=9 t=8|9|10 p=8|9|10 v=9 u=7|8"),
  row("ZA", "South Africa", 27, "00", "0", "f=9 m=5|6|7|8|9 t=9 p=9 s=9 v=9 u=9|10"),
]

///|
/// Uppercase a region code so lookups are case-insensitive.
fn normalize_code(code : String) -> String {
  let buf = StringBuilder()
  for c in code {
    buf.write_char(upper_ascii(c))
  }
  buf.to_string()
}

///|
/// Look a region up by its ISO 3166-1 alpha-2 code, case-insensitively.
fn find_region(code : String) -> Region? {
  let want = normalize_code(code)
  for r in regions {
    if r.code == want {
      return Some(r)
    }
  }
  None
}

///|
/// The letter that stands for a number type inside a `types` spec.
fn type_of_letter(c : Char) -> NumberType? {
  match c {
    'f' => Some(FixedLine)
    'm' => Some(Mobile)
    't' => Some(TollFree)
    'p' => Some(PremiumRate)
    's' => Some(SharedCost)
    'v' => Some(Voip)
    'u' => Some(Uan)
    'r' => Some(PersonalNumber)
    'g' => Some(Pager)
    'o' => Some(Voicemail)
    _ => None
  }
}

///|
/// Expand a `types` spec such as `"f=8 m=9|10"` into `(type, lengths)` pairs.
/// Unknown letters and malformed length fields are skipped rather than rejected:
/// the spec is an internal constant.
fn parse_type_spec(spec : String) -> Array[(NumberType, Array[Int])] {
  let out : Array[(NumberType, Array[Int])] = []
  for token in split_on(spec, ' ') {
    if token == "" {
      continue
    }
    let parts = split_on(token, '=')
    if parts.length() != 2 {
      continue
    }
    let letter = chars_of(parts[0])
    if letter.length() != 1 {
      continue
    }
    let ty = match type_of_letter(letter[0]) {
      Some(t) => t
      None => continue
    }
    let lens : Array[Int] = []
    for field in split_on(parts[1], '|') {
      match int_of_digits(field) {
        Some(v) => lens.push(v)
        None => ()
      }
    }
    out.push((ty, lens))
  }
  out
}

///|
/// The `(type, lengths)` pairs of a region, expanded from its spec.
fn type_lengths(r : Region) -> Array[(NumberType, Array[Int])] {
  parse_type_spec(r.types)
}

///|
/// Every national-number length the region uses, sorted and without repeats.
fn lengths_of(r : Region) -> Array[Int] {
  let out : Array[Int] = []
  for pair in type_lengths(r) {
    for n in pair.1 {
      push_unique_sorted(out, n)
    }
  }
  out
}

///|
/// Insert `v` into a sorted, unique array, keeping it sorted.
fn push_unique_sorted(a : Array[Int], v : Int) -> Unit {
  let mut i = 0
  while i < a.length() && a[i] < v {
    i = i + 1
  }
  if i < a.length() && a[i] == v {
    return
  }
  a.insert(i, v)
}

///|
/// The number types whose length rule admits an `n`-digit national number.
fn types_for_length(r : Region, n : Int) -> Array[NumberType] {
  let out : Array[NumberType] = []
  for pair in type_lengths(r) {
    if pair.1.contains(n) {
      out.push(pair.0)
    }
  }
  out
}

// ---------------------------------------------------------------------------
// Public lookups
// ---------------------------------------------------------------------------

///|
/// Every region code in the table, sorted.
pub fn region_codes() -> Array[String] {
  let out : Array[String] = []
  for r in regions {
    out.push(r.code)
  }
  out
}

///|
/// True when the region code is one the table knows.
pub fn region_exists(code : String) -> Bool {
  find_region(code) is Some(_)
}

///|
/// The table's own spelling of a region code, or None when it is not known.
/// Useful for echoing a user-supplied code in a canonical form.
pub fn canonical_region_code(code : String) -> String? {
  match find_region(code) {
    Some(r) => Some(r.code)
    None => None
  }
}

///|
/// The English name of a region, e.g. `"Japan"`.
pub fn region_name(code : String) -> String? {
  match find_region(code) {
    Some(r) => Some(r.name)
    None => None
  }
}

///|
/// The E.164 calling code of a region, without a leading `+`.
pub fn calling_code(code : String) -> Int? {
  match find_region(code) {
    Some(r) => Some(r.calling_code)
    None => None
  }
}

///|
/// The international dial-out prefix used inside a region, e.g. `"011"` for the
/// NANP or `"00"` for most of Europe.
pub fn idd_prefix(code : String) -> String? {
  match find_region(code) {
    Some(r) => Some(r.idd)
    None => None
  }
}

///|
/// The trunk prefix dialled before a national number, or None when the plan has
/// none (Italy, Spain, Portugal and others).
pub fn national_prefix(code : String) -> String? {
  match find_region(code) {
    Some(r) => if r.national_prefix == "" { None } else { Some(r.national_prefix) }
    None => None
  }
}

///|
/// The national-number lengths the region uses, sorted, without repeats.
pub fn possible_lengths(code : String) -> Array[Int]? {
  match find_region(code) {
    Some(r) => Some(lengths_of(r))
    None => None
  }
}

///|
/// One region that uses a calling code, or None when no region does. When
/// several share the code (`+1` is both the United States and Canada here) the
/// region marked primary wins; otherwise the first in code order. Use
/// `regions_by_calling_code` for all of them.
pub fn region_by_calling_code(cc : Int) -> String? {
  for r in regions {
    if r.calling_code == cc && r.primary {
      return Some(r.code)
    }
  }
  for r in regions {
    if r.calling_code == cc {
      return Some(r.code)
    }
  }
  None
}

///|
/// Every region that shares a calling code, in table order.
pub fn regions_by_calling_code(cc : Int) -> Array[String] {
  let out : Array[String] = []
  for r in regions {
    if r.calling_code == cc {
      out.push(r.code)
    }
  }
  out
}