///|
fn dc_writer(capacity : Int) -> @codec.Writer raise DataChannelError {
  @codec.Writer::new(capacity~) catch {
    _ => raise ProtocolViolation("invalid DCEP output length")
  }
}

///|
fn dc_read_u8(
  reader : @codec.Reader,
  context : String,
) -> Byte raise DataChannelError {
  reader.read_u8() catch {
    _ => raise ProtocolViolation("truncated \{context}")
  }
}

///|
fn dc_read_u16(
  reader : @codec.Reader,
  context : String,
) -> UInt16 raise DataChannelError {
  reader.read_u16_be() catch {
    _ => raise ProtocolViolation("truncated \{context}")
  }
}

///|
fn dc_read_u32(
  reader : @codec.Reader,
  context : String,
) -> UInt raise DataChannelError {
  reader.read_u32_be() catch {
    _ => raise ProtocolViolation("truncated \{context}")
  }
}

///|
fn dc_read_bytes(
  reader : @codec.Reader,
  length : Int,
  context : String,
) -> Bytes raise DataChannelError {
  reader.read_bytes(length) catch {
    _ => raise ProtocolViolation("truncated \{context}")
  }
}

///|
fn channel_type_code(channel_type : ChannelType) -> Byte {
  match channel_type {
    ReliableChannel => 0x00
    ReliableUnorderedChannel => 0x80
    PartialReliableRetransmitChannel => 0x01
    PartialReliableRetransmitUnorderedChannel => 0x81
    PartialReliableTimedChannel => 0x02
    PartialReliableTimedUnorderedChannel => 0x82
  }
}

///|
fn decode_channel_type(value : Byte) -> ChannelType raise DataChannelError {
  match value {
    0x00 => ReliableChannel
    0x80 => ReliableUnorderedChannel
    0x01 => PartialReliableRetransmitChannel
    0x81 => PartialReliableRetransmitUnorderedChannel
    0x02 => PartialReliableTimedChannel
    0x82 => PartialReliableTimedUnorderedChannel
    _ =>
      raise ProtocolViolation(
        "unsupported DCEP channel type \{value.to_uint()}",
      )
  }
}

///|
pub fn DcepMessage::encode(self : DcepMessage) -> Bytes raise DataChannelError {
  match self {
    Acknowledgement => b"\x02"
    Open(open) => {
      let label = @utf8.encode(open.label)
      let protocol = @utf8.encode(open.protocol)
      if label.length() > 65535 || protocol.length() > 65535 {
        raise InvalidConfiguration(
          "DCEP label and protocol must each fit in 65535 UTF-8 bytes",
        )
      }
      let writer = dc_writer(12 + label.length() + protocol.length())
      writer.write_u8(0x03)
      writer.write_u8(channel_type_code(open.channel_type))
      writer.write_u16_be(open.priority)
      writer.write_u32_be(open.reliability_parameter)
      writer.write_u16_be(label.length().to_uint16())
      writer.write_u16_be(protocol.length().to_uint16())
      writer.write_bytes(label)
      writer.write_bytes(protocol)
      writer.finish()
    }
  }
}

///|
pub fn DcepMessage::decode(data : Bytes) -> DcepMessage raise DataChannelError {
  let reader = @codec.Reader::new(data)
  let message_type = dc_read_u8(reader, "DCEP message type")
  match message_type {
    0x02 => {
      if reader.remaining() != 0 {
        raise ProtocolViolation("DCEP ACK contains trailing bytes")
      }
      Acknowledgement
    }
    0x03 => {
      let channel_type = decode_channel_type(
        dc_read_u8(reader, "DCEP channel type"),
      )
      let priority = dc_read_u16(reader, "DCEP priority")
      let reliability_parameter = dc_read_u32(
        reader, "DCEP reliability parameter",
      )
      let label_length = dc_read_u16(reader, "DCEP label length").to_int()
      let protocol_length = dc_read_u16(reader, "DCEP protocol length").to_int()
      if reader.remaining() != label_length + protocol_length {
        raise ProtocolViolation("DCEP OPEN length does not match its vectors")
      }
      let label = @utf8.decode(
        dc_read_bytes(reader, label_length, "DCEP label"),
      ) catch {
        _ => raise ProtocolViolation("DCEP label is not valid UTF-8")
      }
      let protocol = @utf8.decode(
        dc_read_bytes(reader, protocol_length, "DCEP protocol"),
      ) catch {
        _ => raise ProtocolViolation("DCEP protocol is not valid UTF-8")
      }
      Open({ channel_type, priority, reliability_parameter, label, protocol, })
    }
    _ =>
      raise ProtocolViolation(
        "unsupported DCEP message type \{message_type.to_uint()}",
      )
  }
}

///|
pub fn PayloadProtocolIdentifier::from_code(
  code : UInt,
) -> PayloadProtocolIdentifier raise DataChannelError {
  match code {
    50U => Dcep
    51U => StringPayload
    53U => BinaryPayload
    56U => StringEmptyPayload
    57U => BinaryEmptyPayload
    52U | 54U =>
      raise ProtocolViolation("deprecated partial WebRTC PPID is unsupported")
    _ => raise ProtocolViolation("unsupported WebRTC PPID \{code}")
  }
}