///|
fn message_type_from_wire(value : Int) -> MessageType {
  match value {
    0 => Confirmable
    1 => NonConfirmable
    2 => Acknowledgement
    _ => Reset
  }
}

///|
fn slice_bytes(bytes : Bytes, start : Int, end : Int) -> Bytes {
  Bytes::makei(end - start, fn(i) { bytes[start + i] })
}

///|
fn read_extended(
  nibble : Int,
  bytes : Bytes,
  cursor : Int,
) -> Result[(Int, Int), Failure] {
  if nibble < 13 {
    return Ok((nibble, cursor))
  }
  if nibble == 13 {
    if cursor >= bytes.length() {
      return Err(Syntax(cursor, "truncated one-byte option extension"))
    }
    return Ok((13 + bytes[cursor].to_int(), cursor + 1))
  }
  if nibble == 14 {
    if cursor > bytes.length() - 2 {
      return Err(Syntax(cursor, "truncated two-byte option extension"))
    }
    let value = bytes[cursor].to_int() * 256 + bytes[cursor + 1].to_int()
    return Ok((269 + value, cursor + 2))
  }
  Err(Syntax(cursor - 1, "reserved option nibble"))
}

///|
fn validate_envelope(message : Message) -> Result[Unit, Failure] {
  if message.mid < 0 || message.mid > 65535 {
    return Err(Invalid("message identifier must be 0..65535"))
  }
  if message.code.value < 0 || message.code.value > 255 {
    return Err(Invalid("code must fit one byte"))
  }
  if message.token.length() > 8 {
    return Err(Invalid("Token must be 0..8 bytes"))
  }
  if message.code.value == 0 {
    if message.token.length() != 0 ||
      !message.options.is_empty() ||
      !message.payload.is_empty() {
      return Err(Invalid("Empty message must consist of the four-byte header"))
    }
    if message.msg_type == NonConfirmable {
      return Err(Invalid("NON Empty messages are not supported"))
    }
  } else {
    if message.msg_type == Reset {
      return Err(Invalid("Reset must be Empty"))
    }
    if message.msg_type == Acknowledgement && !message.code.is_response() {
      return Err(Invalid("non-empty ACK must carry a response"))
    }
    if !message.code.is_request() && !message.code.is_response() {
      return Err(Unsupported("reserved code class"))
    }
  }
  Ok(())
}

///|
pub fn decode(bytes : Bytes, limits : Limits) -> Result[Message, Failure] {
  match limits.validate() {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  if bytes.length() > limits.max_datagram {
    return Err(Capacity("datagram exceeds configured limit"))
  }
  if bytes.length() < 4 {
    return Err(Syntax(bytes.length(), "truncated header"))
  }
  let first = bytes[0].to_int()
  if first >> 6 != 1 {
    return Err(Syntax(0, "unsupported CoAP version"))
  }
  let token_length = first & 15
  if token_length > 8 {
    return Err(Syntax(0, "reserved Token length"))
  }
  if bytes.length() - 4 < token_length {
    return Err(Syntax(4, "truncated Token"))
  }
  let message = Message::new(
    message_type_from_wire((first >> 4) & 3),
    { value: bytes[1].to_int(), },
    bytes[2].to_int() * 256 + bytes[3].to_int(),
  )
  message.token = slice_bytes(bytes, 4, 4 + token_length)
  let mut cursor = 4 + token_length
  let mut option_number = 0
  let mut option_bytes = 0
  while cursor < bytes.length() {
    let header = bytes[cursor].to_int()
    if header == 255 {
      cursor = cursor + 1
      if cursor == bytes.length() {
        return Err(Syntax(cursor - 1, "payload marker without payload"))
      }
      if bytes.length() - cursor > limits.max_payload {
        return Err(Capacity("payload exceeds configured limit"))
      }
      message.payload = slice_bytes(bytes, cursor, bytes.length())
      cursor = bytes.length()
      break
    }
    if message.options.length() >= limits.max_options {
      return Err(Capacity("too many options"))
    }
    cursor = cursor + 1
    let (delta, next) = match read_extended(header >> 4, bytes, cursor) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    cursor = next
    let (length, next) = match read_extended(header & 15, bytes, cursor) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    cursor = next
    if delta > 65535 - option_number {
      return Err(Syntax(cursor, "option number exceeds 65535"))
    }
    option_number = option_number + delta
    if length > bytes.length() - cursor {
      return Err(Syntax(cursor, "truncated option value"))
    }
    if length > limits.max_option_bytes - option_bytes {
      return Err(Capacity("option values exceed configured limit"))
    }
    option_bytes = option_bytes + length
    message.options.push({
      number: option_number,
      value: slice_bytes(bytes, cursor, cursor + length),
    })
    cursor = cursor + length
  }
  match validate_envelope(message) {
    Err(error) => Err(error)
    Ok(_) => Ok(message)
  }
}