MODULE = Thrift::XS   PACKAGE = Thrift::XS::CompactProtocol

 # new() in parent

 #` Not a standard method, but used for tests
void
resetState(TBinaryProtocol *p)
CODE:
{
  DEBUG_TRACE("resetState()\n");

  p->bool_type     = -1;
  p->bool_id       = -1;
  p->bool_value_id = -1;
  p->last_field_id = 0;

  // clear field queue
  struct field_entry *entry;
  while (!SIMPLEQ_EMPTY(p->last_fields)) {
    entry = SIMPLEQ_FIRST(p->last_fields);
    SIMPLEQ_REMOVE_HEAD(p->last_fields, entries);
    MEM_TRACE("free entry @ %p\n", entry);
    Safefree(entry);
  }
}

void
writeMessageBegin(TBinaryProtocol *p, SV *name, int type, uint32_t seqid)
CODE:
{
  DEBUG_TRACE("writeMessageBegin()\n");

  SV *namecopy = sv_mortalcopy(name); // because we can't modify the original name
  sv_utf8_encode(namecopy);
  uint32_t namelen = sv_len(namecopy);
  SV *data = sv_2mortal(newSV(16 + namelen));
  char tmp[5]; // 5 required for varint32

  // byte protocol ID
  tmp[0] = PROTOCOL_ID;

  // byte version/type
  tmp[1] = (VERSION_N & VERSION_MASK_COMPACT) | ((type << TYPE_SHIFT_AMOUNT) & TYPE_MASK);

  sv_setpvn(data, tmp, 2);

  // varint32 seqid
  int varlen;
  UINT_TO_VARINT(varlen, tmp, seqid, 0);
  sv_catpvn(data, tmp, varlen);

  // varint32 len + string
  UINT_TO_VARINT(varlen, tmp, namelen, 0);
  sv_catpvn(data, tmp, varlen);
  sv_catsv(data, namecopy);

  WRITE_SV(p, data);
}

# writeMessageEnd in parent

void
writeStructBegin(TBinaryProtocol *p, SV *)
CODE:
{
  DEBUG_TRACE("writeStructBegin()\n");

  // No writing here, but we push last_field_id onto the fields stack
  // and reset last_field_id to 0
  struct field_entry *entry;
  New(0, entry, sizeof(struct field_entry), struct field_entry);
  MEM_TRACE("New entry @ %p\n", entry);
  entry->field_id = p->last_field_id;
  SIMPLEQ_INSERT_HEAD(p->last_fields, entry, entries);

  DEBUG_TRACE("writeStructBegin(), insert_head %d\n", entry->field_id);

  p->last_field_id = 0;
}

void
writeStructEnd(TBinaryProtocol *p)
CODE:
{
  DEBUG_TRACE("writeStructEnd()\n");

  // pop last field off the stack and save it in
  // last_field_id
  struct field_entry *entry = SIMPLEQ_FIRST(p->last_fields);
  p->last_field_id = entry->field_id;
  SIMPLEQ_REMOVE_HEAD(p->last_fields, entries);

  DEBUG_TRACE("writeStructEnd(), remove_head %d\n", entry->field_id);

  MEM_TRACE("free entry @ %p\n", entry);
  Safefree(entry);
}

void
writeFieldBegin(TBinaryProtocol *p, SV *name, int type, int id)
CODE:
{
  DEBUG_TRACE("writeFieldBegin()\n");

  PERL_UNUSED_VAR(name);
  if (unlikely(type == T_BOOL)) {
    // Special case, save type/id for use later
    p->bool_type = type;
    p->bool_id = id;
  }
  else {
    write_field_begin_internal(p, type, id, -1);
  }
}

 # writeFieldEnd in parent

 # writeFieldStop in parent

void
writeMapBegin(TBinaryProtocol *p, int keytype, int valtype, uint32_t size)
CODE:
{
  DEBUG_TRACE("writeMapBegin()\n");

  char data[6];

  if (size == 0) {
    data[0] = 0;
    WRITE(p, data, 1);
  }
  else {
    int varlen;
    UINT_TO_VARINT(varlen, data, size, 0);
    data[varlen] = (get_compact_type(keytype) << 4) | get_compact_type(valtype);
    WRITE(p, data, varlen + 1);
  }
}

 # writeMapEnd in parent

void
writeListBegin(TBinaryProtocol *p, int elemtype, int size)
CODE:
{
  DEBUG_TRACE("writeListBegin()\n");

  write_collection_begin_internal(p, elemtype, size);
}

 # writeListEnd in parent

