/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef THRIFT_PY_PROTOCOL_TCC
#define THRIFT_PY_PROTOCOL_TCC

#include <iterator>

#define CHECK_RANGE(v, min, max) (((v) <= (max)) && ((v) >= (min)))
#define INIT_OUTBUF_SIZE 128

#if PY_MAJOR_VERSION < 3
#include <cStringIO.h>
#else
#include <algorithm>
#endif

namespace apache {
namespace thrift {
namespace py {

#if PY_MAJOR_VERSION < 3

namespace detail {

inline bool input_check(PyObject* input) {
  return PycStringIO_InputCheck(input);
}

inline EncodeBuffer* new_encode_buffer(size_t size) {
  if (!PycStringIO) {
    PycString_IMPORT;
  }
  if (!PycStringIO) {
    return NULL;
  }
  return PycStringIO->NewOutput(size);
}

inline int read_buffer(PyObject* buf, char** output, int len) {
  if (!PycStringIO) {
    PycString_IMPORT;
  }
  if (!PycStringIO) {
    PyErr_SetString(PyExc_ImportError, "failed to import native cStringIO");
    return -1;
  }
  return PycStringIO->cread(buf, output, len);
}
}

template <typename Impl>
inline ProtocolBase<Impl>::~ProtocolBase() {
  if (output_) {
    Py_CLEAR(output_);
  }
}

template <typename Impl>
inline bool ProtocolBase<Impl>::isUtf8(PyObject* typeargs) {
  return PyString_Check(typeargs) && !strncmp(PyString_AS_STRING(typeargs), "UTF8", 4);
}

template <typename Impl>
PyObject* ProtocolBase<Impl>::getEncodedValue() {
  if (!PycStringIO) {
    PycString_IMPORT;
  }
  if (!PycStringIO) {
    return NULL;
  }
  return PycStringIO->cgetvalue(output_);
}

template <typename Impl>
inline bool ProtocolBase<Impl>::writeBuffer(char* data, size_t size) {
  if (!PycStringIO) {
    PycString_IMPORT;
  }
  if (!PycStringIO) {
    PyErr_SetString(PyExc_ImportError, "failed to import native cStringIO");
    return false;
  }
  int len = PycStringIO->cwrite(output_, data, size);
  if (len < 0) {
    PyErr_SetString(PyExc_IOError, "failed to write to cStringIO object");
    return false;
  }
  if (static_cast<size_t>(len) != size) {
    PyErr_Format(PyExc_EOFError, "write length mismatch: expected %lu got %d", size, len);
    return false;
  }
  return true;
}

#else

namespace detail {

inline bool input_check(PyObject* input) {
  // TODO: Check for BytesIO type
  return true;
}

inline EncodeBuffer* new_encode_buffer(size_t size) {
  EncodeBuffer* buffer = new EncodeBuffer;
  buffer->buf.reserve(size);
  buffer->pos = 0;
  return buffer;
}

struct bytesio {
  PyObject_HEAD
#if PY_MINOR_VERSION < 5
      char* buf;
#else
      PyObject* buf;
#endif
  Py_ssize_t pos;
  Py_ssize_t string_size;
};

inline int read_buffer(PyObject* buf, char** output, int len) {
  bytesio* buf2 = reinterpret_cast<bytesio*>(buf);
#if PY_MINOR_VERSION < 5
  *output = buf2->buf + buf2->pos;
#else
  *output = PyBytes_AS_STRING(buf2->buf) + buf2->pos;
#endif
  Py_ssize_t pos0 = buf2->pos;
  buf2->pos = std::min(buf2->pos + static_cast<Py_ssize_t>(len), buf2->string_size);
  return static_cast<int>(buf2->pos - pos0);
}
}

template <typename Impl>
inline ProtocolBase<Impl>::~ProtocolBase() {
  if (output_) {
    delete output_;
  }
}

template <typename Impl>
inline bool ProtocolBase<Impl>::isUtf8(PyObject* typeargs) {
  // while condition for py2 is "arg == 'UTF8'", it should be "arg != 'BINARY'" for py3.
  // HACK: check the length and don't bother reading the value
  return !PyUnicode_Check(typeargs) || PyUnicode_GET_LENGTH(typeargs) != 6;
}

template <typename Impl>
PyObject* ProtocolBase<Impl>::getEncodedValue() {
  return PyBytes_FromStringAndSize(output_->buf.data(), output_->buf.size());
}

template <typename Impl>
inline bool ProtocolBase<Impl>::writeBuffer(char* data, size_t size) {
  size_t need = size + output_->pos;
  if (output_->buf.capacity() < need) {
    try {
      output_->buf.reserve(need);
    } catch (std::bad_alloc& ex) {
      PyErr_SetString(PyExc_MemoryError, "Failed to allocate write buffer");
      return false;
    }
  }
  std::copy(data, data + size, std::back_inserter(output_->buf));
  return true;
}

#endif

namespace detail {

#define DECLARE_OP_SCOPE(name, op)                                                                 \
  template <typename Impl>                                                                         \
  struct name##Scope {                                                                             \
    Impl* impl;                                                                                    \
    bool valid;                                                                                    \
    name##Scope(Impl* thiz) : impl(thiz), valid(impl->op##Begin()) {}                              \
    ~name##Scope() {                                                                               \
      if (valid)                                                                                   \
        impl->op##End();                                                                           \
    }                                                                                              \
    operator bool() { return valid; }                                                              \
  };                                                                                               \
  template <typename Impl, template <typename> class T>                                            \
  name##Scope<Impl> op##Scope(T<Impl>* thiz) {                                                     \
    return name##Scope<Impl>(static_cast<Impl*>(thiz));                                            \
  }
DECLARE_OP_SCOPE(WriteStruct, writeStruct)
DECLARE_OP_SCOPE(ReadStruct, readStruct)
#undef DECLARE_OP_SCOPE

inline bool check_ssize_t_32(Py_ssize_t len) {
  // error from getting the int
  if (INT_CONV_ERROR_OCCURRED(len)) {
    return false;
  }
  if (!CHECK_RANGE(len, 0, std::numeric_limits<int32_t>::max())) {
    PyErr_SetString(PyExc_OverflowError, "size out of range: exceeded INT32_MAX");
    return false;
  }
  return true;
}
}

