///|
pub struct Ipv6Address {
  hi : UInt64
  lo : UInt64
} derive(Eq, Compare, Debug)

///|
pub struct Ipv6Network {
  address : Ipv6Address
  prefix : Int
} derive(Eq, Debug)

///|
pub fn Ipv6Address::from_segments(
  s0 : Int,
  s1 : Int,
  s2 : Int,
  s3 : Int,
  s4 : Int,
  s5 : Int,
  s6 : Int,
  s7 : Int,
) -> Result[Ipv6Address, CidrError] {
  let segments = [s0, s1, s2, s3, s4, s5, s6, s7]
  let mut index = 0
  while index < segments.length() {
    if segments[index] < 0 || segments[index] > 65535 {
      return Err(OctetOutOfRange(index, segments[index]))
    }
    index = index + 1
  }
  Ok({
    hi: (s0.to_uint64() << 48) |
    (s1.to_uint64() << 32) |
    (s2.to_uint64() << 16) |
    s3.to_uint64(),
    lo: (s4.to_uint64() << 48) |
    (s5.to_uint64() << 32) |
    (s6.to_uint64() << 16) |
    s7.to_uint64(),
  })
}

///|
pub fn Ipv6Address::parse(input : String) -> Result[Ipv6Address, CidrError] {
  if input.length() == 0 {
    return Err(EmptyInput)
  }
  if contains_ipv6_compression(input) {
    parse_compressed_ipv6(input)
  } else {
    parse_full_ipv6(input)
  }
}

///|
pub fn Ipv6Address::segment(self : Ipv6Address, index : Int) -> Int {
  match index {
    0 => ((self.hi >> 48) & 0xffffUL).to_int()
    1 => ((self.hi >> 32) & 0xffffUL).to_int()
    2 => ((self.hi >> 16) & 0xffffUL).to_int()
    3 => (self.hi & 0xffffUL).to_int()
    4 => ((self.lo >> 48) & 0xffffUL).to_int()
    5 => ((self.lo >> 32) & 0xffffUL).to_int()
    6 => ((self.lo >> 16) & 0xffffUL).to_int()
    7 => (self.lo & 0xffffUL).to_int()
    _ => 0
  }
}

///|
pub fn Ipv6Address::to_string(self : Ipv6Address) -> String {
  "\{hex16(self.segment(0))}:\{hex16(self.segment(1))}:\{hex16(self.segment(2))}:\{hex16(self.segment(3))}:\{hex16(self.segment(4))}:\{hex16(self.segment(5))}:\{hex16(self.segment(6))}:\{hex16(self.segment(7))}"
}

///|
pub fn Ipv6Address::is_unspecified(self : Ipv6Address) -> Bool {
  self.hi == 0UL && self.lo == 0UL
}

///|
pub fn Ipv6Address::is_loopback(self : Ipv6Address) -> Bool {
  self.hi == 0UL && self.lo == 1UL
}

///|
pub fn Ipv6Address::is_multicast(self : Ipv6Address) -> Bool {
  ((self.hi >> 56) & 0xffUL) == 0xffUL
}

///|
pub fn Ipv6Address::is_unique_local(self : Ipv6Address) -> Bool {
  ((self.hi >> 56) & 0xfeUL) == 0xfcUL
}

///|
pub fn Ipv6Address::is_link_local(self : Ipv6Address) -> Bool {
  ((self.hi >> 54) & 0x3ffUL) == 0x3faUL
}

///|
pub fn Ipv6Address::mask(self : Ipv6Address, prefix : Int) -> Ipv6Address {
  if prefix <= 0 {
    { hi: 0UL, lo: 0UL }
  } else if prefix < 64 {
    { hi: self.hi & (18446744073709551615UL << (64 - prefix)), lo: 0UL }
  } else if prefix == 64 {
    { hi: self.hi, lo: 0UL }
  } else if prefix < 128 {
    { hi: self.hi, lo: self.lo & (18446744073709551615UL << (128 - prefix)) }
  } else {
    self
  }
}

///|
pub fn Ipv6Network::new(
  address : Ipv6Address,
  prefix : Int,
) -> Result[Ipv6Network, CidrError] {
  if prefix < 0 || prefix > 128 {
    Err(PrefixOutOfRange(prefix))
  } else {
    Ok({ address: address.mask(prefix), prefix })
  }
}

///|
pub fn Ipv6Network::parse(input : String) -> Result[Ipv6Network, CidrError] {
  match find_single_slash(input) {
    Err(error) => Err(error)
    Ok(slash) => {
      let address_text = input[:slash].to_owned()
      let prefix_text = input[slash + 1:].to_owned()
      match Ipv6Address::parse(address_text) {
        Err(error) => Err(error)
        Ok(address) =>
          match parse_ipv6_prefix(prefix_text) {
            Err(error) => Err(error)
            Ok(prefix) => Ipv6Network::new(address, prefix)
          }
      }
    }
  }
}