void
writeSetBegin(TBinaryProtocol *p, int elemtype, int size)
CODE:
{
  DEBUG_TRACE("writeSetBegin()\n");

  write_collection_begin_internal(p, elemtype, size);
}

 # writeSetEnd in parent

void
writeBool(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("writeBool()\n");

  if (unlikely(p->bool_type != -1)) {
    // we haven't written the field header yet
    write_field_begin_internal(p, p->bool_type, p->bool_id, SvTRUE(value) ? CTYPE_BOOLEAN_TRUE: CTYPE_BOOLEAN_FALSE);
    p->bool_type = -1;
    p->bool_id = -1;
  }
  else {
    // we're not part of a field, so just write the value.
    char data[1];
    data[0] = SvTRUE(value) ? 1 : 0;

    WRITE(p, data, 1);
  }
}

 # writeByte in parent

void
writeI16(TBinaryProtocol *p, int value)
CODE:
{
  DEBUG_TRACE("writeI16()\n");

  char data[3];
  int varlen;

  uint32_t uvalue = int_to_zigzag(value);
  UINT_TO_VARINT(varlen, data, uvalue, 0);
  WRITE(p, data, varlen);
}

void
writeI32(TBinaryProtocol *p, int value)
CODE:
{
  DEBUG_TRACE("writeI32()\n");

  char data[5];
  int varlen;

  uint32_t uvalue = int_to_zigzag(value);
  UINT_TO_VARINT(varlen, data, uvalue, 0);
  WRITE(p, data, varlen);
}

void
writeI64(TBinaryProtocol *p, SV *value)
CODE:
{
  char data[10];
  int varlen = 0;

  // Stringify the value, then convert to an int64_t
  const char *str = SvPOK(value) ? SvPVX(value) : SvPV_nolen(value);
  int64_t i64 = (int64_t)strtoll(str, NULL, 10);

  DEBUG_TRACE("writeI64(%" PRId64 ")\n", i64);

  uint64_t uvalue = ll_to_zigzag(i64);
  UINT_TO_VARINT(varlen, data, uvalue, 0);
  WRITE(p, data, varlen);
}

void
writeDouble(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("writeDouble()\n");

  char data[8];
  union {
    double d;
    int64_t i;
  } u;

  u.d = (double)SvNV(value);

  data[0] = u.i & 0xff;
  data[1] = (u.i >> 8) & 0xff;
  data[2] = (u.i >> 16) & 0xff;
  data[3] = (u.i >> 24) & 0xff;
  data[4] = (u.i >> 32) & 0xff;
  data[5] = (u.i >> 40) & 0xff;
  data[6] = (u.i >> 48) & 0xff;
  data[7] = (u.i >> 56) & 0xff;

  WRITE(p, data, 8);
}

void
writeString(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("writeString()\n");

  SV *valuecopy = sv_mortalcopy(value);
  if (SvUTF8(value) != 0) {
    sv_utf8_encode(valuecopy);
  }
  int len = sv_len(valuecopy);
  SV *data = sv_2mortal(newSV(5 + len));
  char tmp[5];

  int varlen;
  UINT_TO_VARINT(varlen, tmp, len, 0);
  sv_setpvn(data, tmp, varlen);
  sv_catsv(data, valuecopy);

  WRITE_SV(p, data);
}

void
readMessageBegin(TBinaryProtocol *p, SV *name, SV *type, SV *seqid)
CODE:
{
  DEBUG_TRACE("readMessageBegin()\n");

  SV *tmp;
  char *tmps;
  uint32_t tmpui;

  // read protocol id, version, type
  READ_SV(p, tmp, 2);
  tmps = SvPVX(tmp);

  uint8_t protocol_id = (uint8_t) tmps[0];
  if (protocol_id != (uint8_t) PROTOCOL_ID) {
    THROW_SV("Thrift::TException", newSVpvf("Expected protocol id %d but got %d", PROTOCOL_ID, protocol_id));
  }

  uint8_t version_and_type = (uint8_t) tmps[1];
  uint8_t version = version_and_type & VERSION_MASK_COMPACT;
  if (version != VERSION_N) {
    THROW_SV("Thrift::TException", newSVpvf("Expected version id %d but got %d", VERSION_N, version));
  }

  // set type
  if (SvROK(type))
    sv_setiv(SvRV(type), (version_and_type >> TYPE_SHIFT_AMOUNT) & 0x03);

  // read/set seqid
  READ_VARINT(p, tmpui);
  if (SvROK(seqid))
    sv_setiv(SvRV(seqid), tmpui);

  // read/set name
  READ_VARINT(p, tmpui);
  if (tmpui) {
    READ_SV(p, tmp, tmpui);
    sv_utf8_decode(tmp);
    if (SvROK(name))
      sv_setsv(SvRV(name), tmp);
  }
  else {
    if (SvROK(name))
      sv_setpv(SvRV(name), "");
  }
}

 # readMessageEnd in parent

