#ifndef PERL_COUCHBASE_H_
#define PERL_COUCHBASE_H_
#define NO_XSLOCKS

#include <sys/types.h> /*for size_t*/
#include <libcouchbase/couchbase.h>
#include <libcouchbase/api3.h>
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

#define PLCB_BKT_CLASSNAME "Couchbase::Bucket"
#define PLCB_RET_CLASSNAME "Couchbase::Document"
#define PLCB_OPCTX_CLASSNAME "Couchbase::OpContext"
#define PLCB_PUB_CONSTANTS_PKG "Couchbase::Constants"
#define PLCB_PRIV_CONSTANTS_PKG "Couchbase::_GlueConstants"
#define PLCB_OBS_PLHELPER "Couchbase::Bucket::__obshelper"
#define PLCB_STATS_PLHELPER "Couchbase::Bucket::__statshelper"
#define PLCB_EVENT_CLASS "Couchbase::IO::Event"
#define PLCB_IOPROCS_CLASS "Couchbase::IO"
#define PLCB_IOPROCS_CONSTANTS_CLASS "Couchbase::IO::Constants"

#if IVSIZE >= 8
#define PLCB_PERL64
#endif
#include "plcb-util.h"

typedef struct PLCB_st PLCB_t;

enum {
    PLCB_CONVERTERS_CUSTOM = 1,
    PLCB_CONVERTERS_JSON,
    PLCB_CONVERTERS_STORABLE
};

enum {
    PLCB_SETTING_INT,
    PLCB_SETTING_UINT,
    PLCB_SETTING_U32,
    PLCB_SETTING_SIZE,
    PLCB_SETTING_STRING,
    PLCB_SETTING_TIMEOUT
};

enum {
    PLCB_CMD_GET,
    PLCB_CMD_GAT,
    PLCB_CMD_TOUCH,
    PLCB_CMD_LOCK,
    PLCB_CMD_SET,
    PLCB_CMD_ADD,
    PLCB_CMD_REPLACE,
    PLCB_CMD_COUNTER,
    PLCB_CMD_APPEND,
    PLCB_CMD_PREPEND,
    PLCB_CMD_REMOVE,
    PLCB_CMD_UNLOCK,
    PLCB_CMD_STATS,
    PLCB_CMD_KEYSTATS,
    PLCB_CMD_OBSERVE,
    PLCB_CMD_ENDURE
};

enum {
    PLCB_RETIDX_KEY = 0,
    PLCB_RETIDX_VALUE,
    PLCB_RETIDX_ERRNUM,
    PLCB_RETIDX_CAS,
    PLCB_RETIDX_OPTIONS,
    PLCB_RETIDX_EXP,
    PLCB_RETIDX_FMTSPEC,
    PLCB_RETIDX_CALLBACK,
    PLCB_RETIDX_MAX
};

enum {
    PLCB_OPCTXIDX_FLAGS = 0,
    PLCB_OPCTXIDX_CBO,
    PLCB_OPCTXIDX_REMAINING,
    PLCB_OPCTXIDX_QUEUE,
    PLCB_OPCTXIDX_EXTRA
};

enum {
    PLCB_LF_JSON = 0x00,
    PLCB_LF_STORABLE = 0x01 << 3,
    PLCB_LF_RAW = 0x03 << 3,
    PLCB_LF_UTF8 = 0x04 << 3,
    PLCB_LF_MASK = 0xFF,

    PLCB_CF_NONE,
    PLCB_CF_PRIVATE = 0x01 << 24,
    PLCB_CF_STORABLE = PLCB_CF_PRIVATE,

    PLCB_CF_JSON = 0x02 << 24,
    PLCB_CF_RAW = 0x03 << 24,
    PLCB_CF_UTF8 = 0x04 << 24,
    PLCB_CF_MASK = 0xFF << 24
};

