///|
priv struct Cursor {
  data : Bytes
  mut pos : Int
}

///|
fn Cursor::new(data : Bytes) -> Cursor {
  { data, pos: 0, }
}

///|
fn Cursor::take(self : Cursor, size : Int) -> Bytes raise {
  if size < 0 || size > self.data.length() - self.pos {
    raise Protocol("truncated or invalid length")
  }
  let result = self.data[self.pos:self.pos + size].to_owned()
  self.pos += size
  result
}

///|
fn Cursor::byte(self : Cursor) -> Byte raise {
  self.take(1)[0]
}

///|
fn Cursor::u16(self : Cursor) -> Int raise {
  let b = self.take(2)
  (b[0].to_int() << 8) | b[1].to_int()
}

///|
fn Cursor::i32(self : Cursor) -> Int raise {
  let b = self.take(4)
  (b[0].to_int() << 24) |
  (b[1].to_int() << 16) |
  (b[2].to_int() << 8) |
  b[3].to_int()
}

///|
fn Cursor::u64(self : Cursor) -> UInt64 raise {
  let hi = self.i32().reinterpret_as_uint().to_uint64()
  (hi << 32) | self.i32().reinterpret_as_uint().to_uint64()
}

///|
fn Cursor::cstring(self : Cursor) -> String raise {
  let start = self.pos
  while self.byte() != b'\x00' {

  }
  @utf8.decode(self.data[start:self.pos - 1], ignore_bom=true)
}

///|
fn Cursor::done(self : Cursor) -> Unit raise {
  if self.pos != self.data.length() {
    raise Protocol("trailing bytes")
  }
}

///|
fn cstring(out : @buffer.Buffer, value : String) -> Unit {
  out.write_string_utf8(value)
  out.write_byte(b'\x00')
}

///|
fn packet(tag : Byte, body : Bytes) -> Bytes {
  let out = @buffer.Buffer()
  out.write_byte(tag)
  out.write_int_be(body.length() + 4)
  out.write_bytes(body)
  out.to_bytes()
}

///|
async fn read_frame(
  reader : &@io.Reader,
  limit : Int,
  role? : FrameConnectionRole = Unknown,
) -> (Byte, Bytes) {
  let header = reader.read_exactly(5)
  let size = Cursor::new(header[1:].to_owned()).i32()
  if size < 4 {
    raise Protocol("frame length below four")
  }
  if size - 4 > limit {
    // Reuse the already consumed header. Numeric fields preserve its five
    // bytes without emitting raw data, control characters or reading a body.
    raise LimitExceeded(
      "message frame: type=\{header[0].to_int()} declared_length=\{size} max_body_bytes=\{limit} role=\{role.description()}",
    )
  }
  (header[0], reader.read_exactly(size - 4))
}

///|
fn server_error(body : Bytes) -> CdcError raise {
  let c = Cursor::new(body)
  let mut code = ""
  let mut message = ""
  while c.pos < body.length() {
    let tag = c.byte()
    if tag == b'\x00' {
      break
    }
    let value = c.cstring()
    if tag == b'C' {
      code = value
    }
    if tag == b'M' {
      message = value
    }
  }
  c.done()
  Server(code, message)
}