///|
pub struct ResponseError {
  code : UInt16
  reason : String
} derive(Debug, Eq)

///|
pub fn ResponseError::new(
  code~ : UInt16,
  reason~ : String,
) -> ResponseError raise StunError {
  if code < 300 || code > 699 {
    raise InvalidMessage("STUN error code must be between 300 and 699")
  }
  let reason_length = @utf8.encode(reason).length()
  if reason_length > 763 {
    raise InvalidMessage("STUN error reason exceeds 763 bytes")
  }
  { code, reason, }
}

///|
pub fn ResponseError::code(self : ResponseError) -> UInt16 {
  self.code
}

///|
pub fn ResponseError::reason(self : ResponseError) -> String {
  self.reason
}

///|
pub fn ResponseError::with_default_reason(
  code : UInt16,
) -> ResponseError raise StunError {
  let reason = match code.to_uint() {
    300U => "Try Alternate"
    400U => "Bad Request"
    401U => "Unauthorized"
    403U => "Forbidden"
    420U => "Unknown Attribute"
    437U => "Allocation Mismatch"
    438U => "Stale Nonce"
    440U => "Address Family not Supported"
    441U => "Wrong Credentials"
    442U => "Unsupported Transport Protocol"
    443U => "Peer Address Family Mismatch"
    446U => "Connection Already Exists"
    447U => "Connection Timeout or Failure"
    486U => "Allocation Quota Reached"
    487U => "Role Conflict"
    500U => "Server Error"
    508U => "Insufficient Capacity"
    _ => raise InvalidMessage("STUN error code has no default reason")
  }
  ResponseError::new(code~, reason~)
}

///|
pub fn Attribute::from_error(
  error : ResponseError,
) -> Attribute raise StunError {
  let code = error.code.to_uint()
  let reason = @utf8.encode(error.reason)
  let value : Array[Byte] = [
    0,
    0,
    (code / 100U).to_byte(),
    (code % 100U).to_byte(),
  ]
  for byte in reason {
    value.push(byte)
  }
  Attribute::new(attribute_type=ErrorCode, value=Bytes::from_array(value))
}

///|
pub fn Attribute::to_error(self : Attribute) -> ResponseError raise StunError {
  if self.attribute_type != ErrorCode || self.value.length() < 4 {
    raise InvalidMessage("attribute does not contain a STUN error")
  }
  if self.value[0] != 0 || self.value[1] != 0 {
    raise InvalidMessage("STUN error reserved bits must be zero")
  }
  let class = self.value[2].to_uint()
  let number = self.value[3].to_uint()
  if class < 3U || class > 6U || number > 99U {
    raise InvalidMessage("invalid STUN error code")
  }
  let reason = @utf8.decode(self.value[4:].to_owned()) catch {
    Malformed(_) => raise InvalidMessage("STUN error reason is not valid UTF-8")
  }
  ResponseError::new(code=(class * 100U + number).to_uint16(), reason~)
}

///|
pub fn Attribute::from_unknown_attributes(
  types : Array[UInt16],
) -> Attribute raise StunError {
  let value : Array[Byte] = []
  for attribute_type in types {
    value.push((attribute_type >> 8).to_byte())
    value.push(attribute_type.to_byte())
  }
  Attribute::new(
    attribute_type=UnknownAttributes,
    value=Bytes::from_array(value),
  )
}

///|
pub fn Attribute::to_unknown_attributes(
  self : Attribute,
) -> Array[UInt16] raise StunError {
  if self.attribute_type != UnknownAttributes || self.value.length() % 2 != 0 {
    raise InvalidMessage("attribute does not contain unknown attribute types")
  }
  let result : Array[UInt16] = []
  for index = 0; index < self.value.length(); index = index + 2 {
    result.push(
      (self.value[index].to_uint16() << 8) | self.value[index + 1].to_uint16(),
    )
  }
  result
}

///|
pub fn Attribute::lifetime(seconds : UInt) -> Attribute raise StunError {
  Attribute::new(attribute_type=Lifetime, value=u32_bytes(seconds))
}

///|
pub fn Attribute::channel_number(channel : UInt16) -> Attribute raise StunError {
  if channel < 0x4000 || channel > 0x7fff {
    raise InvalidMessage("TURN channel number must be in 0x4000..0x7fff")
  }
  Attribute::new(
    attribute_type=ChannelNumber,
    value=Bytes::from_array([(channel >> 8).to_byte(), channel.to_byte(), 0, 0]),
  )
}

///|
pub fn Attribute::network_cost(
  network_id : UInt16,
  cost : UInt16,
) -> Attribute raise StunError {
  Attribute::new(
    attribute_type=NetworkCost,
    value=Bytes::from_array([
      (network_id >> 8).to_byte(),
      network_id.to_byte(),
      (cost >> 8).to_byte(),
      cost.to_byte(),
    ]),
  )
}

///|
pub fn Attribute::as_network_cost(
  self : Attribute,
) -> (UInt16, UInt16) raise StunError {
  if self.attribute_type != NetworkCost || self.value.length() != 4 {
    raise InvalidMessage("attribute does not contain ICE network cost")
  }
  (
    (self.value[0].to_uint16() << 8) | self.value[1].to_uint16(),
    (self.value[2].to_uint16() << 8) | self.value[3].to_uint16(),
  )
}

///|
pub fn Message::error_response(
  request : Message,
  error : ResponseError,
) -> Message raise StunError {
  Message::new(
    class=ErrorResponse,
    stun_method=request.stun_method,
    transaction_id=request.transaction_id,
    attributes=[Attribute::from_error(error)],
  )
}