///|
fn hex_value(ch : Char) -> Int {
  let n = ch.to_int()
  if n >= '0'.to_int() && n <= '9'.to_int() {
    n - '0'.to_int()
  } else if n >= 'a'.to_int() && n <= 'f'.to_int() {
    n - 'a'.to_int() + 10
  } else if n >= 'A'.to_int() && n <= 'F'.to_int() {
    n - 'A'.to_int() + 10
  } else {
    -1
  }
}

///|
fn ipv6_side_groups(
  side : String,
  offset : Int,
) -> Result[Array[Int], ProxyError] {
  if side.length() == 0 {
    return Ok(Array::make(0, 0))
  }
  let pieces = side.split(":").to_array()
  let result = Array::make(0, 0)
  let mut pos = offset
  for piece_view in pieces {
    let piece = piece_view.to_owned()
    let chars = piece.to_array()
    if chars.length() == 0 || chars.length() > 4 {
      return Err(
        proxy_error(
          InvalidIpv6,
          pos,
          "IPv6 group must be 1..4 hexadecimal digits",
        ),
      )
    }
    let mut value = 0
    for ch in chars {
      let digit = hex_value(ch)
      if digit < 0 {
        return Err(
          proxy_error(InvalidIpv6, pos, "invalid IPv6 hexadecimal digit"),
        )
      }
      value = (value << 4) | digit
    }
    result.push(value)
    pos = pos + piece.length() + 1
  }
  Ok(result)
}

///|
pub fn parse_ipv6_text(input : String) -> Result[Ipv6Address, ProxyError] {
  if input.length() == 0 || input.contains("%") || input.contains(".") {
    return Err(
      proxy_error(
        InvalidIpv6,
        0,
        "empty, scoped, and IPv4-embedded IPv6 text is rejected",
      ),
    )
  }
  let halves = input.split("::").to_array()
  if halves.length() > 2 {
    return Err(
      proxy_error(
        InvalidIpv6,
        0,
        "at most one :: compression marker is allowed",
      ),
    )
  }
  let compressed = halves.length() == 2
  let left = match ipv6_side_groups(halves[0].to_owned(), 0) {
    Err(err) => return Err(err)
    Ok(v) => v
  }
  let right = if compressed {
    match ipv6_side_groups(halves[1].to_owned(), halves[0].length() + 2) {
      Err(err) => return Err(err)
      Ok(v) => v
    }
  } else {
    Array::make(0, 0)
  }
  let group_count = left.length() + right.length()
  if (compressed && group_count >= 8) || (!compressed && group_count != 8) {
    return Err(proxy_error(InvalidIpv6, 0, "invalid IPv6 group count"))
  }
  let bytes = Array::make(16, b'\x00')
  let mut at = 0
  for value in left {
    bytes[at] = (value >> 8).to_byte()
    bytes[at + 1] = value.to_byte()
    at = at + 2
  }
  at = (8 - right.length()) * 2
  for value in right {
    bytes[at] = (value >> 8).to_byte()
    bytes[at + 1] = value.to_byte()
    at = at + 2
  }
  Ipv6Address::new(Bytes::from_array(bytes).to_fixedarray())
}

///|
fn group_hex(value : Int) -> String {
  if value == 0 {
    return "0"
  }
  let chars = Array::make(0, '0')
  let mut started = false
  for shift = 12; shift >= 0; shift = shift - 4 {
    let digit = (value >> shift) & 15
    if digit != 0 || started {
      chars.push(
        if digit < 10 {
          ('0'.to_int() + digit).to_char().unwrap()
        } else {
          ('a'.to_int() + digit - 10).to_char().unwrap()
        },
      )
      started = true
    }
  }
  String::from_array(chars)
}

///|
pub fn format_ipv6(address : Ipv6Address) -> String {
  let groups = Array::make(8, 0)
  for i = 0; i < 8; i = i + 1 {
    groups[i] = (address.octets[i * 2].to_int() << 8) |
      address.octets[i * 2 + 1].to_int()
  }
  let mut best_start = -1
  let mut best_len = 0
  let mut run_start = 0
  let mut run_len = 0
  for i = 0; i <= 8; i = i + 1 {
    if i < 8 && groups[i] == 0 {
      if run_len == 0 {
        run_start = i
      }
      run_len = run_len + 1
    } else {
      if run_len >= 2 && run_len > best_len {
        best_start = run_start
        best_len = run_len
      }
      run_len = 0
    }
  }
  let buffer = @buffer.Buffer(size_hint=40)
  let mut i = 0
  while i < 8 {
    if i == best_start {
      buffer.write_string_utf8("::")
      i = i + best_len
    } else {
      if i > 0 && i != best_start + best_len {
        buffer.write_string_utf8(":")
      }
      buffer.write_string_utf8(group_hex(groups[i]))
      i = i + 1
    }
  }
  let chars = Array::make(0, ' ')
  for byte in buffer.to_bytes() {
    chars.push(byte.to_int().to_char().unwrap())
  }
  String::from_array(chars)
}