///|
fn lower_ascii(value : String) -> String {
  let output = StringBuilder()
  for char in value {
    output.write_char(char.to_ascii_lowercase())
  }
  output.to_string()
}

///|
fn glob_from(
  pattern : String,
  pattern_index : Int,
  value : String,
  value_index : Int,
) -> Bool {
  let mut pattern_pos = pattern_index
  let mut value_pos = value_index
  let mut last_star = -1
  let mut retry_value_pos = value_index
  while value_pos < value.length() {
    if pattern_pos < pattern.length() &&
      (pattern[pattern_pos] == '?' || pattern[pattern_pos] == value[value_pos]) {
      pattern_pos += 1
      value_pos += 1
    } else if pattern_pos < pattern.length() && pattern[pattern_pos] == '*' {
      last_star = pattern_pos
      pattern_pos += 1
      retry_value_pos = value_pos
    } else if last_star >= 0 {
      retry_value_pos += 1
      value_pos = retry_value_pos
      pattern_pos = last_star + 1
    } else {
      return false
    }
  }
  while pattern_pos < pattern.length() && pattern[pattern_pos] == '*' {
    pattern_pos += 1
  }
  pattern_pos == pattern.length()
}

///|
/// Maximum accepted pattern length for the bounded public matching APIs.
pub let default_pattern_length_limit : Int = 4 * 1024

///|
/// Maximum number of entries accepted by one compiled pattern-list.
pub let default_pattern_list_limit : Int = 256

///|
/// Maximum accepted host or Match-context value length.
pub let default_match_value_length_limit : Int = 64 * 1024

///|
/// Match one case-insensitive OpenSSH host glob containing `*` and `?`.
///
/// This P0 matcher folds ASCII only. Inputs exceeding
/// `default_pattern_length_limit` (or the value limit) are rejected as a
/// non-match so that the compatibility Bool API remains allocation-free and
/// non-throwing.
pub fn host_matches(pattern : String, host : String) -> Bool {
  if pattern.length() == 0 ||
    pattern == "!" ||
    pattern.length() > default_pattern_length_limit ||
    host.length() > default_match_value_length_limit {
    false
  } else {
    glob_from(lower_ascii(pattern), 0, lower_ascii(host), 0)
  }
}

///|
/// Match an OpenSSH pattern-list. A matching negated item always excludes the
/// host; otherwise at least one positive item must match.
pub fn patterns_match(patterns : Array[String], host : String) -> Bool {
  if patterns.length() > default_pattern_list_limit ||
    host.length() > default_match_value_length_limit {
    return false
  }
  let positive_match = Ref(false)
  let normalized_host = lower_ascii(host)
  for item in patterns {
    if item.length() == 0 ||
      item == "!" ||
      item.length() > default_pattern_length_limit {
      return false
    }
    let normalized_pattern = lower_ascii(item)
    if item.length() > 0 && item[0] == '!' {
      if glob_from(normalized_pattern, 1, normalized_host, 0) {
        return false
      }
    } else if glob_from(normalized_pattern, 0, normalized_host, 0) {
      positive_match.val = true
    }
  }
  positive_match.val
}