template <typename T>
bool parse_pyint(PyObject* o, T* ret, int32_t min, int32_t max) {
  long val = PyInt_AsLong(o);

  if (INT_CONV_ERROR_OCCURRED(val)) {
    return false;
  }
  if (!CHECK_RANGE(val, min, max)) {
    PyErr_SetString(PyExc_OverflowError, "int out of range");
    return false;
  }

  *ret = static_cast<T>(val);
  return true;
}

template <typename Impl>
inline bool ProtocolBase<Impl>::checkType(TType got, TType expected) {
  if (expected != got) {
    PyErr_SetString(PyExc_TypeError, "got wrong ttype while reading field");
    return false;
  }
  return true;
}

template <typename Impl>
bool ProtocolBase<Impl>::checkLengthLimit(int32_t len, long limit) {
  if (len < 0) {
    PyErr_Format(PyExc_OverflowError, "negative length: %ld", limit);
    return false;
  }
  if (len > limit) {
    PyErr_Format(PyExc_OverflowError, "size exceeded specified limit: %ld", limit);
    return false;
  }
  return true;
}

template <typename Impl>
bool ProtocolBase<Impl>::readBytes(char** output, int len) {
  if (len < 0) {
    PyErr_Format(PyExc_ValueError, "attempted to read negative length: %d", len);
    return false;
  }
  // TODO(dreiss): Don't fear the malloc.  Think about taking a copy of
  //               the partial read instead of forcing the transport
  //               to prepend it to its buffer.

  int rlen = detail::read_buffer(input_.stringiobuf.get(), output, len);

  if (rlen == len) {
    return true;
  } else if (rlen == -1) {
    return false;
  } else {
    // using building functions as this is a rare codepath
    ScopedPyObject newiobuf(PyObject_CallFunction(input_.refill_callable.get(), refill_signature,
                                                  *output, rlen, len, NULL));
    if (!newiobuf) {
      return false;
    }

    // must do this *AFTER* the call so that we don't deref the io buffer
    input_.stringiobuf.reset(newiobuf.release());

    rlen = detail::read_buffer(input_.stringiobuf.get(), output, len);

    if (rlen == len) {
      return true;
    } else if (rlen == -1) {
      return false;
    } else {
      // TODO(dreiss): This could be a valid code path for big binary blobs.
      PyErr_SetString(PyExc_TypeError, "refill claimed to have refilled the buffer, but didn't!!");
      return false;
    }
  }
}

