///|
pub(all) suberror AddressError {
  InvalidIpv6Length(Int)
  InvalidAddress(String)
} derive(Debug, Eq)

///|
pub enum IpAddress {
  V4(UInt)
  V6(Bytes)
} derive(Debug, Eq)

///|
pub fn IpAddress::v4(a : Byte, b : Byte, c : Byte, d : Byte) -> IpAddress {
  V4(
    (a.to_uint() << 24) | (b.to_uint() << 16) | (c.to_uint() << 8) | d.to_uint(),
  )
}

///|
pub fn IpAddress::v6(bytes : Bytes) -> IpAddress raise AddressError {
  if bytes.length() != 16 {
    raise InvalidIpv6Length(bytes.length())
  }
  V6(bytes)
}

///|
pub fn IpAddress::loopback_v4() -> IpAddress {
  IpAddress::v4(127, 0, 0, 1)
}

///|
pub fn IpAddress::to_bytes(self : IpAddress) -> Bytes {
  match self {
    V4(value) =>
      Bytes::from_array([
        (value >> 24).to_byte(),
        (value >> 16).to_byte(),
        (value >> 8).to_byte(),
        value.to_byte(),
      ])
    V6(bytes) => bytes
  }
}

///|
fn parse_decimal_octet(value : StringView) -> Byte raise AddressError {
  if value.is_empty() || value.length() > 3 {
    raise InvalidAddress("invalid IPv4 octet")
  }
  let mut result = 0
  for character in value.code_units() {
    if character < '0' || character > '9' {
      raise InvalidAddress("invalid IPv4 digit")
    }
    result = result * 10 + (character - '0').to_int()
    if result > 255 {
      raise InvalidAddress("IPv4 octet exceeds 255")
    }
  }
  result.to_byte()
}

///|
fn parse_ipv4(value : StringView) -> IpAddress raise AddressError {
  let parts = value.split(".").to_array()
  if parts.length() != 4 {
    raise InvalidAddress("IPv4 address must contain four octets")
  }
  IpAddress::v4(
    parse_decimal_octet(parts[0]),
    parse_decimal_octet(parts[1]),
    parse_decimal_octet(parts[2]),
    parse_decimal_octet(parts[3]),
  )
}

///|
fn hex_value(character : UInt16) -> UInt16 raise AddressError {
  if character >= '0' && character <= '9' {
    character - '0'
  } else if character >= 'a' && character <= 'f' {
    character - 'a' + 10
  } else if character >= 'A' && character <= 'F' {
    character - 'A' + 10
  } else {
    raise InvalidAddress("invalid IPv6 hex digit")
  }
}

///|
fn parse_hextet(value : StringView) -> UInt16 raise AddressError {
  if value.is_empty() || value.length() > 4 {
    raise InvalidAddress("invalid IPv6 hextet")
  }
  let mut result : UInt16 = 0
  for character in value.code_units() {
    result = (result << 4) | hex_value(character)
  }
  result
}

///|
fn parse_ipv6_side(value : StringView) -> Array[UInt16] raise AddressError {
  if value.is_empty() {
    return []
  }
  let tokens = value.split(":").to_array()
  let result : Array[UInt16] = []
  for index = 0; index < tokens.length(); index = index + 1 {
    let token = tokens[index]
    if token.is_empty() {
      raise InvalidAddress("empty IPv6 hextet outside ::")
    }
    if token.contains(".") {
      if index != tokens.length() - 1 {
        raise InvalidAddress("embedded IPv4 address must be last")
      }
      let ipv4 = parse_ipv4(token).to_bytes()
      result.push((ipv4[0].to_uint16() << 8) | ipv4[1].to_uint16())
      result.push((ipv4[2].to_uint16() << 8) | ipv4[3].to_uint16())
    } else {
      result.push(parse_hextet(token))
    }
  }
  result
}