///|
pub fn Ipv6Network::contains(self : Ipv6Network, address : Ipv6Address) -> Bool {
  address.mask(self.prefix) == self.address
}

///|
pub fn Ipv6Network::address(self : Ipv6Network) -> Ipv6Address {
  self.address
}

///|
pub fn Ipv6Network::prefix(self : Ipv6Network) -> Int {
  self.prefix
}

///|
pub fn Ipv6Network::to_string(self : Ipv6Network) -> String {
  "\{self.address.to_string()}/\{self.prefix}"
}

///|
fn parse_full_ipv6(input : String) -> Result[Ipv6Address, CidrError] {
  let pieces = split_ipv6_groups(input)
  if pieces.length() != 8 {
    return Err(InvalidOctetCount(pieces.length()))
  }
  let segments = Array::new()
  for piece in pieces {
    match parse_hex16(piece) {
      Err(error) => return Err(error)
      Ok(segment) => segments.push(segment)
    }
  }
  Ipv6Address::from_segments(
    segments[0],
    segments[1],
    segments[2],
    segments[3],
    segments[4],
    segments[5],
    segments[6],
    segments[7],
  )
}

///|
fn parse_compressed_ipv6(input : String) -> Result[Ipv6Address, CidrError] {
  let double = find_double_colon(input)
  if double < 0 {
    return Err(InvalidPrefix(input))
  }
  let left_text = input[:double].to_owned()
  let right_text = input[double + 2:].to_owned()
  let left = if left_text.length() == 0 {
    []
  } else {
    split_ipv6_groups(left_text)
  }
  let right = if right_text.length() == 0 {
    []
  } else {
    split_ipv6_groups(right_text)
  }
  if left.length() + right.length() >= 8 {
    return Err(InvalidOctetCount(left.length() + right.length()))
  }
  let zeros = 8 - left.length() - right.length()
  let segments = Array::new()
  for piece in left {
    match parse_hex16(piece) {
      Err(error) => return Err(error)
      Ok(segment) => segments.push(segment)
    }
  }
  let mut inserted = 0
  while inserted < zeros {
    segments.push(0)
    inserted = inserted + 1
  }
  for piece in right {
    match parse_hex16(piece) {
      Err(error) => return Err(error)
      Ok(segment) => segments.push(segment)
    }
  }
  Ipv6Address::from_segments(
    segments[0],
    segments[1],
    segments[2],
    segments[3],
    segments[4],
    segments[5],
    segments[6],
    segments[7],
  )
}

///|
fn split_ipv6_groups(input : String) -> Array[String] {
  let pieces = Array::new()
  let mut current = ""
  for ch in input {
    if ch == ':' {
      pieces.push(current)
      current = ""
    } else {
      current = current + ch.to_string()
    }
  }
  pieces.push(current)
  pieces
}

///|
fn contains_ipv6_compression(input : String) -> Bool {
  find_double_colon(input) >= 0
}

///|
fn find_double_colon(input : String) -> Int {
  let mut previous = '\u{0}'
  let mut index = 0
  let mut found = -1
  for ch in input {
    if previous == ':' && ch == ':' {
      if found != -1 {
        return -1
      }
      found = index - 1
    }
    previous = ch
    index = index + 1
  }
  found
}

///|
fn parse_hex16(input : String) -> Result[Int, CidrError] {
  if input.length() == 0 || input.length() > 4 {
    return Err(InvalidPrefix(input))
  }
  let mut value = 0
  for ch in input {
    let digit = hex_digit(ch)
    if digit < 0 {
      return Err(InvalidCharacter(ch.to_string()))
    }
    value = value * 16 + digit
  }
  Ok(value)
}

///|
fn parse_ipv6_prefix(input : String) -> Result[Int, CidrError] {
  if input.length() == 0 {
    return Err(InvalidPrefix(input))
  }
  let mut value = 0
  for ch in input {
    if ch < '0' || ch > '9' {
      return Err(InvalidPrefix(input))
    }
    value = value * 10 + char_digit(ch)
  }
  if value < 0 || value > 128 {
    Err(PrefixOutOfRange(value))
  } else {
    Ok(value)
  }
}

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

///|
fn hex16(value : Int) -> String {
  let digits = "0123456789abcdef"
  let a = (value >> 12) & 0xf
  let b = (value >> 8) & 0xf
  let c = (value >> 4) & 0xf
  let d = value & 0xf
  char_at_hex(digits, a) +
  char_at_hex(digits, b) +
  char_at_hex(digits, c) +
  char_at_hex(digits, d)
}

///|
fn char_at_hex(digits : String, index : Int) -> String {
  let mut i = 0
  for ch in digits {
    if i == index {
      return ch.to_string()
    }
    i = i + 1
  }
  "0"
}