///|
fn ipv4(value : String) -> Bool {
  let parts = value.split(".").to_array()
  if parts.length() != 4 {
    return false
  }
  for part in parts {
    let a = part.to_owned().to_array()
    if a.is_empty() ||
      a.length() > 3 ||
      !a.all(ascii_digit) ||
      (a.length() > 1 && a[0] == '0') {
      return false
    }
    let mut n = 0
    for c in a {
      n = n * 10 + c.to_int() - 48
    }
    if n > 255 {
      return false
    }
  }
  true
}

///|
fn ip_literal(value : String) -> Bool {
  let a = value.to_array()
  if a.length() > 2 && (a[0] == 'v' || a[0] == 'V') {
    let mut pos = 1
    while pos < a.length() && hex_digit(a[pos]) {
      pos += 1
    }
    if pos == 1 || pos + 1 >= a.length() || a[pos] != '.' {
      return false
    }
    for i = pos + 1; i < a.length(); i = i + 1 {
      if !ascii_alpha(a[i]) &&
        !ascii_digit(a[i]) &&
        !"-._~!$&'()*+,;=:".contains(a[i].to_string()) {
        return false
      }
    }
    return true
  }
  let compressed = value.contains("::")
  if (value.has_prefix(":") && !value.has_prefix("::")) ||
    (value.has_suffix(":") && !value.has_suffix("::")) {
    return false
  }
  let halves = value.split("::").to_array()
  if halves.length() > 2 || value.contains(":::") {
    return false
  }
  let mut groups = 0
  let pieces = value.split(":").to_array()
  for i, piece in pieces {
    if piece.is_empty() {
      if !compressed {
        return false
      }
      continue
    }
    let text = piece.to_owned()
    if text.contains(".") {
      if i != pieces.length() - 1 || !ipv4(text) {
        return false
      }
      groups += 2
    } else {
      let chars = text.to_array()
      if chars.length() > 4 || !chars.all(hex_digit) {
        return false
      }
      groups += 1
    }
  }
  if compressed {
    groups < 8
  } else {
    groups == 8
  }
}

///|
/// RFC 3987 ucschar excludes private use, surrogates and plane-end noncharacters.
fn iri_unicode(c : Char, query : Bool) -> Bool {
  let n = c.to_int()
  (n >= 0xA0 && n <= 0xD7FF) ||
  (n >= 0xF900 && n <= 0xFDCF) ||
  (n >= 0xFDF0 && n <= 0xFFEF) ||
  (n >= 0x10000 && n <= 0xDFFFD && n % 65536 <= 0xFFFD) ||
  (n >= 0xE1000 && n <= 0xEFFFD) ||
  (
    query &&
    (
      (n >= 0xE000 && n <= 0xF8FF) ||
      (n >= 0xF0000 && n <= 0xFFFFD) ||
      (n >= 0x100000 && n <= 0x10FFFD)
    )
  )
}