static int
handle_append_flags(pycbc_Bucket *self, PyObject **flagsobj)
{
    unsigned long val = 0;

    if (*flagsobj == NULL || *flagsobj == Py_None) {
        *flagsobj = pycbc_helpers.fmt_utf8_flags;
        return 0;
    }

    if (self->tc) {
        return 0; /* let the transcoder handle it */
    }

    val = pycbc_IntAsUL(*flagsobj);
    if (val == (unsigned long)-1) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0, "invalid flags", *flagsobj);
        return -1;
    }

    if ((val & PYCBC_FMT_BYTES) == PYCBC_FMT_BYTES) {
        return 0;
    } else if ((val & PYCBC_FMT_UTF8) == PYCBC_FMT_UTF8) {
        return 0;
    }

    PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0, "Only FMT_BYTES and FMT_UTF8 are supported for append/prepend", *flagsobj);
    return -1;

}
static int
do_call_tc(pycbc_Bucket *conn,
          PyObject *obj,
          PyObject *flags,
          PyObject **result,
          int mode)
{
    PyObject *meth = NULL;
    PyObject *args = NULL;
    PyObject *strlookup = NULL;
    int ret = -1;

    switch (mode) {
    case ENCODE_KEY:
        strlookup = pycbc_helpers.tcname_encode_key;
        args = PyTuple_Pack(1, obj);
        break;
    case DECODE_KEY:
        strlookup = pycbc_helpers.tcname_decode_key;
        args = PyTuple_Pack(1, obj);
        break;

    case ENCODE_VALUE:
        strlookup = pycbc_helpers.tcname_encode_value;
        args = PyTuple_Pack(2, obj, flags);
        break;

    case DECODE_VALUE:
        strlookup = pycbc_helpers.tcname_decode_value;
        args = PyTuple_Pack(2, obj, flags);
        break;
    }
    if (args == NULL) {
        PYCBC_EXC_WRAP(PYCBC_EXC_INTERNAL, 0, "Couldn't build arguments");
        goto GT_DONE;
    }

    meth = PyObject_GetAttr(conn->tc, strlookup);
    if (!meth) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING, 0,
                           "Couldn't find transcoder method",
                           conn->tc);
        goto GT_DONE;
    }
    *result = PyObject_Call(meth, args, NULL);
    if (*result) {
        ret = 0;
    } else {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING, 0,
                           "User-Defined transcoder failed",
                           obj);
    }

    GT_DONE:
    Py_XDECREF(meth);
    Py_XDECREF(args);
    return ret;
}
static void
mk_sd_error(pycbc__SDResult *res,
    pycbc_MultiResult *mres, lcb_error_t rc, size_t ix)
{
    PyObject *spec = PyTuple_GET_ITEM(res->specs, ix);
    PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_LCBERR, rc, "Subcommand failure", spec);
    pycbc_multiresult_adderr(mres);
}
/**
 * This is only called if 'o' is not bytes
 */
static PyObject*
convert_to_bytesobj(PyObject *o)
{
    PyObject *bytesobj = NULL;
    pycbc_assert(!PyBytes_Check(o));

    if (PyUnicode_Check(o)) {
        bytesobj = PyUnicode_AsUTF8String(o);
    }

    if (!bytesobj) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING,
                           0, "Couldn't convert object to bytes",
                           o);
    }
    return bytesobj;
}
static int
load_cached_method(PyObject *obj,
                   PyObject *attr, PyObject **target, int optional)
{
    *target = PyObject_GetAttr(obj, attr);
    if (*target) {
        if (!PyCallable_Check(*target)) {
            PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0,
                               "Invalid IOPS object", obj);
            return -1;
        }
        return 0;
    }

    if (optional) {
        PyErr_Clear();
        return 0;
    }

    return -1;
}
static int
handle_item_kv(pycbc_Item *itm, PyObject *options, const struct storecmd_vars *scv,
    struct single_key_context *skc)
{
    int rv;
    PyObject *ttl_O = NULL, *flagsobj_Oalt = NULL, *igncas_O = NULL;
    PyObject *frag_O = NULL;
    static char *itm_optlist[] = {
            "ttl", "format", "ignore_cas", "fragment", NULL };

