///|
pub(all) enum Failure {
  Syntax(Int, String)
  Invalid(String)
  Capacity(String)
  Unsupported(String)
  Clock(String)
  NotFound(String)
  Conflict(String)
  Transport(String)
  TimedOut
  ResetByPeer
} derive(Eq, Debug)

///|
pub(all) enum MessageType {
  Confirmable
  NonConfirmable
  Acknowledgement
  Reset
} derive(Eq, Debug)

///|
pub fn MessageType::wire(self : MessageType) -> Int {
  match self {
    Confirmable => 0
    NonConfirmable => 1
    Acknowledgement => 2
    Reset => 3
  }
}

///|
pub(all) enum Method {
  Get
  Post
  Put
  Delete
} derive(Eq, Debug)

///|
pub fn Method::wire(self : Method) -> Int {
  match self {
    Get => 1
    Post => 2
    Put => 3
    Delete => 4
  }
}

///|
pub fn Method::name(self : Method) -> String {
  match self {
    Get => "GET"
    Post => "POST"
    Put => "PUT"
    Delete => "DELETE"
  }
}

///|
pub(all) struct Code {
  value : Int
} derive(Eq, Debug)

///|
pub fn Code::request(verb : Method) -> Code {
  { value: verb.wire(), }
}

///|
pub fn Code::response(class : Int, detail : Int) -> Result[Code, Failure] {
  if (class != 2 && class != 4 && class != 5) || detail < 0 || detail > 31 {
    return Err(Invalid("response class must be 2, 4 or 5 and detail 0..31"))
  }
  Ok({ value: class * 32 + detail, })
}

///|
pub fn Code::class(self : Code) -> Int {
  self.value >> 5
}

///|
pub fn Code::detail(self : Code) -> Int {
  self.value & 31
}

///|
pub fn Code::is_request(self : Code) -> Bool {
  self.value > 0 && self.value < 32
}

///|
pub fn Code::is_response(self : Code) -> Bool {
  self.class() == 2 || self.class() == 4 || self.class() == 5
}

///|
pub fn Code::is_success(self : Code) -> Bool {
  self.class() == 2
}

///|
pub fn Code::verb(self : Code) -> Method? {
  match self.value {
    1 => Some(Get)
    2 => Some(Post)
    3 => Some(Put)
    4 => Some(Delete)
    _ => None
  }
}

///|
pub fn Code::description(self : Code) -> String {
  match self.value {
    0 => "Empty"
    1 => "GET"
    2 => "POST"
    3 => "PUT"
    4 => "DELETE"
    65 => "Created"
    66 => "Deleted"
    67 => "Valid"
    68 => "Changed"
    69 => "Content"
    128 => "Bad Request"
    129 => "Unauthorized"
    130 => "Bad Option"
    131 => "Forbidden"
    132 => "Not Found"
    133 => "Method Not Allowed"
    134 => "Not Acceptable"
    140 => "Precondition Failed"
    141 => "Request Entity Too Large"
    143 => "Unsupported Content-Format"
    160 => "Internal Server Error"
    161 => "Not Implemented"
    162 => "Bad Gateway"
    163 => "Service Unavailable"
    164 => "Gateway Timeout"
    165 => "Proxying Not Supported"
    _ => "Unassigned"
  }
}

///|
pub(all) struct CoapOption {
  number : Int
  value : Bytes
} derive(Eq, Debug)

///|
pub(all) struct Message {
  mut msg_type : MessageType
  mut code : Code
  mut mid : Int
  mut token : Bytes
  mut options : Array[CoapOption]
  mut payload : Bytes
} derive(Eq, Debug)

///|
pub fn Message::new(msg_type : MessageType, code : Code, mid : Int) -> Message {
  { msg_type, code, mid, token: b"", options: [], payload: b"", }
}

///|
pub fn Message::copy(self : Message) -> Message {
  {
    msg_type: self.msg_type,
    code: self.code,
    mid: self.mid,
    token: self.token,
    options: self.options.map(fn(o) { { number: o.number, value: o.value, } }),
    payload: self.payload,
  }
}

///|
pub fn Message::option_values(self : Message, number : Int) -> Array[Bytes] {
  self.options.filter_map(fn(o) {
    if o.number == number {
      Some(o.value)
    } else {
      None
    }
  })
}