enum {
    PLCB_EVIDX_FD,
    PLCB_EVIDX_DUPFH,
    PLCB_EVIDX_WATCHFLAGS,
    PLCB_EVIDX_OPAQUE,
    PLCB_EVIDX_PLDATA,
    PLCB_EVIDX_TYPE,
    PLCB_EVIDX_MAX
};

/*various types of actions which may be taken by the callback*/
enum {
    PLCB_EVACTION_WATCH,
    PLCB_EVACTION_UNWATCH,
    PLCB_EVACTION_INIT,
    PLCB_EVACTION_CLEANUP
};

enum {
    PLCB_EVTYPE_IO,
    PLCB_EVTYPE_TIMER
};

struct PLCB_st {
    lcb_t instance; /*our library handle*/
    HV *ret_stash; /*stash with which we bless our return objects*/
    HV *view_stash;
    HV *design_stash;
    HV *handle_av_stash;
    HV *opctx_sync_stash;
    HV *opctx_cb_stash;

    int connected;
    int wait_for_kv; /* Awaiting KV completion */
    int wait_for_views; /* Awaiting views completion */
    
    SV *cv_serialize;
    SV *cv_deserialize;
    SV *cv_jsonenc;
    SV *cv_jsondec;
    SV *cv_customenc;
    SV *cv_customdec;

    SV *curctx;
    SV *cachectx;
    SV *selfobj;
    SV *ioprocs;
    SV *udata;
    SV *conncb;

    /*how many operations are pending on this object*/
    int npending;
    int async;
};

#define plcb_kv_wait(obj) do { \
    (obj)->wait_for_kv = 1; \
    lcb_wait3((obj)->instance, LCB_WAIT_NOCHECK); \
} while (0);

#define plcb_views_wait(obj) do { \
    (obj)->wait_for_views = 1; \
    lcb_wait3((obj)->instance, LCB_WAIT_NOCHECK); \
} while (0);

#define plcb_kv_waitdone(obj) do { \
    (obj)->wait_for_kv = 0; \
    plcb_evloop_wait_unref(obj); \
} while (0);

#define plcb_views_waitdone(obj) do { \
    (obj)->wait_for_views = 0; \
    plcb_evloop_wait_unref(obj); \
} while (0);

typedef struct {
    unsigned nremaining;
    unsigned flags;
    int waiting;
    HV *docs;
    SV *parent; /* PLCB_T */
    lcb_MULTICMD_CTX *multi;
    union {
        SV *callback; /* For async only */
        AV *ctxqueue; /* For queued operations */
    } u;
} plcb_OPCTX;

typedef struct {
    int cmdbase; /* Effective command passed, without flags or modifiers */
    PLCB_t *parent;
    AV *docav; /* The document */
    SV *opctx; /* The context */
    SV *cmdopts; /* Command options */
    SV *docrv; /* Reference for the document */
    void *cookie;
    plcb_OPCTX *ctxptr;
} plcb_SINGLEOP;

/* Temporary structure used for encoding/storing values */
typedef struct {
    SV *value;
    uint32_t flags;
    uint32_t spec;
    short need_free;
    const char *encoded;
    size_t len;
} plcb_DOCVAL;

#define PLCB_OPCTXf_IMPLICIT 0x01
#define PLCB_OPCTXf_CALLEACH 0x02
#define PLCB_OPCTXf_CALLDONE 0x04
#define PLCB_OPCTXf_WAITONE 0x08

/*need to include this after defining PLCB_t*/
#include "plcb-return.h"
#include "perl-couchbase-couch.h"
#include "plcb-args.h"

void plcb_callbacks_setup(PLCB_t *object);

/*options for common constructor settings*/
void plcb_ctor_cbc_opts(AV *options, struct lcb_create_st *cropts);
void plcb_ctor_conversion_opts(PLCB_t *object, AV *options);
void plcb_ctor_init_common(PLCB_t *object, lcb_t instance, AV *options);

