///|
fn match_bban_pattern(bban : String, pattern : String) -> Int? {
  let mut pi = 0
  let mut bi = 0
  while pi < pattern.length() {
    let mut count = 0
    let mut saw_digit = false
    while pi < pattern.length() && is_digit(pattern[pi]) {
      saw_digit = true
      count = count * 10 + (pattern[pi].to_int() - 48)
      pi += 1
    }
    if !saw_digit || count <= 0 || pi >= pattern.length() {
      return Some(bi)
    }
    let cls = pattern[pi]
    pi += 1
    for _ in 0..= bban.length() {
        return Some(bi)
      }
      let c = bban[bi]
      let ok = match cls {
        'n' => is_digit(c)
        'a' => is_letter(c)
        'c' => is_alnum(c)
        _ => false
      }
      if !ok {
        return Some(bi)
      }
      bi += 1
    }
  }
  if bi != bban.length() {
    return Some(bi)
  }
  None
}

///|
fn country_code_ok(compact : String) -> Bool {
  compact.length() >= 2 && is_letter(compact[0]) && is_letter(compact[1])
}

///|
pub fn parse(input : String) -> Result[Iban, IbanError] {
  let compact = compact_text(input)
  if compact.length() == 0 {
    return Err(EmptyInput)
  }
  match charset_offset(compact) {
    Some(offset) => return Err(InvalidCharset(offset))
    None => ()
  }
  if compact.length() < 15 {
    return Err(TooShort(compact.length()))
  }
  if compact.length() > 34 {
    return Err(TooLong(compact.length()))
  }
  if !country_code_ok(compact) {
    return Err(InvalidCountry(compact[0:2].to_owned()))
  }
  if compact.length() < 4 || !is_digit(compact[2]) || !is_digit(compact[3]) {
    return Err(InvalidCharset(2))
  }
  let country = compact[0:2].to_owned()
  match country_spec(country) {
    None => Err(UnknownCountry(country))
    Some(spec) => {
      if compact.length() != spec.length {
        return Err(LengthMismatch(country, spec.length, compact.length()))
      }
      let bban = compact[4:].to_owned()
      match match_bban_pattern(bban, spec.bban_pattern) {
        Some(offset) => Err(BbanPattern(country, offset))
        None => {
          let rem = remainder97(compact)
          if rem != 1 {
            Err(BadChecksum(rem))
          } else {
            Ok({
              country,
              check_digits: compact[2:4].to_owned(),
              bban,
              compact,
            })
          }
        }
      }
    }
  }
}

///|
pub fn validate(input : String) -> Result[Iban, IbanError] {
  parse(input)
}