///|
pub fn Message::first_option(self : Message, number : Int) -> Bytes? {
  for option in self.options {
    if option.number == number {
      return Some(option.value)
    }
  }
  None
}

///|
pub fn Message::replace_option(
  self : Message,
  number : Int,
  value : Bytes,
) -> Unit {
  self.options = self.options.filter(fn(o) { o.number != number })
  self.options.push({ number, value, })
}

///|
pub fn Message::remove_option(self : Message, number : Int) -> Unit {
  self.options = self.options.filter(fn(o) { o.number != number })
}

///|
pub(all) struct Limits {
  max_datagram : Int
  max_options : Int
  max_option_bytes : Int
  max_payload : Int
  max_uri_bytes : Int
  max_links : Int
  max_link_attributes : Int
} derive(Eq, Debug)

///|
pub fn Limits::default() -> Limits {
  {
    max_datagram: 1152,
    max_options: 32,
    max_option_bytes: 512,
    max_payload: 1024,
    max_uri_bytes: 1024,
    max_links: 64,
    max_link_attributes: 16,
  }
}

///|
pub fn Limits::validate(self : Limits) -> Result[Unit, Failure] {
  if self.max_datagram < 4 || self.max_datagram > 65535 {
    return Err(Invalid("max_datagram must be 4..65535"))
  }
  if self.max_options < 0 || self.max_options > 1024 {
    return Err(Invalid("max_options must be 0..1024"))
  }
  if self.max_option_bytes < 0 || self.max_option_bytes > 65535 {
    return Err(Invalid("invalid option byte limit"))
  }
  if self.max_payload < 0 || self.max_payload > 65535 {
    return Err(Invalid("invalid payload limit"))
  }
  if self.max_uri_bytes < 1 || self.max_uri_bytes > 65535 {
    return Err(Invalid("invalid URI byte limit"))
  }
  if self.max_links < 1 || self.max_links > 4096 {
    return Err(Invalid("invalid discovery link limit"))
  }
  if self.max_link_attributes < 1 || self.max_link_attributes > 128 {
    return Err(Invalid("invalid link attribute limit"))
  }
  Ok(())
}

///|
pub(all) struct Request {
  verb : Method
  options : Array[CoapOption]
  payload : Bytes
  confirmable : Bool
} derive(Eq, Debug)

///|
pub fn Request::new(verb : Method) -> Request {
  { verb, options: [], payload: b"", confirmable: true, }
}

///|
pub(all) struct Response {
  code : Code
  options : Array[CoapOption]
  payload : Bytes
} derive(Eq, Debug)

///|
pub fn Response::content(payload : Bytes) -> Response {
  { code: { value: 69, }, options: [], payload, }
}

///|
pub fn Response::error(code : Int) -> Response {
  { code: { value: code, }, options: [], payload: b"", }
}

///|
pub fn bytes_to_hex(bytes : Bytes) -> String {
  let digits = "0123456789abcdef"
  let result : Array[Char] = []
  for byte in bytes {
    let value = byte.to_int()
    result.push(digits.get_char(value >> 4).unwrap())
    result.push(digits.get_char(value & 15).unwrap())
  }
  String::from_array(result)
}

///|
pub fn hex_to_bytes(text : String) -> Result[Bytes, Failure] {
  if text.length() % 2 != 0 {
    return Err(Invalid("hex input has odd length"))
  }
  let output : Array[Byte] = []
  for i = 0; i < text.length(); i = i + 2 {
    let high = hex_digit(text[i].to_int())
    let low = hex_digit(text[i + 1].to_int())
    if high < 0 || low < 0 {
      return Err(Syntax(i, "invalid hexadecimal digit"))
    }
    output.push((high * 16 + low).to_byte())
  }
  Ok(Bytes::from_array(output))
}

///|
fn hex_digit(value : Int) -> Int {
  if value >= 48 && value <= 57 {
    value - 48
  } else if value >= 65 && value <= 70 {
    value - 55
  } else if value >= 97 && value <= 102 {
    value - 87
  } else {
    -1
  }
}

///|
pub fn decode_utf8(bytes : Bytes) -> Result[String, Failure] {
  Ok(@utf8.decode(bytes[:])) catch {
    _ => Err(Syntax(0, "invalid UTF-8"))
  }
}