/*cleanup functions*/
void plcb_cleanup(PLCB_t *object);

/*conversion functions*/
void
plcb_convert_storage(PLCB_t* object, AV *doc, plcb_DOCVAL *vspec);

void plcb_convert_storage_free(PLCB_t *object, plcb_DOCVAL *vspec);

SV*
plcb_convert_retrieval(PLCB_t *object, AV *doc, const char *data, size_t data_len, uint32_t flags);


/**
 * This function decrements the wait count by one, and possibly calls stop_event_loop
 * if the reference count has hit 0.
 */
void plcb_evloop_wait_unref(PLCB_t *obj);

/**
 * Returns a new blessed operation context, also makes it the current
 * context
 */
SV *plcb_opctx_new(PLCB_t *, int);
void plcb_opctx_clear(PLCB_t *parent);
void plcb_opctx_initop(plcb_SINGLEOP *so, PLCB_t *parent, SV *doc, SV *ctx, SV *options);
SV * plcb_opctx_return(plcb_SINGLEOP *so, lcb_error_t err);
void plcb_opctx_submit(PLCB_t *parent, plcb_OPCTX *ctx);

#define plcb_opctx_is_cmd_multi(cmd) \
    ((cmd) == PLCB_CMD_OBSERVE || (cmd) == PLCB_CMD_STATS)

/** Operation functions */
SV *PLCB_op_get(PLCB_t*,plcb_SINGLEOP*);
SV *PLCB_op_set(PLCB_t*,plcb_SINGLEOP*);
SV* PLCB_op_counter(PLCB_t *object, plcb_SINGLEOP *opinfo);
SV *PLCB_op_remove(PLCB_t*,plcb_SINGLEOP*);
SV *PLCB_op_observe(PLCB_t *object, plcb_SINGLEOP *args);
SV *PLCB_op_endure(PLCB_t *object, plcb_SINGLEOP *args);
SV *PLCB_op_unlock(PLCB_t *object, plcb_SINGLEOP *args);
SV *PLCB_op_stats(PLCB_t *object, plcb_SINGLEOP *args);
SV *PLCB_op_observe(PLCB_t *object, plcb_SINGLEOP *args);
SV *PLCB_op_endure(PLCB_t *object, plcb_SINGLEOP *opinfo);

SV *
PLCB_args_return(plcb_SINGLEOP *so, lcb_error_t err);

void plcb_define_constants(void);

typedef struct plcb_EVENT_st plcb_EVENT;
struct plcb_EVENT_st {
    /* Corresponding Perl event object */
    AV *pl_event;
    SV *rv_event;

    int evtype;
    lcb_ioE_callback lcb_handler;
    void *lcb_arg;
    short flags;

    /*FD from libcouchbase*/
    lcb_socket_t fd;
    lcb_io_opt_t ioptr;
};

/*our base object*/
typedef struct {
    lcb_io_opt_t iops_ptr;
    SV *userdata;
    SV *action_sv;
    SV *flags_sv;
    SV *usec_sv;
    SV *sched_r_sv;
    SV *sched_w_sv;
    SV *stop_r_sv;
    SV *stop_w_sv;

    SV *selfrv;
    SV *cv_evmod; /* Modify an event */
    SV *cv_timermod; /* Modify a timer */
    SV *cv_evinit;
    SV *cv_evclean;
    SV *cv_tminit;
    SV *cv_tmclean;
    int refcount;
} plcb_IOPROCS;

#define PLCB_READ_EVENT LCB_READ_EVENT
#define PLCB_WRITE_EVENT LCB_WRITE_EVENT

SV * PLCB_ioprocs_new(SV *options);
void PLCB_ioprocs_dtor(lcb_io_opt_t cbcio);

/* Declare these ahead of time */
XS(boot_Couchbase__View);
XS(boot_Couchbase__BucketConfig);
XS(boot_Couchbase__IO);

#endif /* PERL_COUCHBASE_H_ */