    lcb_cas_t itmcas = itm->cas;
    skc->value = itm->value;

    if (options) {
        rv = PyArg_ParseTupleAndKeywords(pycbc_DummyTuple, options, "|OOOO",
            itm_optlist, &ttl_O, &flagsobj_Oalt, &igncas_O, &frag_O);
        if (!rv) {
            PYCBC_EXC_WRAP(PYCBC_EXC_ARGUMENTS, 0,
                           "Couldn't parse item options");
            return -1;
        }

        if (ttl_O) {
            if (-1 == pycbc_get_ttl(ttl_O, &skc->ttl, 1)) {
                return -1;
            }

            if (!skc->ttl) {
                skc->ttl = scv->ttl;
            }
        }

        if (flagsobj_Oalt && flagsobj_Oalt != Py_None) {
            skc->flagsobj = flagsobj_Oalt;
        }

        if (igncas_O && PyObject_IsTrue(igncas_O)) {
            itmcas = 0;
        }

        if (frag_O == NULL) {
            if (scv->operation == LCB_APPEND || scv->operation == LCB_PREPEND) {
                PYCBC_EXC_WRAP(PYCBC_EXC_ARGUMENTS, 0, "append/prepend must provide options with 'fragment' specifier");
                return -1;
            }

        } else {
            if (scv->operation != LCB_APPEND && scv->operation != LCB_PREPEND) {
                PYCBC_EXC_WRAP(PYCBC_EXC_ARGUMENTS, 0, "'fragment' only valid for append/prepend");
                return -1;
            }

            skc->value = frag_O;
        }

    } else {
        if (scv->operation == LCB_APPEND || scv->operation == LCB_PREPEND) {
            PYCBC_EXC_WRAP(PYCBC_EXC_ARGUMENTS, 0, "append/prepend must provide options with 'fragment' specifier");
            return -1;
        }
    }

    if (!skc->value) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0, "Value is empty", skc->value);
        return -1;
    }

    skc->cas = itmcas;
    return 0;
}
static int
encode_common(PyObject **o, void **buf, size_t *nbuf, lcb_uint32_t flags)
{
    PyObject *bytesobj;
    Py_ssize_t plen;
    int rv;

    if (flags == PYCBC_FMT_UTF8) {
#if PY_MAJOR_VERSION == 2
        if (PyString_Check(*o)) {
#else
        if (0) {
#endif
            bytesobj = *o;
            Py_INCREF(*o);
        } else {
            if (!PyUnicode_Check(*o)) {
                PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING,
                                   0, "Must be unicode or string", *o);
                return -1;
            }
            bytesobj = PyUnicode_AsUTF8String(*o);
        }

    } else if (flags == PYCBC_FMT_BYTES) {
        if (PyBytes_Check(*o) || PyByteArray_Check(*o)) {
            bytesobj = *o;
            Py_INCREF(*o);

        } else {
            PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING, 0,
                               "Must be bytes or bytearray", *o);
            return -1;
        }

    } else {
        PyObject *args = NULL;
        PyObject *helper;

        if (flags == PYCBC_FMT_PICKLE) {
            helper = pycbc_helpers.pickle_encode;

        } else if (flags == PYCBC_FMT_JSON) {
            helper = pycbc_helpers.json_encode;

        } else {
            PYCBC_EXC_WRAP(PYCBC_EXC_ARGUMENTS, 0, "Unrecognized format");
            return -1;
        }

        args = PyTuple_Pack(1, *o);
        bytesobj = PyObject_CallObject(helper, args);
        Py_DECREF(args);

        if (!bytesobj) {
            PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING,
                               0, "Couldn't encode value", *o);
            return -1;
        }

        if (!PyBytes_Check(bytesobj)) {
            PyObject *old = bytesobj;
            bytesobj = convert_to_bytesobj(old);
            Py_DECREF(old);
            if (!bytesobj) {
                return -1;
            }
        }
    }

    if (PyByteArray_Check(bytesobj)) {
        *buf = PyByteArray_AS_STRING(bytesobj);
        plen = PyByteArray_GET_SIZE(bytesobj);
        rv = 0;
    } else {
        rv = PyBytes_AsStringAndSize(bytesobj, (char**)buf, &plen);
    }

    if (rv < 0) {
        Py_DECREF(bytesobj);
        PYCBC_EXC_WRAP(PYCBC_EXC_ENCODING, 0, "Couldn't encode value");
        return -1;
    }

    *nbuf = plen;
    *o = bytesobj;
    return 0;
}