template <typename Impl>
bool ProtocolBase<Impl>::prepareDecodeBufferFromTransport(PyObject* trans) {
  if (input_.stringiobuf) {
    PyErr_SetString(PyExc_ValueError, "decode buffer is already initialized");
    return false;
  }

  ScopedPyObject stringiobuf(PyObject_GetAttr(trans, INTERN_STRING(cstringio_buf)));
  if (!stringiobuf) {
    return false;
  }
  if (!detail::input_check(stringiobuf.get())) {
    PyErr_SetString(PyExc_TypeError, "expecting stringio input_");
    return false;
  }

  ScopedPyObject refill_callable(PyObject_GetAttr(trans, INTERN_STRING(cstringio_refill)));
  if (!refill_callable) {
    return false;
  }
  if (!PyCallable_Check(refill_callable.get())) {
    PyErr_SetString(PyExc_TypeError, "expecting callable");
    return false;
  }

  input_.stringiobuf.swap(stringiobuf);
  input_.refill_callable.swap(refill_callable);
  return true;
}

template <typename Impl>
bool ProtocolBase<Impl>::prepareEncodeBuffer() {
  output_ = detail::new_encode_buffer(INIT_OUTBUF_SIZE);
  return output_ != NULL;
}

template <typename Impl>
bool ProtocolBase<Impl>::encodeValue(PyObject* value, TType type, PyObject* typeargs) {
  /*
   * Refcounting Strategy:
   *
   * We assume that elements of the thrift_spec tuple are not going to be
   * mutated, so we don't ref count those at all. Other than that, we try to
   * keep a reference to all the user-created objects while we work with them.
   * encodeValue assumes that a reference is already held. The *caller* is
   * responsible for handling references
   */

  switch (type) {

  case T_BOOL: {
    int v = PyObject_IsTrue(value);
    if (v == -1) {
      return false;
    }
    impl()->writeBool(v);
    return true;
  }
  case T_I08: {
    int8_t val;

    if (!parse_pyint(value, &val, std::numeric_limits<int8_t>::min(),
                     std::numeric_limits<int8_t>::max())) {
      return false;
    }

    impl()->writeI8(val);
    return true;
  }
  case T_I16: {
    int16_t val;

    if (!parse_pyint(value, &val, std::numeric_limits<int16_t>::min(),
                     std::numeric_limits<int16_t>::max())) {
      return false;
    }

    impl()->writeI16(val);
    return true;
  }
  case T_I32: {
    int32_t val;

    if (!parse_pyint(value, &val, std::numeric_limits<int32_t>::min(),
                     std::numeric_limits<int32_t>::max())) {
      return false;
    }

    impl()->writeI32(val);
    return true;
  }
  case T_I64: {
    int64_t nval = PyLong_AsLongLong(value);

    if (INT_CONV_ERROR_OCCURRED(nval)) {
      return false;
    }

    if (!CHECK_RANGE(nval, std::numeric_limits<int64_t>::min(),
                     std::numeric_limits<int64_t>::max())) {
      PyErr_SetString(PyExc_OverflowError, "int out of range");
      return false;
    }

    impl()->writeI64(nval);
    return true;
  }

  case T_DOUBLE: {
    double nval = PyFloat_AsDouble(value);
    if (nval == -1.0 && PyErr_Occurred()) {
      return false;
    }

    impl()->writeDouble(nval);
    return true;
  }

  case T_STRING: {
    ScopedPyObject nval;

    if (PyUnicode_Check(value)) {
      nval.reset(PyUnicode_AsUTF8String(value));
      if (!nval) {
        return false;
      }
    } else {
      Py_INCREF(value);
      nval.reset(value);
    }

    Py_ssize_t len = PyBytes_Size(nval.get());
    if (!detail::check_ssize_t_32(len)) {
      return false;
    }

    impl()->writeString(nval.get(), static_cast<int32_t>(len));
    return true;
  }

  case T_LIST:
  case T_SET: {
    SetListTypeArgs parsedargs;
    if (!parse_set_list_args(&parsedargs, typeargs)) {
      return false;
    }

    Py_ssize_t len = PyObject_Length(value);
    if (!detail::check_ssize_t_32(len)) {
      return false;
    }

    if (!impl()->writeListBegin(value, parsedargs, static_cast<int32_t>(len)) || PyErr_Occurred()) {
      return false;
    }
    ScopedPyObject iterator(PyObject_GetIter(value));
    if (!iterator) {
      return false;
    }

    while (PyObject* rawItem = PyIter_Next(iterator.get())) {
      ScopedPyObject item(rawItem);
      if (!encodeValue(item.get(), parsedargs.element_type, parsedargs.typeargs)) {
        return false;
      }
    }

    return true;
  }

  case T_MAP: {
    Py_ssize_t len = PyDict_Size(value);
    if (!detail::check_ssize_t_32(len)) {
      return false;
    }

    MapTypeArgs parsedargs;
    if (!parse_map_args(&parsedargs, typeargs)) {
      return false;
    }

    if (!impl()->writeMapBegin(value, parsedargs, static_cast<int32_t>(len)) || PyErr_Occurred()) {
      return false;
    }
    Py_ssize_t pos = 0;
    PyObject* k = NULL;
    PyObject* v = NULL;
    // TODO(bmaurer): should support any mapping, not just dicts
    while (PyDict_Next(value, &pos, &k, &v)) {
      if (!encodeValue(k, parsedargs.ktag, parsedargs.ktypeargs)
          || !encodeValue(v, parsedargs.vtag, parsedargs.vtypeargs)) {
        return false;
      }
    }
    return true;
  }

  case T_STRUCT: {
    StructTypeArgs parsedargs;
    if (!parse_struct_args(&parsedargs, typeargs)) {
      return false;
    }

    Py_ssize_t nspec = PyTuple_Size(parsedargs.spec);
    if (nspec == -1) {
      PyErr_SetString(PyExc_TypeError, "spec is not a tuple");
      return false;
    }

    detail::WriteStructScope<Impl> scope = detail::writeStructScope(this);
    if (!scope) {
      return false;
    }
    for (Py_ssize_t i = 0; i < nspec; i++) {
      PyObject* spec_tuple = PyTuple_GET_ITEM(parsedargs.spec, i);
      if (spec_tuple == Py_None) {
        continue;
      }

      StructItemSpec parsedspec;
      if (!parse_struct_item_spec(&parsedspec, spec_tuple)) {
        return false;
      }

      ScopedPyObject instval(PyObject_GetAttr(value, parsedspec.attrname));

      if (!instval) {
        return false;
      }

      if (instval.get() == Py_None) {
        continue;
      }

      bool res = impl()->writeField(instval.get(), parsedspec);
      if (!res) {
        return false;
      }
    }
    impl()->writeFieldStop();
    return true;
  }

  case T_STOP:
  case T_VOID:
  case T_UTF16:
  case T_UTF8:
  case T_U64:
  default:
    PyErr_Format(PyExc_TypeError, "Unexpected TType for encodeValue: %d", type);
    return false;
  }

  return true;
}