///|
fn parse_ipv6(value : StringView) -> IpAddress raise AddressError {
  let words : Array[UInt16] = []
  match value.split_once("::") {
    Some((left, right)) => {
      if right.contains("::") {
        raise InvalidAddress("IPv6 address contains multiple :: sequences")
      }
      let left_words = parse_ipv6_side(left)
      let right_words = parse_ipv6_side(right)
      if left_words.length() + right_words.length() >= 8 {
        raise InvalidAddress("IPv6 :: must compress at least one hextet")
      }
      for word in left_words {
        words.push(word)
      }
      for zero_index = left_words.length() + right_words.length()
          zero_index < 8
          zero_index = zero_index + 1 {
        words.push(0)
      }
      for word in right_words {
        words.push(word)
      }
    }
    None => {
      let parsed = parse_ipv6_side(value)
      if parsed.length() != 8 {
        raise InvalidAddress("IPv6 address must contain eight hextets")
      }
      for word in parsed {
        words.push(word)
      }
    }
  }
  let bytes : Array[Byte] = []
  for word in words {
    bytes.push((word >> 8).to_byte())
    bytes.push(word.to_byte())
  }
  IpAddress::v6(Bytes::from_array(bytes))
}

///|
pub fn IpAddress::parse(value : String) -> IpAddress raise AddressError {
  if value.is_empty() {
    raise InvalidAddress("IP address is empty")
  }
  if value.contains(":") {
    parse_ipv6(value)
  } else {
    parse_ipv4(value)
  }
}

///|
pub fn IpAddress::to_string(self : IpAddress) -> String {
  match self {
    V4(_) => {
      let bytes = self.to_bytes()
      bytes[0].to_uint().to_string() +
      "." +
      bytes[1].to_uint().to_string() +
      "." +
      bytes[2].to_uint().to_string() +
      "." +
      bytes[3].to_uint().to_string()
    }
    V6(bytes) => {
      let words : Array[UInt16] = []
      for index = 0; index < 16; index = index + 2 {
        words.push(
          (bytes[index].to_uint16() << 8) | bytes[index + 1].to_uint16(),
        )
      }
      let mut best_start = -1
      let mut best_length = 0
      let mut index = 0
      while index < words.length() {
        if words[index] != 0 {
          index += 1
          continue
        }
        let start = index
        while index < words.length() && words[index] == 0 {
          index += 1
        }
        let length = index - start
        if length >= 2 && length > best_length {
          best_start = start
          best_length = length
        }
      }
      let mut result = ""
      index = 0
      while index < words.length() {
        if index == best_start {
          result = result + "::"
          index += best_length
          continue
        }
        if !result.is_empty() && !result.has_suffix(":") {
          result = result + ":"
        }
        result = result + words[index].to_string(radix=16)
        index += 1
      }
      result
    }
  }
}

///|
pub fn IpAddress::is_unspecified(self : IpAddress) -> Bool {
  match self {
    V4(value) => value == 0U
    V6(bytes) => {
      for byte in bytes {
        if byte != 0 {
          return false
        }
      }
      true
    }
  }
}

///|
pub struct SocketAddress {
  address : IpAddress
  port : UInt16
} derive(Debug, Eq)

///|
pub fn SocketAddress::new(
  address~ : IpAddress,
  port~ : UInt16,
) -> SocketAddress {
  { address, port, }
}

///|
pub fn SocketAddress::address(self : SocketAddress) -> IpAddress {
  self.address
}

///|
pub fn SocketAddress::port(self : SocketAddress) -> UInt16 {
  self.port
}

///|
pub fn SocketAddress::to_string(self : SocketAddress) -> String {
  match self.address {
    V4(_) => self.address.to_string() + ":" + self.port.to_string()
    V6(_) => "[" + self.address.to_string() + "]:" + self.port.to_string()
  }
}

///|
pub(all) enum TransportProtocol {
  Udp
  Tcp
} derive(Debug, Eq)

///|
pub(all) enum EcnCodepoint {
  Ect0
  Ect1
  CongestionExperienced
} derive(Debug, Eq)