void
readStructBegin(TBinaryProtocol *p, SV *name)
CODE:
{
  DEBUG_TRACE("readStructBegin()\n");

  // No reading here, but we push last_field_id onto the fields stack
  struct field_entry *entry;
  New(0, entry, sizeof(struct field_entry), struct field_entry);
  MEM_TRACE("New entry @ %p\n", entry);
  entry->field_id = p->last_field_id;
  SIMPLEQ_INSERT_HEAD(p->last_fields, entry, entries);

  p->last_field_id = 0;

  if (SvROK(name))
    sv_setpv(SvRV(name), "");
}

void
readStructEnd(TBinaryProtocol *p)
CODE:
{
  DEBUG_TRACE("readStructEnd()\n");

  // pop last field off the stack
  struct field_entry *entry = SIMPLEQ_FIRST(p->last_fields);
  SIMPLEQ_REMOVE_HEAD(p->last_fields, entries);
  p->last_field_id = entry->field_id;

  MEM_TRACE("free entry @ %p\n", entry);
  Safefree(entry);
}

void
readFieldBegin(TBinaryProtocol *p, SV *name, SV *fieldtype, SV *fieldid)
CODE:
{
  DEBUG_TRACE("readFieldBegin()\n");

  SV *tmp;
  char *tmps;

  PERL_UNUSED_VAR(name);
  // fieldtype byte
  READ_SV(p, tmp, 1);
  tmps = SvPVX(tmp);
  int type = tmps[0];

  if (SvROK(fieldtype))
    sv_setiv(SvRV(fieldtype), get_ttype(type & 0x0f));

  if (type == T_STOP) {
    if (SvROK(fieldid))
      sv_setiv(SvRV(fieldid), 0);
    XSRETURN_EMPTY;
  }

  // fieldid i16 varint
  int fid;

  // mask off the 4 MSB of the type header. it could contain a field id delta.
  uint8_t modifier = ((type & 0xf0) >> 4);
  if (modifier == 0) {
    // pop field
    struct field_entry *entry = SIMPLEQ_FIRST(p->last_fields);
    SIMPLEQ_REMOVE_HEAD(p->last_fields, entries);
    MEM_TRACE("free entry @ %p\n", entry);
    Safefree(entry);

    // not a delta. look ahead for the zigzag varint field id.
    READ_VARINT(p, fid);
    fid = zigzag_to_int(fid);
  }
  else {
    // has a delta. add the delta to the last read field id.
    // pop field
    struct field_entry *entry = SIMPLEQ_FIRST(p->last_fields);
    SIMPLEQ_REMOVE_HEAD(p->last_fields, entries);
    int last = entry->field_id;
    MEM_TRACE("free entry @ %p\n", entry);
    Safefree(entry);

    fid = last + modifier;
  }

  // if this happens to be a boolean field, the value is encoded in the type
  if (is_bool_type(type)) {
    // save the boolean value in a special instance variable.
    p->bool_value_id = (type & 0x0f) == CTYPE_BOOLEAN_TRUE ? 1 : 0;
  }

  // push the new field onto the field stack so we can keep the deltas going.
  struct field_entry *entry;
  New(0, entry, sizeof(struct field_entry), struct field_entry);
  MEM_TRACE("New entry @ %p\n", entry);
  entry->field_id = fid;
  SIMPLEQ_INSERT_HEAD(p->last_fields, entry, entries);

  if (SvROK(fieldid))
    sv_setiv(SvRV(fieldid), fid);
}

 # readFieldEnd in parent

void
readMapBegin(TBinaryProtocol *p, SV *keytype, SV *valtype, SV *size)
CODE:
{
  DEBUG_TRACE("readMapBegin()\n");

  SV *tmp;
  char *tmps;

  // size
  uint32_t isize;
  READ_VARINT(p, isize);
  if (SvROK(size))
    sv_setiv(SvRV(size), isize);

  // key and value type
  READ_SV(p, tmp, 1);
  tmps = SvPVX(tmp);

  // keytype byte
  if (SvROK(keytype))
    sv_setiv(SvRV(keytype), get_ttype((tmps[0] >> 4) & 0xf));

  // valtype byte
  if (SvROK(valtype))
    sv_setiv(SvRV(valtype), get_ttype(tmps[0] & 0xf));
}

 # readMapEnd in parent