template <typename Impl>
bool ProtocolBase<Impl>::skip(TType type) {
  switch (type) {
  case T_BOOL:
    return impl()->skipBool();
  case T_I08:
    return impl()->skipByte();
  case T_I16:
    return impl()->skipI16();
  case T_I32:
    return impl()->skipI32();
  case T_I64:
    return impl()->skipI64();
  case T_DOUBLE:
    return impl()->skipDouble();

  case T_STRING: {
    return impl()->skipString();
  }

  case T_LIST:
  case T_SET: {
    TType etype = T_STOP;
    int32_t len = impl()->readListBegin(etype);
    if (len < 0) {
      return false;
    }
    for (int32_t i = 0; i < len; i++) {
      if (!skip(etype)) {
        return false;
      }
    }
    return true;
  }

  case T_MAP: {
    TType ktype = T_STOP;
    TType vtype = T_STOP;
    int32_t len = impl()->readMapBegin(ktype, vtype);
    if (len < 0) {
      return false;
    }
    for (int32_t i = 0; i < len; i++) {
      if (!skip(ktype) || !skip(vtype)) {
        return false;
      }
    }
    return true;
  }

  case T_STRUCT: {
    detail::ReadStructScope<Impl> scope = detail::readStructScope(this);
    if (!scope) {
      return false;
    }
    while (true) {
      TType type = T_STOP;
      int16_t tag;
      if (!impl()->readFieldBegin(type, tag)) {
        return false;
      }
      if (type == T_STOP) {
        return true;
      }
      if (!skip(type)) {
        return false;
      }
    }
    return true;
  }

  case T_STOP:
  case T_VOID:
  case T_UTF16:
  case T_UTF8:
  case T_U64:
  default:
    PyErr_Format(PyExc_TypeError, "Unexpected TType for skip: %d", type);
    return false;
  }

  return true;
}

