// TBinaryProtocol: a strict message header, structs as typed fields, and every number
// fixed-width and big-endian. The doubles are the one place a reader has to be careful:
// this protocol writes them big-endian, TCompactProtocol writes them little-endian.

///|
/// A binary-protocol reader and writer over one message.
pub struct Binary {
  mut data : Bytes
  mut off : Int32
  mut out : @buffer.Buffer
}

///|
pub fn Binary::new(input : Bytes) -> Binary {
  { data: input, off: 0, out: @buffer.Buffer(), }
}

///|
pub fn Binary::eof(self : Binary) -> Bool {
  self.off >= self.data.length()
}

///|
fn Binary::need(self : Binary, n : Int32) -> Unit raise ThriftError {
  if n < 0 || self.off + n > self.data.length() {
    raise Truncated
  }
}

///|
pub impl Protocol for Binary with fn reset(self, input) {
  self.data = input
  self.off = 0
  self.out = @buffer.Buffer()
}

///|
pub impl Protocol for Binary with fn output(self) {
  self.out.to_bytes()
}

///|
pub impl Protocol for Binary with fn write_byte(self, value) {
  self.out.write_byte((value & 0xFF).to_byte())
}

///|
pub impl Protocol for Binary with fn read_byte(self) {
  self.need(1)
  let value = self.data[self.off].to_int()
  self.off = self.off + 1
  value
}

///|
pub impl Protocol for Binary with fn write_i16(self, value) {
  self.write_byte(value >> 8)
  self.write_byte(value)
}

///|
pub impl Protocol for Binary with fn read_i16(self) {
  let hi = self.read_byte()
  let lo = self.read_byte()
  let value = (hi << 8) | lo
  if value >= 0x8000 {
    value - 0x10000
  } else {
    value
  }
}

///|
pub impl Protocol for Binary with fn write_i32(self, value) {
  self.write_byte(value >> 24)
  self.write_byte(value >> 16)
  self.write_byte(value >> 8)
  self.write_byte(value)
}

///|
pub impl Protocol for Binary with fn read_i32(self) {
  let b0 = self.read_byte()
  let b1 = self.read_byte()
  let b2 = self.read_byte()
  let b3 = self.read_byte()
  (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}

///|
pub impl Protocol for Binary with fn write_i64(self, value) {
  for shift in [56, 48, 40, 32, 24, 16, 8, 0] {
    self.write_byte(((value >> shift) & 0xFFL).to_int())
  }
}

///|
pub impl Protocol for Binary with fn read_i64(self) {
  let mut value = 0L
  for _ in 0..<8 {
    value = (value << 8) | self.read_byte().to_int64()
  }
  value
}

///|
pub impl Protocol for Binary with fn write_bool(self, value) {
  self.write_byte(if value { 1 } else { 0 })
}

///|
pub impl Protocol for Binary with fn read_bool(self) {
  self.read_byte() != 0
}

// Eight bytes big-endian: the bit pattern of the double.

///|
pub impl Protocol for Binary with fn write_double(self, value) {
  let bits = value.reinterpret_as_uint64().to_be_bytes()
  for i in 0..<8 {
    self.write_byte(bits[i].to_int())
  }
}

///|
pub impl Protocol for Binary with fn read_double(self) {
  let mut bits = 0L
  for _ in 0..<8 {
    bits = (bits << 8) | self.read_byte().to_int64()
  }
  bits.reinterpret_as_double()
}

///|
pub impl Protocol for Binary with fn write_string(self, value) {
  let body = @utf8.encode(value)
  self.write_i32(body.length())
  self.out.write_bytes(body)
}

///|
pub impl Protocol for Binary with fn read_string(self) {
  let n = self.read_i32()
  self.need(n)
  let body = self.data[self.off:self.off + n].to_owned()
  self.off = self.off + n
  @utf8.decode_lossy(body[:])
}

///|
pub impl Protocol for Binary with fn write_bytes(self, body) {
  self.write_i32(body.length())
  self.out.write_bytes(body)
}

///|
pub impl Protocol for Binary with fn read_bytes(self) {
  let n = self.read_i32()
  self.need(n)
  let body = self.data[self.off:self.off + n].to_owned()
  self.off = self.off + n
  body
}

///|
pub impl Protocol for Binary with fn write_field_begin(self, type_, id) {
  self.write_byte(type_)
  self.write_i16(id)
}

///|
pub impl Protocol for Binary with fn read_field_begin(self) {
  let type_ = self.read_byte()
  if type_ == type_stop {
    (type_stop, 0)
  } else {
    (type_, self.read_i16())
  }
}

///|
pub impl Protocol for Binary with fn write_field_stop(self) {
  self.write_byte(type_stop)
}

///|
pub impl Protocol for Binary with fn write_message_begin(
  self,
  name,
  kind,
  seqid,
) {
  // Strict format: version 1 in the high half, the message type in the low byte.
  self.write_i32(-2147418112 | kind)
  self.write_string(name)
  self.write_i32(seqid)
}

///|
pub impl Protocol for Binary with fn read_message_begin(self) {
  let header = self.read_i32()
  if ((header >> 16) & 0xFFFF) != 0x8001 {
    raise BadVersion(header)
  }
  let kind = header & 0xFF
  let name = self.read_string()
  let seqid = self.read_i32()
  (name, kind, seqid)
}

///|
pub impl Protocol for Binary with fn write_list_begin(self, elem, size) {
  self.write_byte(elem)
  self.write_i32(size)
}

///|
pub impl Protocol for Binary with fn read_list_begin(self) {
  (self.read_byte(), self.read_i32())
}

///|
pub impl Protocol for Binary with fn write_map_begin(self, key, value, size) {
  self.write_byte(key)
  self.write_byte(value)
  self.write_i32(size)
}

///|
pub impl Protocol for Binary with fn read_map_begin(self) {
  (self.read_byte(), self.read_byte(), self.read_i32())
}

// A struct has no markers in this protocol. Only the compact one's field-id deltas
// need to know where a struct starts and ends.

///|
pub impl Protocol for Binary with fn read_struct_begin(self) {
  ignore(self)
}

///|
pub impl Protocol for Binary with fn read_struct_end(self) {
  ignore(self)
}

///|
pub impl Protocol for Binary with fn write_struct_begin(self, name) {
  ignore(self)
  ignore(name)
}

///|
pub impl Protocol for Binary with fn write_struct_end(self) {
  ignore(self)
}

///|
pub extend Binary with Protocol::{
  reset,
  output,
  read_bytes,
  write_bytes,
  read_message_begin,
  write_message_begin,
  read_struct_begin,
  read_struct_end,
  write_struct_begin,
  write_struct_end,
  read_field_begin,
  write_field_begin,
  write_field_stop,
  read_bool,
  read_byte,
  read_i16,
  read_i32,
  read_i64,
  read_double,
  read_string,
  read_list_begin,
  read_map_begin,
  write_bool,
  write_byte,
  write_i16,
  write_i32,
  write_i64,
  write_double,
  write_string,
  write_list_begin,
  write_map_begin,
}