void
readListBegin(TBinaryProtocol *p, SV *elemtype, SV *size)
CODE:
{
  DEBUG_TRACE("readListBegin()\n");

  SV *tmp;
  char *tmps;

  // size and type may be in the same byte
  READ_SV(p, tmp, 1);
  tmps = SvPVX(tmp);
  int isize = (tmps[0] >> 4) & 0x0f;
  if (isize == 15) {
    // size is in a varint
    READ_VARINT(p, isize);
  }
  int type = get_ttype(tmps[0] & 0x0f);

  // elemtype byte
  if (SvROK(elemtype))
    sv_setiv(SvRV(elemtype), type);

  // size
  if (SvROK(size))
    sv_setiv(SvRV(size), isize);
}

 # readListEnd in parent

void
readSetBegin(TBinaryProtocol *p, SV *elemtype, SV *size)
CODE:
{
  DEBUG_TRACE("readSetBegin()\n");

  SV *tmp;
  char *tmps;

  // size and type may be in the same byte
  READ_SV(p, tmp, 1);
  tmps = SvPVX(tmp);
  int isize = (tmps[0] >> 4) & 0x0f;
  if (isize == 15) {
    // size is in a varint
    READ_VARINT(p, isize);
  }
  int type = get_ttype(tmps[0] & 0x0f);

  // elemtype byte
  if (SvROK(elemtype))
    sv_setiv(SvRV(elemtype), type);

  // size
  if (SvROK(size))
    sv_setiv(SvRV(size), isize);
}

 # readSetEnd in parent

void
readBool(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("readBool()\n");

  SV *tmp;
  char *tmps;

  // Check for bool_value encoded in the fieldBegin type
  if (p->bool_value_id != -1) {
    if (SvROK(value))
      sv_setiv(SvRV(value), p->bool_value_id);
    p->bool_value_id = -1;
  }
  else {
    READ_SV(p, tmp, 1);
    tmps = SvPVX(tmp);

    if (SvROK(value))
      sv_setiv(SvRV(value), tmps[0] ? 1 : 0);
  }
}

 # readByte in parent

void
readI16(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("readI16()\n");

  uint32_t varint;
  READ_VARINT(p, varint);

  if (SvROK(value))
    sv_setiv(SvRV(value), zigzag_to_int(varint));
}

void
readI32(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("readI32()\n");

  uint32_t varint;
  READ_VARINT(p, varint);

  if (SvROK(value))
    sv_setiv(SvRV(value), zigzag_to_int(varint));
}

void
readI64(TBinaryProtocol *p, SV *value)
CODE:
{
  uint64_t varint;
  READ_VARINT(p, varint);

  DEBUG_TRACE("readI64(%" PRId64 ") (from varint %" PRIu64 ")\n", zigzag_to_ll(varint), varint);

  // Store as a string so it works on 32-bit platforms
  if (SvROK(value)) {
    char string[25];
    STRLEN length;
    length = sprintf(string, "%" PRId64, zigzag_to_ll(varint));
    sv_setpvn(SvRV(value), string, length);
  }
}

void
readDouble(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("readDouble()\n");

  SV *tmp;
  char *tmps;

  READ_SV(p, tmp, 8);
  tmps = SvPVX(tmp);

  uint32_t lo = (uint8_t)tmps[0] |
    ((uint8_t)tmps[1] << 8) |
    ((uint8_t)tmps[2] << 16) |
    ((uint8_t)tmps[3] << 24);
  uint64_t hi = (uint8_t)tmps[4] |
    ((uint8_t)tmps[5] << 8) |
    ((uint8_t)tmps[6] << 16) |
    ((uint8_t)tmps[7] << 24);

  union {
    double d;
    int64_t i;
  } u;
  u.i = (hi << 32) | lo;

  if (SvROK(value))
    sv_setnv(SvRV(value), u.d);
}

void
readString(TBinaryProtocol *p, SV *value)
CODE:
{
  DEBUG_TRACE("readString()\n");

  SV *tmp;

  uint64_t len;
  READ_VARINT(p, len);
  if (len) {
    READ_SV(p, tmp, len);
    sv_utf8_decode(tmp);
    if (SvROK(value))
      sv_setsv(SvRV(value), tmp);
  }
  else {
    if (SvROK(value))
      sv_setpv(SvRV(value), "");
  }
}