// Returns a new reference.
template <typename Impl>
PyObject* ProtocolBase<Impl>::decodeValue(TType type, PyObject* typeargs) {
  switch (type) {

  case T_BOOL: {
    bool v = 0;
    if (!impl()->readBool(v)) {
      return NULL;
    }
    if (v) {
      Py_RETURN_TRUE;
    } else {
      Py_RETURN_FALSE;
    }
  }
  case T_I08: {
    int8_t v = 0;
    if (!impl()->readI8(v)) {
      return NULL;
    }
    return PyInt_FromLong(v);
  }
  case T_I16: {
    int16_t v = 0;
    if (!impl()->readI16(v)) {
      return NULL;
    }
    return PyInt_FromLong(v);
  }
  case T_I32: {
    int32_t v = 0;
    if (!impl()->readI32(v)) {
      return NULL;
    }
    return PyInt_FromLong(v);
  }

  case T_I64: {
    int64_t v = 0;
    if (!impl()->readI64(v)) {
      return NULL;
    }
    // TODO(dreiss): Find out if we can take this fastpath always when
    //               sizeof(long) == sizeof(long long).
    if (CHECK_RANGE(v, LONG_MIN, LONG_MAX)) {
      return PyInt_FromLong((long)v);
    }
    return PyLong_FromLongLong(v);
  }

  case T_DOUBLE: {
    double v = 0.0;
    if (!impl()->readDouble(v)) {
      return NULL;
    }
    return PyFloat_FromDouble(v);
  }

  case T_STRING: {
    char* buf = NULL;
    int len = impl()->readString(&buf);
    if (len < 0) {
      return NULL;
    }
    if (isUtf8(typeargs)) {
      return PyUnicode_DecodeUTF8(buf, len, 0);
    } else {
      return PyBytes_FromStringAndSize(buf, len);
    }
  }

  case T_LIST:
  case T_SET: {
    SetListTypeArgs parsedargs;
    if (!parse_set_list_args(&parsedargs, typeargs)) {
      return NULL;
    }

    TType etype = T_STOP;
    int32_t len = impl()->readListBegin(etype);
    if (len < 0) {
      return NULL;
    }
    if (len > 0 && !checkType(etype, parsedargs.element_type)) {
      return NULL;
    }

    bool use_tuple = type == T_LIST && parsedargs.immutable;
    ScopedPyObject ret(use_tuple ? PyTuple_New(len) : PyList_New(len));
    if (!ret) {
      return NULL;
    }

    for (int i = 0; i < len; i++) {
      PyObject* item = decodeValue(etype, parsedargs.typeargs);
      if (!item) {
        return NULL;
      }
      if (use_tuple) {
        PyTuple_SET_ITEM(ret.get(), i, item);
      } else {
        PyList_SET_ITEM(ret.get(), i, item);
      }
    }

    // TODO(dreiss): Consider biting the bullet and making two separate cases
    //               for list and set, avoiding this post facto conversion.
    if (type == T_SET) {
      PyObject* setret;
      setret = parsedargs.immutable ? PyFrozenSet_New(ret.get()) : PySet_New(ret.get());
      return setret;
    }
    return ret.release();
  }

  case T_MAP: {
    MapTypeArgs parsedargs;
    if (!parse_map_args(&parsedargs, typeargs)) {
      return NULL;
    }

    TType ktype = T_STOP;
    TType vtype = T_STOP;
    uint32_t len = impl()->readMapBegin(ktype, vtype);
    if (len > 0 && (!checkType(ktype, parsedargs.ktag) || !checkType(vtype, parsedargs.vtag))) {
      return NULL;
    }

    ScopedPyObject ret(PyDict_New());
    if (!ret) {
      return NULL;
    }

    for (uint32_t i = 0; i < len; i++) {
      ScopedPyObject k(decodeValue(ktype, parsedargs.ktypeargs));
      if (!k) {
        return NULL;
      }
      ScopedPyObject v(decodeValue(vtype, parsedargs.vtypeargs));
      if (!v) {
        return NULL;
      }
      if (PyDict_SetItem(ret.get(), k.get(), v.get()) == -1) {
        return NULL;
      }
    }

    if (parsedargs.immutable) {
      if (!ThriftModule) {
        ThriftModule = PyImport_ImportModule("thrift.Thrift");
      }
      if (!ThriftModule) {
        return NULL;
      }

      ScopedPyObject cls(PyObject_GetAttr(ThriftModule, INTERN_STRING(TFrozenDict)));
      if (!cls) {
        return NULL;
      }

      ScopedPyObject arg(PyTuple_New(1));
      PyTuple_SET_ITEM(arg.get(), 0, ret.release());
      ret.reset(PyObject_CallObject(cls.get(), arg.get()));
    }

    return ret.release();
  }

  case T_STRUCT: {
    StructTypeArgs parsedargs;
    if (!parse_struct_args(&parsedargs, typeargs)) {
      return NULL;
    }
    return readStruct(Py_None, parsedargs.klass, parsedargs.spec);
  }

  case T_STOP:
  case T_VOID:
  case T_UTF16:
  case T_UTF8:
  case T_U64:
  default:
    PyErr_Format(PyExc_TypeError, "Unexpected TType for decodeValue: %d", type);
    return NULL;
  }
}

