// As-you-type formatting: render the digits as they arrive.
//
// A number is not typed in one go, and the grouping changes as it grows: "0"
// becomes "02", "020", "020 7", "020 79", ... The formatter keeps exactly the
// digits entered, interprets a supplied trunk prefix and uses the plan's own
// formatting rules after every keystroke, so the intermediate forms come from the
// same table the full renderings use.

///|
/// A formatter that grows one digit at a time.
pub(all) struct AsYouTypeFormatter {
  priv region : String
  priv mut digits : String
}

///|
/// Start a formatter for a region. An unknown region still consumes digits; it
/// simply has no rules to group them with.
pub fn AsYouTypeFormatter::new(region : String) -> AsYouTypeFormatter {
  { region, digits: "", }
}

///|
/// Forget what has been typed.
pub fn AsYouTypeFormatter::clear(self : AsYouTypeFormatter) -> Unit {
  self.digits = ""
}

///|
/// The rendering of the digits typed so far.
pub fn AsYouTypeFormatter::current_output(self : AsYouTypeFormatter) -> String {
  let text = self.digits
  if starts_with(text, "+") {
    let body = digits_of(text)
    let cc = match country_code_prefix(body) {
      None => return text
      Some(cc) => cc
    }
    let nsn = match drop(body, cc.to_string().length()) {
      None => return text
      Some(s) => s
    }
    if nsn == "" {
      return text
    }
    let region = match region_by_calling_code(cc) {
      None => return "+" + cc.to_string() + " " + nsn
      Some(code) => code
    }
    return match find_region(region) {
      None => text
      Some(r) =>
        "+" + cc.to_string() + " " + render_partial(r, nsn, false, true)
    }
  }
  match find_region(self.region) {
    None => text
    Some(r) =>
      if text == "" {
        ""
      } else {
        let prefix = r.national_prefix
        if prefix != "" && starts_with(text, prefix) {
          let rest = match drop(text, prefix.length()) {
            Some(s) => s
            None => ""
          }
          if rest == "" {
            text
          } else if r.calling_code == 1 {
            prefix + " " + render_partial(r, rest, false, false)
          } else {
            render_partial(r, rest, true, false)
          }
        } else if prefix != "" && r.calling_code != 1 {
          // Do not fabricate a trunk prefix the user has not typed.
          text
        } else {
          render_partial(r, text, false, false)
        }
      }
  }
}

///|
/// Add one digit and return the new rendering. Non-digits are ignored, which is
/// what a key handler wants: the caller does not have to filter.
pub fn AsYouTypeFormatter::input_digit(
  self : AsYouTypeFormatter,
  digit : Char,
) -> String {
  if is_digit(digit) || (digit == '+' && self.digits == "") {
    self.digits = self.digits + digit.to_string()
  }
  self.current_output()
}

///|
/// Split the digits available so far over the rule's groups: complete groups take
/// their full width, the last one takes what is left.
fn distribute_prefix(
  groups : Array[FormatGroup],
  total : Int32,
) -> Array[Int32]? {
  let lens : Array[Int32] = []
  let mut remaining = total
  let mut i = 0
  while i < groups.length() && remaining > 0 {
    let g = groups[i]
    let take = if remaining < g.max || g.max < 0 { remaining } else { g.max }
    if take == 0 {
      break
    }
    lens.push(take)
    remaining = remaining - take
    i = i + 1
  }
  if lens.length() == 0 || remaining != 0 {
    None
  } else {
    Some(lens)
  }
}

///|
/// Render a prefix of a national number through the first rule that fits it.
fn render_partial(
  r : Region,
  digits : String,
  trunk : Bool,
  international : Bool,
) -> String {
  let cs = chars_of(digits)
  for f in formats_of(r) {
    // Local-only NANP rules must not compete with the complete-number
    // template, otherwise the layout switches at seven digits.
    if r.calling_code == 1 && f.intl_format == "NA" {
      continue
    }
    if !format_leading_matches(f, digits) {
      continue
    }
    let groups = match parse_format_groups(f.pattern) {
      None => continue
      Some(g) => g
    }
    let lens = match distribute_prefix(groups, cs.length()) {
      None => continue
      Some(l) => l
    }
    // A rule whose first group is wider than what has been typed has nothing to
    // group yet; leave the digits alone until it does.
    if lens.length() == 0 || lens[0] < groups[0].min {
      continue
    }
    let rule = if international {
      if f.intl_format == "NA" {
        continue
      }
      if f.intl_format != "" {
        f.intl_format
      } else {
        f.format
      }
    } else if trunk && f.prefix_rule != "" {
      expand_prefix_rule(f.prefix_rule, r.national_prefix) +
      format_tail(f.format)
    } else {
      f.format
    }
    let rendered = apply_partial_format(rule, cs, lens)
    let expected = if trunk { r.national_prefix + digits } else { digits }
    // A partial rule must preserve every typed digit, including on overflow.
    if digits_of(rendered) == expected {
      return rendered
    }
  }
  if trunk {
    r.national_prefix + digits
  } else {
    digits
  }
}

///|
/// Stop before separators belonging to a group that has not been entered yet.
fn apply_partial_format(
  rule : String,
  digits : Array[Char],
  lens : Array[Int32],
) -> String {
  let cs = chars_of(rule)
  let mut end = cs.length()
  let mut i = 0
  while i + 1 < cs.length() {
    if cs[i] == '$' &&
      cs[i + 1] >= '1' &&
      cs[i + 1] <= '9' &&
      cs[i + 1].to_int() - '1'.to_int() >= lens.length() {
      end = i
      break
    }
    i = i + 1
  }
  while end > 0 &&
        (cs[end - 1] == ' ' || cs[end - 1] == '-' || cs[end - 1] == '.') {
    end = end - 1
  }
  apply_format_rule(slice(cs, 0, end), digits, lens)
}

///|
test "as-you-type grows through the plan's rules" {
  let f = AsYouTypeFormatter::new("GB")
  let mut last = ""
  for c in "02079460958" {
    last = f.input_digit(c)
  }
  assert_eq(last, "020 7946 0958")
  f.clear()
  assert_eq(f.current_output(), "")
  // An unknown region keeps the digits without inventing a grouping.
  let g = AsYouTypeFormatter::new("ZZ")
  let mut out = ""
  for c in "12345" {
    out = g.input_digit(c)
  }
  assert_eq(out, "12345")
}