///|
// Percent triplets are checked once before component grammar checks.
fn uri_component(text : StringView, kind : Int32) -> Bool {
  let mut at = 0
  while at < text.length() {
    let c = text.code_unit_at(at)
    let valid = if c == 37 {
      at += 3
      continue
    } else if kind == 0 {
      uri_unreserved(c) || uri_subdelimiter(c)
    } else if kind == 1 {
      uri_unreserved(c) || uri_subdelimiter(c) || c == 58
    } else if kind == 2 {
      uri_pchar(c) || c == 47
    } else {
      uri_pchar(c) || c == 47 || c == 63
    }
    if !valid {
      return false
    }
    at += 1
  }
  true
}

///|
fn valid_ipv4(text : StringView) -> Bool {
  if text.length() > 15 {
    return false
  }
  let parts = text.split(".").collect()
  if parts.length() != 4 {
    return false
  }
  for part in parts {
    if part.is_empty() ||
      part.length() > 3 ||
      (part.length() > 1 && part.code_unit_at(0) == 48) {
      return false
    }
    let mut number = 0
    for c in part.code_units() {
      if !uri_digit(c) {
        return false
      }
      number = number * 10 + c.to_int() - 48
    }
    if number > 255 {
      return false
    }
  }
  true
}

///|
fn ipv6_groups(text : StringView, ipv4_last : Bool) -> Int32? {
  if text.is_empty() {
    return Some(0)
  }
  let groups = text.split(":").collect()
  if groups.length() > 8 {
    return None
  }
  let mut count = 0
  for i, group in groups {
    if ipv4_last && i == groups.length() - 1 && group.contains(".") {
      if !valid_ipv4(group) {
        return None
      }
      count += 2
    } else {
      if group.is_empty() || group.length() > 4 {
        return None
      }
      for c in group.code_units() {
        if hex(c) is None {
          return None
        }
      }
      count += 1
    }
  }
  Some(count)
}

///|
fn valid_ipv6(text : StringView) -> Bool {
  if text.length() > 45 {
    return false
  }
  let parts = text.split("::").collect()
  if parts.length() == 1 {
    return ipv6_groups(text, true) == Some(8)
  }
  if parts.length() != 2 {
    return false
  }
  match (ipv6_groups(parts[0], false), ipv6_groups(parts[1], true)) {
    (Some(a), Some(b)) => a + b < 8
    _ => false
  }
}

///|
fn valid_ipvfuture(text : StringView) -> Bool {
  guard text.length() > 3 &&
    (text.code_unit_at(0) == 118 || text.code_unit_at(0) == 86) else {
    return false
  }
  guard text.find(".") is Some(dot) && dot > 1 && dot < text.length() - 1 else {
    return false
  }
  for i in 1.. Authority raise UriRead {
  let raw = text.to_owned()
  let mut rest = text
  let userinfo = if text.find("@") is Some(at) {
    let user = text[:at]
    if !uri_component(user, 1) {
      uri_invalid("Invalid URI user information")
    }
    rest = text[at + 1:]
    Some(user.to_owned())
  } else {
    None
  }
  let (host, port) = if rest.has_prefix("[") {
    guard rest.find("]") is Some(end) else {
      uri_invalid("Unclosed URI IP literal")
    }
    let address = rest[1:end]
    let host = if valid_ipv6(address) {
      Ipv6(address.to_owned())
    } else if valid_ipvfuture(address) {
      IpvFuture(address.to_owned())
    } else {
      uri_invalid("Invalid URI IP literal")
    }
    let suffix = rest[end + 1:]
    let port = if suffix.is_empty() {
      None
    } else if suffix.has_prefix(":") {
      Some(suffix[1:].to_owned())
    } else {
      uri_invalid("Unexpected text after URI IP literal")
    }
    (host, port)
  } else {
    let (name, port) = if rest.find(":") is Some(colon) {
      (rest[:colon], Some(rest[colon + 1:].to_owned()))
    } else {
      (rest, None)
    }
    if !uri_component(name, 0) {
      uri_invalid("Invalid URI registered host name")
    }
    let host = if valid_ipv4(name) {
      Ipv4(name.to_owned())
    } else {
      RegisteredName(name.to_owned())
    }
    (host, port)
  }
  if port is Some(port) {
    for c in port.code_units() {
      if !uri_digit(c) {
        uri_invalid("URI port must contain only digits")
      }
    }
  }
  { raw, userinfo, host, port, }
}

///|
fn read_uri(text : String) -> UriReference raise UriRead {
  let mut at = 0
  while at < text.length() {
    let c = text.code_unit_at(at)
    if c <= 32 || c >= 127 {
      uri_invalid("URI references require ASCII URI characters")
    }
    if c == 37 {
      if text.length() - at < 3 ||
        hex(text.code_unit_at(at + 1)) is None ||
        hex(text.code_unit_at(at + 2)) is None {
        uri_invalid("Invalid URI percent escape")
      }
      at += 2
    }
    at += 1
  }
  let mut hierarchy = text[:]
  let fragment = if text.find("#") is Some(hash) {
    hierarchy = text[:hash]
    let fragment = text[hash + 1:]
    if !uri_component(fragment, 3) {
      uri_invalid("Invalid URI fragment character")
    }
    Some(fragment.to_owned())
  } else {
    None
  }
  let query = if hierarchy.find("?") is Some(question) {
    let query = hierarchy[question + 1:]
    if !uri_component(query, 3) {
      uri_invalid("Invalid URI query character")
    }
    hierarchy = hierarchy[:question]
    Some(query.to_owned())
  } else {
    None
  }
  let scheme = if hierarchy.find(":") is Some(colon) &&
    (hierarchy.find("/") is None || colon < hierarchy.find("/").unwrap()) {
    if colon == 0 {
      uri_invalid("URI scheme must start with a letter")
    }
    for i in 0.. Result[UriReference, @json.Diagnostic] {
  if text.length() > limits.max_source() {
    return Err(
      @json.Diagnostic::new(@json.LimitExceeded, "URI source limit exceeded"),
    )
  }
  if limits.max_nodes() < 1 {
    return Err(
      @json.Diagnostic::new(@json.LimitExceeded, "URI value limit exceeded"),
    )
  }
  Ok(read_uri(text)) catch {
    UriRead(d) => Err(d)
  }
}