template <typename Impl>
PyObject* ProtocolBase<Impl>::readStruct(PyObject* output, PyObject* klass, PyObject* spec_seq) {
  int spec_seq_len = PyTuple_Size(spec_seq);
  bool immutable = output == Py_None;
  ScopedPyObject kwargs;
  if (spec_seq_len == -1) {
    return NULL;
  }

  if (immutable) {
    kwargs.reset(PyDict_New());
    if (!kwargs) {
      PyErr_SetString(PyExc_TypeError, "failed to prepare kwargument storage");
      return NULL;
    }
  }

  detail::ReadStructScope<Impl> scope = detail::readStructScope(this);
  if (!scope) {
    return NULL;
  }
  while (true) {
    TType type = T_STOP;
    int16_t tag;
    if (!impl()->readFieldBegin(type, tag)) {
      return NULL;
    }
    if (type == T_STOP) {
      break;
    }
    if (tag < 0 || tag >= spec_seq_len) {
      if (!skip(type)) {
        PyErr_SetString(PyExc_TypeError, "Error while skipping unknown field");
        return NULL;
      }
      continue;
    }

    PyObject* item_spec = PyTuple_GET_ITEM(spec_seq, tag);
    if (item_spec == Py_None) {
      if (!skip(type)) {
        PyErr_SetString(PyExc_TypeError, "Error while skipping unknown field");
        return NULL;
      }
      continue;
    }
    StructItemSpec parsedspec;
    if (!parse_struct_item_spec(&parsedspec, item_spec)) {
      return NULL;
    }
    if (parsedspec.type != type) {
      if (!skip(type)) {
        PyErr_Format(PyExc_TypeError, "struct field had wrong type: expected %d but got %d",
                     parsedspec.type, type);
        return NULL;
      }
      continue;
    }

    ScopedPyObject fieldval(decodeValue(parsedspec.type, parsedspec.typeargs));
    if (!fieldval) {
      return NULL;
    }

    if ((immutable && PyDict_SetItem(kwargs.get(), parsedspec.attrname, fieldval.get()) == -1)
        || (!immutable && PyObject_SetAttr(output, parsedspec.attrname, fieldval.get()) == -1)) {
      return NULL;
    }
  }
  if (immutable) {
    ScopedPyObject args(PyTuple_New(0));
    if (!args) {
      PyErr_SetString(PyExc_TypeError, "failed to prepare argument storage");
      return NULL;
    }
    return PyObject_Call(klass, args.get(), kwargs.get());
  }
  Py_INCREF(output);
  return output;
}
}
}
}
#endif // THRIFT_PY_PROTOCOL_H