static int
decode_common(PyObject **vp, const char *buf, size_t nbuf, lcb_uint32_t flags)
{
    PyObject *decoded = NULL;
    lcb_U32 c_flags, l_flags;

    c_flags = flags & PYCBC_FMT_COMMON_MASK;
    l_flags = flags & PYCBC_FMT_LEGACY_MASK;

    #define FMT_MATCHES(fmtbase) \
        (c_flags == PYCBC_FMT_COMMON_##fmtbase || l_flags == PYCBC_FMT_LEGACY_##fmtbase)

    if (FMT_MATCHES(UTF8)) {
        decoded = convert_to_string(buf, nbuf, CONVERT_MODE_UTF8_ONLY);
        if (!decoded) {
            return -1;
        }

    } else if (FMT_MATCHES(BYTES)) {
        GT_BYTES:
        decoded = convert_to_string(buf, nbuf, CONVERT_MODE_BYTES_ONLY);
        pycbc_assert(decoded);

    } else {
        PyObject *converter = NULL;
        PyObject *args = NULL;
        PyObject *first_arg = NULL;

        if (FMT_MATCHES(PICKLE)) {
            converter = pycbc_helpers.pickle_decode;
            first_arg = convert_to_string(buf, nbuf, CONVERT_MODE_BYTES_ONLY);
            pycbc_assert(first_arg);

        } else if (FMT_MATCHES(JSON)) {
            converter = pycbc_helpers.json_decode;
            first_arg = convert_to_string(buf, nbuf, CONVERT_MODE_UTF8_ONLY);

            if (!first_arg) {
                return -1;
            }

        } else {
            PyErr_Warn(PyExc_UserWarning, "Unrecognized flags. Forcing bytes");
            goto GT_BYTES;
        }

        pycbc_assert(first_arg);
        args = PyTuple_Pack(1, first_arg);
        decoded = PyObject_CallObject(converter, args);

        Py_DECREF(args);
        Py_DECREF(first_arg);
    }

    if (!decoded) {
        PyObject *bytes_tmp = PyBytes_FromStringAndSize(buf, nbuf);
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ENCODING, 0, "Failed to decode bytes",
                           bytes_tmp);
        Py_XDECREF(bytes_tmp);
        return -1;
    }

    *vp = decoded;
    return 0;

    #undef FMT_MATCHES
}
int
pycbc_tc_encode_value(pycbc_Bucket *conn,
                       PyObject **value,
                       PyObject *flag_v,
                       void **buf,
                       size_t *nbuf,
                       lcb_uint32_t *flags)
{
    PyObject *flags_obj;
    PyObject *orig_value;
    PyObject *new_value = NULL;
    PyObject *result_tuple = NULL;
    lcb_uint32_t flags_stackval;
    int rv;
    Py_ssize_t plen;

    orig_value = *value;

    if (!flag_v) {
        flag_v = conn->dfl_fmt;
    }

    if (!conn->tc) {

        if (flag_v == pycbc_helpers.fmt_auto) {
            flag_v = pycbc_tc_determine_format(*value);
        }

        rv = pycbc_get_u32(flag_v, &flags_stackval);
        if (rv < 0) {
            PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0,
                               "Bad value for flags",
                               flag_v);
            return -1;
        }

        *flags = flags_stackval;
        return encode_common(value, buf, nbuf, flags_stackval);
    }

    /**
     * Calling into Transcoder
     */

    rv = do_call_tc(conn, orig_value, flag_v, &result_tuple, ENCODE_VALUE);
    if (rv < 0) {
        return -1;
    }

    if (!PyTuple_Check(result_tuple) || PyTuple_GET_SIZE(result_tuple) != 2) {
        PYCBC_EXC_WRAP_EX(PYCBC_EXC_ENCODING, 0,
                          "Expected return of (bytes, flags)",
                          orig_value,
                          result_tuple);

        Py_XDECREF(result_tuple);
        return -1;

    }

    new_value = PyTuple_GET_ITEM(result_tuple, 0);
    flags_obj = PyTuple_GET_ITEM(result_tuple, 1);

    if (new_value == NULL || flags_obj == NULL) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_INTERNAL, 0, "Tuple GET_ITEM had NULL",
                           result_tuple);

        Py_XDECREF(result_tuple);
        return -1;
    }

    rv = pycbc_get_u32(flags_obj, &flags_stackval);
    if (rv < 0) {
        Py_XDECREF(result_tuple);
        PYCBC_EXC_WRAP_VALUE(PYCBC_EXC_ENCODING, 0,
                             "Transcoder.encode_value() returned a bad "
                             "value for flags", orig_value);
        return -1;
    }

    *flags = flags_stackval;
    rv = PyBytes_AsStringAndSize(new_value, (char**)buf, &plen);
    if (rv == -1) {
        Py_XDECREF(result_tuple);

        PYCBC_EXC_WRAP_VALUE(PYCBC_EXC_ENCODING, 0,
                             "Value returned by Transcoder.encode_value() "
                             "could not be converted to bytes",
                orig_value);
        return -1;
    }

    *value = new_value;
    *nbuf = plen;

    Py_INCREF(new_value);
    Py_XDECREF(result_tuple);

    return 0;
}
int
pycbc_tc_encode_value(pycbc_Bucket *conn, PyObject *srcbuf, PyObject *srcflags,
                      pycbc_pybuffer *dstbuf, lcb_U32 *dstflags)
{
    PyObject *flags_obj;
    PyObject *new_value = NULL;
    PyObject *result_tuple = NULL;
    lcb_U32 flags_stackval;
    int rv;
    Py_ssize_t plen;

    if (!srcflags) {
        srcflags = conn->dfl_fmt;
    }

    if (!conn->tc) {
        if (srcflags == pycbc_helpers.fmt_auto) {
            srcflags = pycbc_tc_determine_format(srcbuf);
        }

        rv = pycbc_get_u32(srcflags, &flags_stackval);
        if (rv < 0) {
            PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_ARGUMENTS, 0,
                               "Bad value for flags", srcflags);
            return -1;
        }

        *dstflags = flags_stackval;
        return encode_common(srcbuf, dstbuf, flags_stackval);
    }

    /**
     * Calling into Transcoder
     */
    rv = do_call_tc(conn, srcbuf, srcflags, &result_tuple, ENCODE_VALUE);
    if (rv < 0) {
        return -1;
    }

    if (!PyTuple_Check(result_tuple) || PyTuple_GET_SIZE(result_tuple) != 2) {
        PYCBC_EXC_WRAP_EX(PYCBC_EXC_ENCODING, 0,
                          "Expected return of (bytes, flags)",
                          srcbuf, result_tuple);

        Py_XDECREF(result_tuple);
        return -1;

    }

    new_value = PyTuple_GET_ITEM(result_tuple, 0);
    flags_obj = PyTuple_GET_ITEM(result_tuple, 1);

    if (new_value == NULL || flags_obj == NULL) {
        PYCBC_EXC_WRAP_OBJ(PYCBC_EXC_INTERNAL, 0, "Tuple GET_ITEM had NULL",
                           result_tuple);

        Py_XDECREF(result_tuple);
        return -1;
    }

    rv = pycbc_get_u32(flags_obj, &flags_stackval);
    if (rv < 0) {
        Py_XDECREF(result_tuple);
        PYCBC_EXC_WRAP_VALUE(PYCBC_EXC_ENCODING, 0,
                             "Transcoder.encode_value() returned a bad "
                             "value for flags", srcbuf);
        return -1;
    }

    *dstflags = flags_stackval;
    rv = PyBytes_AsStringAndSize(new_value, (char**)&dstbuf->buffer, &plen);
    if (rv == -1) {
        Py_XDECREF(result_tuple);
        PYCBC_EXC_WRAP_VALUE(PYCBC_EXC_ENCODING, 0,
                             "Value returned by Transcoder.encode_value() "
                             "could not be converted to bytes", srcbuf);
        return -1;
    }

    dstbuf->pyobj = new_value;
    dstbuf->length = plen;

    Py_INCREF(new_value);
    Py_XDECREF(result_tuple);

    return 0;
}