///|
fn is_plain_address_type(attribute_type : AttributeType) -> Bool {
  match attribute_type {
    MappedAddress
    | AlternateServer
    | SourceAddress
    | ChangedAddress
    | ResponseOrigin
    | OtherAddress => true
    _ => false
  }
}

///|
fn is_xor_address_type(attribute_type : AttributeType) -> Bool {
  match attribute_type {
    XorMappedAddress | XorPeerAddress | XorRelayedAddress => true
    _ => false
  }
}

///|
fn encode_address_value(
  address : @transport.SocketAddress,
) -> Bytes raise StunError {
  let ip = address.address().to_bytes()
  let family : Byte = match ip.length() {
    4 => 1
    16 => 2
    length => raise InvalidMessage("invalid IP address length \{length}")
  }
  let port = address.port()
  let result : Array[Byte] = [0, family, (port >> 8).to_byte(), port.to_byte()]
  for byte in ip {
    result.push(byte)
  }
  Bytes::from_array(result)
}

///|
fn decode_ip(value : Bytes) -> @transport.IpAddress raise StunError {
  if value.length() != 8 && value.length() != 20 {
    raise InvalidMessage("STUN address must contain 8 or 20 bytes")
  }
  if value[0] != 0 {
    raise InvalidMessage("reserved STUN address byte must be zero")
  }
  match value[1] {
    1 if value.length() == 8 =>
      @transport.IpAddress::v4(value[4], value[5], value[6], value[7])
    2 if value.length() == 20 =>
      @transport.IpAddress::v6(value[4:20].to_owned()) catch {
        InvalidIpv6Length(length) =>
          raise InvalidMessage("invalid IPv6 address length \{length}")
        InvalidAddress(message) => raise InvalidMessage(message)
      }
    family =>
      raise InvalidMessage(
        "address family \{family} does not match its encoded length",
      )
  }
}

///|
fn decode_address_value(
  value : Bytes,
) -> @transport.SocketAddress raise StunError {
  let address = decode_ip(value)
  let port = (value[2].to_uint16() << 8) | value[3].to_uint16()
  @transport.SocketAddress::new(address~, port~)
}

///|
pub fn Attribute::from_address(
  address : @transport.SocketAddress,
  attribute_type? : AttributeType = MappedAddress,
) -> Attribute raise StunError {
  if !is_plain_address_type(attribute_type) {
    raise InvalidMessage("attribute type is not a plain STUN address")
  }
  Attribute::new(attribute_type~, value=encode_address_value(address))
}

///|
pub fn Attribute::to_address(
  self : Attribute,
) -> @transport.SocketAddress raise StunError {
  if !is_plain_address_type(self.attribute_type) {
    raise InvalidMessage("attribute type is not a plain STUN address")
  }
  decode_address_value(self.value)
}

///|
fn xor_address_value(
  value : Bytes,
  transaction_id : TransactionId,
) -> Bytes raise StunError {
  if value.length() != 8 && value.length() != 20 {
    raise InvalidMessage("STUN XOR address must contain 8 or 20 bytes")
  }
  let result = value.to_array()
  result[2] = result[2] ^ 0x21
  result[3] = result[3] ^ 0x12
  let mask : Array[Byte] = [0x21, 0x12, 0xa4, 0x42]
  for byte in transaction_id.as_bytes() {
    mask.push(byte)
  }
  for index = 4; index < result.length(); index = index + 1 {
    result[index] = result[index] ^ mask[index - 4]
  }
  Bytes::from_array(result)
}

///|
pub fn Attribute::from_xor_address(
  address : @transport.SocketAddress,
  transaction_id : TransactionId,
  attribute_type? : AttributeType = XorMappedAddress,
) -> Attribute raise StunError {
  if !is_xor_address_type(attribute_type) {
    raise InvalidMessage("attribute type is not a STUN XOR address")
  }
  Attribute::new(
    attribute_type~,
    value=xor_address_value(encode_address_value(address), transaction_id),
  )
}

///|
pub fn Attribute::to_xor_address(
  self : Attribute,
  transaction_id : TransactionId,
) -> @transport.SocketAddress raise StunError {
  if !is_xor_address_type(self.attribute_type) {
    raise InvalidMessage("attribute type is not a STUN XOR address")
  }
  decode_address_value(xor_address_value(self.value, transaction_id))
}