// Thrift's protocol contract, and the pieces every protocol shares: the type ids, the
// message kinds, and `skip`, which is written once here against the contract rather
// than per protocol. `binary.mbt` and `compact.mbt` fill in the bytes.

///|
pub(all) suberror ThriftError {
  BadVersion(Int32)
  Truncated
  BadType(Int32)
  Depth
  Unsupported(String)
  Application(String)
}

///|
pub let type_stop : Int32 = 0

///|
pub let type_bool : Int32 = 2

///|
pub let type_byte : Int32 = 3

///|
pub let type_double : Int32 = 4

///|
pub let type_i16 : Int32 = 6

///|
pub let type_i32 : Int32 = 8

///|
pub let type_i64 : Int32 = 10

///|
pub let type_string : Int32 = 11

///|
pub let type_struct : Int32 = 12

///|
pub let type_map : Int32 = 13

///|
pub let type_set : Int32 = 14

///|
pub let type_list : Int32 = 15

///|
pub let message_call : Int32 = 1

///|
pub let message_reply : Int32 = 2

///|
pub let message_exception : Int32 = 3

///|
pub let message_oneway : Int32 = 4

///|
/// The largest nesting `skip` will follow before giving up.
pub let max_depth : Int32 = 64

///|
/// One message's worth of reading and writing. `reset` points a protocol at the bytes
/// to read and clears what it has written, so one object serves a whole connection:
/// read a frame, write the reply, read the next frame.
///
/// Both byte orders live behind this contract. TBinaryProtocol writes fixed-width
/// big-endian numbers; TCompactProtocol writes zigzag varints and encodes a boolean's
/// value inside its field header. Nothing above this trait — the processor, the client
/// request, a service's handlers — knows which one it is talking to.
pub(open) trait Protocol {
  /// Point at `input` to read, and drop anything written before.
  fn reset(Self, Bytes) -> Unit
  /// The bytes written since the last `reset`.
  fn output(Self) -> Bytes
  fn read_message_begin(Self) -> (String, Int32, Int32) raise ThriftError
  fn write_message_begin(Self, String, Int32, Int32) -> Unit
  /// Enter a struct. TBinaryProtocol writes nothing; TCompactProtocol resets the field
  /// id that its deltas count from.
  fn read_struct_begin(Self) -> Unit
  fn read_struct_end(Self) -> Unit
  fn write_struct_begin(Self, String) -> Unit
  fn write_struct_end(Self) -> Unit
  fn read_field_begin(Self) -> (Int32, Int32) raise ThriftError
  fn write_field_begin(Self, Int32, Int32) -> Unit
  fn write_field_stop(Self) -> Unit
  fn read_bool(Self) -> Bool raise ThriftError
  fn read_byte(Self) -> Int32 raise ThriftError
  fn read_i16(Self) -> Int32 raise ThriftError
  fn read_i32(Self) -> Int32 raise ThriftError
  fn read_i64(Self) -> Int64 raise ThriftError
  fn read_double(Self) -> Double raise ThriftError
  fn read_string(Self) -> String raise ThriftError
  fn read_bytes(Self) -> Bytes raise ThriftError
  fn read_list_begin(Self) -> (Int32, Int32) raise ThriftError
  fn read_map_begin(Self) -> (Int32, Int32, Int32) raise ThriftError
  fn write_bool(Self, Bool) -> Unit
  fn write_byte(Self, Int32) -> Unit
  fn write_i16(Self, Int32) -> Unit
  fn write_i32(Self, Int32) -> Unit
  fn write_i64(Self, Int64) -> Unit
  fn write_double(Self, Double) -> Unit
  fn write_string(Self, String) -> Unit
  fn write_bytes(Self, Bytes) -> Unit
  fn write_list_begin(Self, Int32, Int32) -> Unit
  fn write_map_begin(Self, Int32, Int32, Int32) -> Unit
}

///|
/// Walk past one value of `type_`, whatever it contains.
pub fn skip(proto : &Protocol, type_ : Int32) -> Unit raise ThriftError {
  skip_at(proto, type_, 0)
}

///|
fn skip_at(
  proto : &Protocol,
  type_ : Int32,
  depth : Int32,
) -> Unit raise ThriftError {
  if depth > max_depth {
    raise Depth
  }
  match type_ {
    // A boolean is read, not counted: TCompactProtocol may hold its value in the field
    // header, and reading it is what clears that.
    t if t == type_bool => ignore(proto.read_bool())
    t if t == type_byte => ignore(proto.read_byte())
    t if t == type_i16 => ignore(proto.read_i16())
    t if t == type_i32 => ignore(proto.read_i32())
    t if t == type_i64 => ignore(proto.read_i64())
    t if t == type_double => ignore(proto.read_double())
    t if t == type_string => ignore(proto.read_bytes())
    t if t == type_struct => {
      proto.read_struct_begin()
      for ;; {
        let (field_type, _) = proto.read_field_begin()
        if field_type == type_stop {
          break
        }
        skip_at(proto, field_type, depth + 1)
      }
      proto.read_struct_end()
    }
    t if t == type_list || t == type_set => {
      let (elem, n) = proto.read_list_begin()
      for _ in 0.. {
      let (key, value, n) = proto.read_map_begin()
      for _ in 0.. raise BadType(other)
  }
}

///|
pub impl Show for ThriftError with fn output(self, logger) {
  match self {
    BadVersion(v) => logger.write_string("BadVersion(" + v.to_string() + ")")
    Truncated => logger.write_string("Truncated")
    BadType(t) => logger.write_string("BadType(" + t.to_string() + ")")
    Depth => logger.write_string("Depth")
    Unsupported(what) => logger.write_string("Unsupported(" + what + ")")
    Application(message) => logger.write_string("Application(" + message + ")")
  }
}

///|
pub extend ThriftError with Show::{to_string, output}