#define PERL_NO_GET_CONTEXT 1

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <assert.h>
#include <stdlib.h>

#include <uv.h>

#if defined(DEBUG) && DEBUG > 0
 #define DEBUG_PRINT(fmt, args...) fprintf(stderr, "C -- %s:%d:%s(): " fmt, \
    __FILE__, __LINE__, __func__, ##args)
#else
 #define DEBUG_PRINT(fmt, args...) /* Don't do anything in release builds */
#endif

#include "perl-backcompat.h"
#include "uv-backcompat.h"

#ifdef MULTIPLICITY
#  define storeTHX(var)  (var) = aTHX
#  define dTHXfield(var) tTHX var;
#else
#  define storeTHX(var)  dNOOP
#  define dTHXfield(var)
#endif

#if defined(__MINGW32__) || defined(WIN32)
#  define HAVE_MSWIN32
#  include <io.h> /* we need _get_osfhandle() on windows */
#  define _MAKE_SOCK(f) (_get_osfhandle(f))
#else
#  define _MAKE_SOCK(f) (f)
#endif

#ifdef AI_V4MAPPED
#  define DEFAULT_AI_FLAGS  (AI_V4MAPPED|AI_ADDRCONFIG)
#else
#  define DEFAULT_AI_FLAGS  (AI_ADDRCONFIG)
#endif

#define do_callback_accessor(var, cb) MY_do_callback_accessor(aTHX_ var, cb)
static SV *MY_do_callback_accessor(pTHX_ SV **var, SV *cb)
{
    if(cb && SvOK(cb)) {
        SvREFCNT_dec(*var);

        *var = newSVsv(cb);
    }

    if(*var && SvOK(*var))
        return SvREFCNT_inc(*var);
    else
        return &PL_sv_undef;
}

#define newSV_error(err)  MY_newSV_error(aTHX_ err)
static SV *MY_newSV_error(pTHX_ int err)
{
    SV *sv = newSVpv(err ? uv_strerror(err) : "", 0);
    sv_upgrade(sv, SVt_PVIV);
    SvIV_set(sv, err);
    SvIOK_on(sv);

    return sv;
}

static HV *make_errstash(pTHX_ int err)
{
    /* Technically a memory leak within libuv if err is unknown; we should
     * consider using uv_err_name_r()
     */
    SV *name = newSVpvf("UV::Exception::%s::", uv_err_name(err));
    sv_2mortal(name);

    HV *stash = get_hv(SvPVX(name), 0);
    if(stash) return stash;

    stash = get_hv(SvPVX(name), GV_ADD);

    /* push @ISA, "UV::Exception" */
    sv_catpvs(name, "ISA");
    av_push(get_av(SvPVX(name), GV_ADD), newSVpvs_share("UV::Exception"));

    return stash;
}

#define THROWERRSV(sv, err)                                               \
    do {                                                                  \
        SV *msgsv = mess_sv(sv, TRUE);                                    \
        sv_upgrade(msgsv, SVt_PVIV);                                      \
        SvIV_set(msgsv, err); SvIOK_on(msgsv);                            \
        croak_sv(sv_bless(newRV_noinc(msgsv), make_errstash(aTHX_ err))); \
    } while(0)

#define THROWERR(message, err)                                            \
    THROWERRSV(newSVpvf(message " (%d): %s", err, uv_strerror(err)), err)

#ifdef HEKf
#  define CHECKCALL(call)                                \
  do {                                                   \
    int err = call;                                      \
    if(err != 0)                                         \
      THROWERRSV(newSVpvf("Couldn't %" HEKf " (%d): %s", \
        HEKfARG(GvNAME_HEK(CvGV(cv))),                   \
        err, uv_strerror(err)), err);                    \
  } while(0)
#else
#  define CHECKCALL(call)                                \
  do {                                                   \
    int err = call;                                      \
    if(err != 0)                                         \
      THROWERRSV(newSVpvf("Couldn't %s (%d): %s",        \
        GvNAME(CvGV(cv)),                                \
        err, uv_strerror(err)), err);                    \
  } while(0)
#endif

/**************
 * UV::Handle *
 **************/

#define FIELDS_UV__Handle     \
    SV *selfrv;               \
    dTHXfield(perl)           \
    SV *data;                 \
    SV *on_close;             \
    bool destroy_after_close;

typedef struct UV__Handle {
    uv_handle_t *h;
    FIELDS_UV__Handle
} *UV__Handle;

#define NEW_UV__Handle(var, type) \
    Newxc(var, sizeof(*var) + sizeof(type), char, void); \
    var->h = (type *)((char *)var + sizeof(*var));

#define INIT_UV__Handle(handle)  {      \
  handle->h->data = handle;             \
  storeTHX(handle->perl);               \
  handle->data     = NULL;              \
  handle->on_close = NULL;              \
  handle->destroy_after_close = FALSE;  \
}

static void destroy_handle(UV__Handle self);
static void destroy_handle_base(pTHX_ UV__Handle self)
{
    SvREFCNT_dec(self->data);
    SvREFCNT_dec(self->on_close);

    /* No need to destroy self->selfrv because Perl is already destroying
     * it, being the reason we are invoked in the first place
     */

    Safefree(self);
}

static void on_alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
{
    Newx(buf->base, suggested, char);
    buf->len = suggested;
}

static void on_close_cb(uv_handle_t *handle)
{
    UV__Handle  self;
    SV         *cb;

    if(!handle || !handle->data) return;

    self = handle->data;

    if((cb = self->on_close) && SvOK(cb)) {
        dTHXa(self->perl);
        dSP;
        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        EXTEND(SP, 1);
        mPUSHs(newRV_inc(self->selfrv));
        PUTBACK;

        call_sv(cb, G_DISCARD|G_VOID);

        FREETMPS;
        LEAVE;
    }

    if(self->destroy_after_close)
        destroy_handle(handle->data);
}

/**************
 * UV::Stream *
 **************/

#define FIELDS_UV__Stream \
    SV *on_read;          \
    SV *on_connection;

#define INIT_UV__Stream(stream)  { \
    stream->on_read = NULL;        \
    stream->on_connection = NULL;  \
}

typedef struct UV__Stream {
    uv_stream_t *h;
    FIELDS_UV__Handle
    FIELDS_UV__Stream
} *UV__Stream;

static void destroy_stream(pTHX_ UV__Stream self)
{
    SvREFCNT_dec(self->on_read);
    SvREFCNT_dec(self->on_connection);
}

static void on_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
{
    UV__Stream self;
    SV         *cb;

    if(!stream || !stream->data) return;

    self = stream->data;
    if((cb = self->on_read) && SvOK(cb)) {
        dTHXa(self->perl);
        dSP;
        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        EXTEND(SP, 3);
        mPUSHs(newRV_inc(self->selfrv));
        mPUSHs(nread < 0 ? newSV_error(nread) : &PL_sv_undef);
        if(nread >= 0)
            mPUSHp(buf->base, nread);
        PUTBACK;

        call_sv(cb, G_DISCARD|G_VOID);

        FREETMPS;
        LEAVE;
    }

    if(buf && buf->base)
        Safefree(buf->base);
}

static void on_connection_cb(uv_stream_t *stream, int status)
{
    UV__Stream self;
    SV         *cb;

    if(!stream || !stream->data) return;

    self = stream->data;
    if(!(cb = self->on_connection) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    mPUSHs(newRV_inc(self->selfrv));
    mPUSHi(status);
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/*************
 * UV::Async *
 *************/

typedef struct UV__Async {
    uv_async_t *h;
    FIELDS_UV__Handle
    SV         *on_async;
} *UV__Async;

static void destroy_async(pTHX_ UV__Async self)
{
    SvREFCNT_dec(self->on_async);
}

static void on_async_cb(uv_async_t *async)
{
    UV__Async self;
    SV        *cb;

    if(!async || !async->data) return;

    self = async->data;
    if(!(cb = self->on_async) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newRV_inc(self->selfrv));
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/*************
 * UV::Check *
 *************/

/* See also http://docs.libuv.org/en/v1.x/check.html */

typedef struct UV__Check {
    uv_check_t *h;
    FIELDS_UV__Handle
    SV         *on_check;
} *UV__Check;

static void destroy_check(pTHX_ UV__Check self)
{
    SvREFCNT_dec(self->on_check);
}

static void on_check_cb(uv_check_t *check)
{
    UV__Check  self;
    SV        *cb;

    if(!check || !check->data) return;

    self = check->data;
    if(!(cb = self->on_check) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newRV_inc(self->selfrv));
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/************
 * UV::Idle *
 ************/

/* See also http://docs.libuv.org/en/v1.x/idle.html */

typedef struct UV__Idle {
    uv_idle_t *h;
    FIELDS_UV__Handle
    SV        *on_idle;
} *UV__Idle;

static void destroy_idle(pTHX_ UV__Idle self)
{
    SvREFCNT_dec(self->on_idle);
}

static void on_idle_cb(uv_idle_t *idle)
{
    UV__Idle self;
    SV       *cb;

    if(!idle || !idle->data) return;

    self = idle->data;
    if(!(cb = self->on_idle) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newRV_inc(self->selfrv));
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/************
 * UV::Pipe *
 ************/

/* See also http://docs.libuv.org/en/v1.x/pipe.html */

typedef struct UV__Pipe {
    uv_pipe_t *h;
    FIELDS_UV__Handle
    FIELDS_UV__Stream
} *UV__Pipe;

static void destroy_pipe(pTHX_ UV__Pipe self)
{
    destroy_stream(aTHX_ (UV__Stream)self);
}

/************
 * UV::Poll *
 ************/

/* See also http://docs.libuv.org/en/v1.x/poll.html */

typedef struct UV__Poll {
    uv_poll_t *h;
    FIELDS_UV__Handle
    SV        *on_poll;
} *UV__Poll;

static void destroy_poll(pTHX_ UV__Poll self)
{
    SvREFCNT_dec(self->on_poll);
}

static void on_poll_cb(uv_poll_t *poll, int status, int events)
{
    UV__Poll self;
    SV       *cb;

    if(!poll || !poll->data) return;

    self = poll->data;
    if(!(cb = self->on_poll) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 3);
    mPUSHs(newRV_inc(self->selfrv));
    mPUSHi(status);
    mPUSHi(events);
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/***************
 * UV::Prepare *
 ***************/

/* See also http://docs.libuv.org/en/v1.x/prepare.html */

typedef struct UV__Prepare {
    uv_prepare_t *h;
    FIELDS_UV__Handle
    SV           *on_prepare;
} *UV__Prepare;

static void destroy_prepare(pTHX_ UV__Prepare self)
{
    SvREFCNT_dec(self->on_prepare);
}

static void on_prepare_cb(uv_prepare_t *prepare)
{
    UV__Prepare  self;
    SV          *cb;

    if(!prepare || !prepare->data) return;

    self = prepare->data;
    if(!(cb = self->on_prepare) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newRV_inc(self->selfrv));
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/* See also http://docs.libuv.org/en/v1.x/process.html */

typedef struct UV__Process {
    uv_process_t *h;
    FIELDS_UV__Handle
    SV           *on_exit;

    /* fields for spawn */
    uv_loop_t *loop;
    uv_process_options_t options;
} *UV__Process;

static void on_exit_cb(uv_process_t *process, int64_t exit_status, int term_signal)
{
    UV__Process self;
    SV          *cb;

    if(!process || !process->data) return;

    self = process->data;
    if(!(cb = self->on_exit) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 3);
    mPUSHs(newRV_inc(self->selfrv));
    mPUSHi(exit_status);
    mPUSHi(term_signal);
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/**************
 * UV::Signal *
 **************/

/* See also http://docs.libuv.org/en/v1.x/signal.html */

typedef struct UV__Signal {
    uv_signal_t *h;
    FIELDS_UV__Handle
    int          signum;
    SV          *on_signal;
} *UV__Signal;

static void destroy_signal(pTHX_ UV__Signal self)
{
    SvREFCNT_dec(self->on_signal);
}

static void on_signal_cb(uv_signal_t *signal, int signum)
{
    UV__Signal self;
    SV         *cb;

    if(!signal || !signal->data) return;

    self = signal->data;
    if(!(cb = self->on_signal) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 2);
    mPUSHs(newRV_inc(self->selfrv));
    mPUSHi(signum);
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/*************
 * UV::Timer *
 *************/

/* See also http://docs.libuv.org/en/v1.x/timer.html */

typedef struct UV__Timer {
    uv_timer_t *h;
    FIELDS_UV__Handle
    SV         *on_timer;
} *UV__Timer;

static void destroy_timer(pTHX_ UV__Timer self)
{
    SvREFCNT_dec(self->on_timer);
}

static void on_timer_cb(uv_timer_t *timer)
{
    UV__Timer  self;
    SV        *cb;

    if(!timer || !timer->data) return;

    self = timer->data;
    if(!(cb = self->on_timer) || !SvOK(cb)) return;

    dTHXa(self->perl);
    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newRV_inc(self->selfrv));
    PUTBACK;

    call_sv(cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
}

/***********
 * UV::TCP *
 ***********/

/* See also http://docs.libuv.org/en/v1.x/tcp.html */

typedef struct UV__TCP {
    uv_tcp_t *h;
    FIELDS_UV__Handle;
    FIELDS_UV__Stream;
} *UV__TCP;

static void destroy_tcp(pTHX_ UV__TCP self)
{
    destroy_stream(aTHX_ (UV__Stream)self);
}

/***********
 * UV::TTY *
 ***********/

/* See also http://docs.libuv.org/en/v1.x/tty.html */

typedef struct UV__TTY {
    uv_tty_t *h;
    FIELDS_UV__Handle
    FIELDS_UV__Stream
} *UV__TTY;

static void destroy_tty(pTHX_ UV__TTY self)
{
    destroy_stream(aTHX_ (UV__Stream)self);
}

/***********
 * UV::UDP *
 ***********/

/* See also http://docs.libuv.org/en/v1.x/udp.html */

typedef struct UV__UDP {
    uv_udp_t *h;
    FIELDS_UV__Handle
    SV       *on_recv;
} *UV__UDP;

static void destroy_udp(pTHX_ UV__UDP self)
{
    SvREFCNT_dec(self->on_recv);
}

static void on_recv_cb(uv_udp_t *udp, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags)
{
    UV__UDP self;
    SV      *cb;

    if(!udp || !udp->data) return;

    self = udp->data;
    if((cb = self->on_recv) && SvOK(cb)) {
        size_t addrlen = 0;
        dTHXa(self->perl);
        dSP;

        /* libuv doesn't give us the length of addr; we'll have to guess */
        switch(((struct sockaddr_storage *)addr)->ss_family) {
            case AF_INET:  addrlen = sizeof(struct sockaddr_in);  break;
            case AF_INET6: addrlen = sizeof(struct sockaddr_in6); break;
        }

        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        EXTEND(SP, 5);
        mPUSHs(newRV_inc(self->selfrv));
        mPUSHs(nread < 0 ? newSV_error(nread) : &PL_sv_undef);
        if(nread >= 0)
            mPUSHp(buf->base, nread);
        else
            PUSHs(&PL_sv_undef);
        mPUSHp((char *)addr, addrlen);
        mPUSHi(flags);
        PUTBACK;

        call_sv(cb, G_DISCARD|G_VOID);

        FREETMPS;
        LEAVE;
    }

    if(buf && buf->base)
        Safefree(buf->base);
}

/* Handle destructor has to be able to see the type-specific destroy_
 * functions above, so must be last
 */

static void destroy_handle(UV__Handle self)
{
    dTHXa(self->perl);

    uv_handle_t *handle = self->h;
    switch(handle->type) {
        case UV_ASYNC:   destroy_async  (aTHX_ (UV__Async)  self); break;
        case UV_CHECK:   destroy_check  (aTHX_ (UV__Check)  self); break;
        case UV_IDLE:    destroy_idle   (aTHX_ (UV__Idle)   self); break;
        case UV_NAMED_PIPE:
                         destroy_pipe   (aTHX_ (UV__Pipe)   self); break;
        case UV_POLL:    destroy_poll   (aTHX_ (UV__Poll)   self); break;
        case UV_PREPARE: destroy_prepare(aTHX_ (UV__Prepare)self); break;
        case UV_SIGNAL:  destroy_signal (aTHX_ (UV__Signal) self); break;
        case UV_TCP:     destroy_tcp    (aTHX_ (UV__TCP)    self); break;
        case UV_TIMER:   destroy_timer  (aTHX_ (UV__Timer)  self); break;
        case UV_TTY:     destroy_tty    (aTHX_ (UV__TTY)    self); break;
        case UV_UDP:     destroy_udp    (aTHX_ (UV__UDP)    self); break;
    }

    destroy_handle_base(aTHX_ self);
}

/***********
 * UV::Req *
 ***********/

#define FIELDS_UV__Req \
    SV *selfrv;        \
    dTHXfield(perl)    \
    SV *cb;

typedef struct UV__Req {
    uv_req_t *r;
    FIELDS_UV__Req
} *UV__Req;

#define NEW_UV__Req(var, type) \
    Newxc(var, sizeof(*var) + sizeof(type), char, void); \
    var->r = (type *)((char *)var + sizeof(*var));

#define INIT_UV__Req(req)  { \
    req->r->data = req;      \
    storeTHX(req->perl);     \
}

static void on_req_cb(uv_req_t *_req, int status)
{
    UV__Req req = _req->data;
    dTHXa(req->perl);

    if(req->cb) {
        dSP;
        ENTER;
        SAVETMPS;

        PUSHMARK(SP);
        EXTEND(SP, 1);
        mPUSHs(newSV_error(status));
        PUTBACK;

        call_sv(req->cb, G_DISCARD|G_VOID);

        FREETMPS;
        LEAVE;
    }

    SvREFCNT_dec(req->selfrv);
}

/* Simple UV::Req subtypes that just invoke a callback with status */

typedef struct UV__Req_connect {
    uv_connect_t *r;
    FIELDS_UV__Req
} *UV__Req_connect;

typedef struct UV__Req_shutdown {
    uv_shutdown_t *r;
    FIELDS_UV__Req
} *UV__Req_shutdown;

typedef struct UV__Req_udp_send {
    uv_udp_send_t *r;
    FIELDS_UV__Req
    char       *s;
} *UV__Req_udp_send;

typedef struct UV__Req_write {
    uv_write_t *r;
    FIELDS_UV__Req
    char       *s;
} *UV__Req_write;

/* See also http://docs.libuv.org/en/v1.x/dns.html#c.uv_getaddrinfo */

typedef struct UV__Req_getaddrinfo {
    uv_getaddrinfo_t *r;
    FIELDS_UV__Req
} *UV__Req_getaddrinfo;

typedef struct UV__getaddrinfo_result {
    int              family;
    int              socktype;
    int              protocol;
    socklen_t        addrlen;
    struct sockaddr *addr;
    char            *canonname;
} *UV__getaddrinfo_result;

static void on_getaddrinfo_cb(uv_getaddrinfo_t *_req, int status, struct addrinfo *res)
{
    UV__Req_getaddrinfo req = _req->data;
    dTHXa(req->perl);

    struct addrinfo *addrp;

    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 1);
    mPUSHs(newSV_error(status));
    for(addrp = res; addrp; addrp = addrp->ai_next) {
        UV__getaddrinfo_result result;
        STRLEN canonnamelen = addrp->ai_canonname ? strlen(addrp->ai_canonname) + 1 : 0;
        Newxc(result, sizeof(*result) + addrp->ai_addrlen + canonnamelen, char, struct UV__getaddrinfo_result);

        result->family   = addrp->ai_family;
        result->socktype = addrp->ai_socktype;
        result->protocol = addrp->ai_protocol;
        result->addrlen  = addrp->ai_addrlen;
        result->addr     = (struct sockaddr *)((char *)result + sizeof(*result));
        Copy(addrp->ai_addr, result->addr, addrp->ai_addrlen, char);
        if(canonnamelen) {
            result->canonname = (char *)result->addr + addrp->ai_addrlen;
            Copy(addrp->ai_canonname, result->canonname, canonnamelen, char);
        }
        else {
            result->canonname = NULL;
        }

        EXTEND(SP, 1);
        PUSHmortal;
        sv_setref_pv(TOPs, "UV::getaddrinfo_result", result);
    }
    PUTBACK;

    call_sv(req->cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;

    uv_freeaddrinfo(res);
    SvREFCNT_dec(req->selfrv);
}

typedef struct UV__Req_getnameinfo {
    uv_getnameinfo_t *r;
    FIELDS_UV__Req
} *UV__Req_getnameinfo;

static void on_getnameinfo_cb(uv_getnameinfo_t *_req, int status, const char *hostname, const char *service)
{
    UV__Req_getnameinfo req = _req->data;
    dTHXa(req->perl);

    dSP;
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    EXTEND(SP, 3);
    mPUSHs(newSV_error(status));
    mPUSHp(hostname, strlen(hostname));
    mPUSHp(service, strlen(service));
    PUTBACK;

    call_sv(req->cb, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;

    SvREFCNT_dec(req->selfrv);
}

/************
 * UV::Loop *
 ************/

typedef struct UV__Loop {
    uv_loop_t *loop; /* may point to uv_default_loop() or past this struct */
} *UV__Loop;

MODULE = UV             PACKAGE = UV            PREFIX = uv_

BOOT:
{
    HV *stash;
    AV *export;
#define DO_CONST_IV(c) \
    newCONSTSUB_flags(stash, #c, strlen(#c), 0, newSViv(c)); \
    av_push(export, newSVpvs(#c));
#define DO_CONST_PV(c) \
    newCONSTSUB_flags(stash, #c, strlen(#c), 0, newSVpvn(c, strlen(c))); \
    av_push(export, newSVpvs(#c));

    /* constants under UV */
    {
        stash = gv_stashpv("UV", GV_ADD);
        export = get_av("UV::EXPORT_XS", TRUE);

        DO_CONST_IV(UV_VERSION_MAJOR);
        DO_CONST_IV(UV_VERSION_MINOR);
        DO_CONST_IV(UV_VERSION_PATCH);
        DO_CONST_IV(UV_VERSION_IS_RELEASE);
        DO_CONST_IV(UV_VERSION_HEX);
        DO_CONST_PV(UV_VERSION_SUFFIX);

        DO_CONST_IV(UV_E2BIG);
        DO_CONST_IV(UV_EACCES);
        DO_CONST_IV(UV_EADDRINUSE);
        DO_CONST_IV(UV_EADDRNOTAVAIL);
        DO_CONST_IV(UV_EAFNOSUPPORT);
        DO_CONST_IV(UV_EAGAIN);
        DO_CONST_IV(UV_EAI_ADDRFAMILY);
        DO_CONST_IV(UV_EAI_AGAIN);
        DO_CONST_IV(UV_EAI_BADFLAGS);
        DO_CONST_IV(UV_EAI_BADHINTS);
        DO_CONST_IV(UV_EAI_CANCELED);
        DO_CONST_IV(UV_EAI_FAIL);
        DO_CONST_IV(UV_EAI_FAMILY);
        DO_CONST_IV(UV_EAI_MEMORY);
        DO_CONST_IV(UV_EAI_NODATA);
        DO_CONST_IV(UV_EAI_NONAME);
        DO_CONST_IV(UV_EAI_OVERFLOW);
        DO_CONST_IV(UV_EAI_PROTOCOL);
        DO_CONST_IV(UV_EAI_SERVICE);
        DO_CONST_IV(UV_EAI_SOCKTYPE);
        DO_CONST_IV(UV_EALREADY);
        DO_CONST_IV(UV_EBADF);
        DO_CONST_IV(UV_EBUSY);
        DO_CONST_IV(UV_ECANCELED);
        DO_CONST_IV(UV_ECHARSET);
        DO_CONST_IV(UV_ECONNABORTED);
        DO_CONST_IV(UV_ECONNREFUSED);
        DO_CONST_IV(UV_ECONNRESET);
        DO_CONST_IV(UV_EDESTADDRREQ);
        DO_CONST_IV(UV_EEXIST);
        DO_CONST_IV(UV_EFAULT);
        DO_CONST_IV(UV_EFBIG);
        DO_CONST_IV(UV_EHOSTUNREACH);
        DO_CONST_IV(UV_EINTR);
        DO_CONST_IV(UV_EINVAL);
        DO_CONST_IV(UV_EIO);
        DO_CONST_IV(UV_EISCONN);
        DO_CONST_IV(UV_EISDIR);
        DO_CONST_IV(UV_ELOOP);
        DO_CONST_IV(UV_EMFILE);
        DO_CONST_IV(UV_EMSGSIZE);
        DO_CONST_IV(UV_ENAMETOOLONG);
        DO_CONST_IV(UV_ENETDOWN);
        DO_CONST_IV(UV_ENETUNREACH);
        DO_CONST_IV(UV_ENFILE);
        DO_CONST_IV(UV_ENOBUFS);
        DO_CONST_IV(UV_ENODEV);
        DO_CONST_IV(UV_ENOENT);
        DO_CONST_IV(UV_ENOMEM);
        DO_CONST_IV(UV_ENONET);
        DO_CONST_IV(UV_ENOPROTOOPT);
        DO_CONST_IV(UV_ENOSPC);
        DO_CONST_IV(UV_ENOSYS);
        DO_CONST_IV(UV_ENOTCONN);
        DO_CONST_IV(UV_ENOTDIR);
        DO_CONST_IV(UV_ENOTEMPTY);
        DO_CONST_IV(UV_ENOTSOCK);
        DO_CONST_IV(UV_ENOTSUP);
        DO_CONST_IV(UV_EPERM);
        DO_CONST_IV(UV_EPIPE);
        DO_CONST_IV(UV_EPROTO);
        DO_CONST_IV(UV_EPROTONOSUPPORT);
        DO_CONST_IV(UV_EPROTOTYPE);
        DO_CONST_IV(UV_ERANGE);
        DO_CONST_IV(UV_EROFS);
        DO_CONST_IV(UV_ESHUTDOWN);
        DO_CONST_IV(UV_ESPIPE);
        DO_CONST_IV(UV_ESRCH);
        DO_CONST_IV(UV_ETIMEDOUT);
        DO_CONST_IV(UV_ETXTBSY);
        DO_CONST_IV(UV_EXDEV);
        DO_CONST_IV(UV_UNKNOWN);
        DO_CONST_IV(UV_EOF);
        DO_CONST_IV(UV_ENXIO);
        DO_CONST_IV(UV_EMLINK);
    }

    /* constants under UV::Handle */
    {
        stash = gv_stashpv("UV::Handle", GV_ADD);
        export = get_av("UV::Handle::EXPORT_XS", TRUE);

        DO_CONST_IV(UV_ASYNC);
        DO_CONST_IV(UV_CHECK);
        DO_CONST_IV(UV_FS_EVENT);
        DO_CONST_IV(UV_FS_POLL);
        DO_CONST_IV(UV_IDLE);
        DO_CONST_IV(UV_NAMED_PIPE);
        DO_CONST_IV(UV_POLL);
        DO_CONST_IV(UV_PREPARE);
        DO_CONST_IV(UV_PROCESS);
        DO_CONST_IV(UV_STREAM);
        DO_CONST_IV(UV_TCP);
        DO_CONST_IV(UV_TIMER);
        DO_CONST_IV(UV_TTY);
        DO_CONST_IV(UV_UDP);
        DO_CONST_IV(UV_SIGNAL);
        DO_CONST_IV(UV_FILE);
    }

    /* constants under UV::Loop */
    {
        stash = gv_stashpv("UV::Loop", GV_ADD);
        export = get_av("UV::Loop::EXPORT_XS", TRUE);

        /* Loop run constants */
        DO_CONST_IV(UV_RUN_DEFAULT);
        DO_CONST_IV(UV_RUN_ONCE);
        DO_CONST_IV(UV_RUN_NOWAIT);

        /* expose the Loop configure constants */
        DO_CONST_IV(UV_LOOP_BLOCK_SIGNAL);
        DO_CONST_IV(SIGPROF);
    }

    /* constants under UV::Poll */
    {
        stash = gv_stashpv("UV::Poll", GV_ADD);
        export = get_av("UV::Poll::EXPORT_XS", TRUE);

        /* Poll Event Types */
        DO_CONST_IV(UV_READABLE);
        DO_CONST_IV(UV_WRITABLE);
        DO_CONST_IV(UV_DISCONNECT);
        DO_CONST_IV(UV_PRIORITIZED);
    }

    /* constants under UV::Signal */
    {
        stash = gv_stashpv("UV::Signal", GV_ADD);
        export = get_av("UV::Signal::EXPORT_XS", TRUE);

        /* Signal numbers - exported again because at least on MSWin32 several
         * of these are emulated, and the values are not known to the rest of
         * the system, including POSIX.xs
         */
        DO_CONST_IV(SIGINT);
        DO_CONST_IV(SIGILL);
        DO_CONST_IV(SIGABRT);
        DO_CONST_IV(SIGFPE);
        DO_CONST_IV(SIGSEGV);
        DO_CONST_IV(SIGTERM);
#ifdef SIGBREAK
        DO_CONST_IV(SIGBREAK);
#endif
        DO_CONST_IV(SIGHUP);
        DO_CONST_IV(SIGKILL);
#ifdef SIGWINCH
        DO_CONST_IV(SIGWINCH);
#endif
}

    /* constants under UV::TTY */
    {
        stash = gv_stashpv("UV::TTY", GV_ADD);
        export = get_av("UV::TTY::EXPORT_XS", TRUE);

        /* TTY mode types */
        DO_CONST_IV(UV_TTY_MODE_NORMAL);
        DO_CONST_IV(UV_TTY_MODE_RAW);
        DO_CONST_IV(UV_TTY_MODE_IO);
    }

    /* constants under UV::UDP */
    {
        stash = gv_stashpv("UV::UDP", GV_ADD);
        export = get_av("UV::UDP::EXPORT_XS", TRUE);

        /* TTY mode types */
        DO_CONST_IV(UV_JOIN_GROUP);
        DO_CONST_IV(UV_LEAVE_GROUP);
    }
}

const char* uv_err_name(int err)

#if UVSIZE >= 8

UV uv_hrtime()
    CODE:
        RETVAL = uv_hrtime();
    OUTPUT:
        RETVAL

#else

NV uv_hrtime()
    CODE:
        RETVAL = (NV)uv_hrtime();
    OUTPUT:
        RETVAL

#endif

const char* uv_strerror(int err)

unsigned int uv_version()

const char* uv_version_string()

MODULE = UV             PACKAGE = UV::Exception

SV *
message(SV *self)
    CODE:
        RETVAL = newSV(0);
        sv_copypv(RETVAL, SvRV(self));
    OUTPUT:
        RETVAL

int
code(SV *self)
    CODE:
        RETVAL = SvIV(SvRV(self));
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Handle

void
DESTROY(UV::Handle self)
    CODE:
        /* TODO:
            $self->stop() if ($self->can('stop') && !$self->closing() && !$self->closed());
         */
        if(!uv_is_closing(self->h))
            uv_close(self->h, on_close_cb);
        self->destroy_after_close = TRUE;

bool
closed(UV::Handle self)
    CODE:
        RETVAL = 0;
    OUTPUT:
        RETVAL

bool
closing(UV::Handle self)
    CODE:
        RETVAL = uv_is_closing(self->h);
    OUTPUT:
        RETVAL

bool
active(UV::Handle self)
    CODE:
        RETVAL = uv_is_active(self->h);
    OUTPUT:
        RETVAL

SV *
loop(UV::Handle self)
    INIT:
        UV__Loop loop;
    CODE:
        Newx(loop, 1, struct UV__Loop);
        loop->loop = self->h->loop;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Loop", loop);
    OUTPUT:
        RETVAL

SV *
data(UV::Handle self, SV *data = NULL)
    CODE:
        if(items > 1) {
            SvREFCNT_dec(self->data);
            self->data = newSVsv(data);
        }
        RETVAL = self->data ? newSVsv(self->data) : &PL_sv_undef;
    OUTPUT:
        RETVAL

void
_close(UV::Handle self)
    CODE:
        uv_close(self->h, on_close_cb);

SV *
_on_close(UV::Handle self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_close, cb);
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Async

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Async self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_async_t);

        err = uv_async_init(loop->loop, self->h, &on_async_cb);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise async handle", err);
        }

        INIT_UV__Handle(self);
        self->on_async = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Async", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_async(UV::Async self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_async, cb);
    OUTPUT:
        RETVAL

void
send(UV::Async self)
    CODE:
        CHECKCALL(uv_async_send(self->h));

MODULE = UV             PACKAGE = UV::Check

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Check self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_check_t);

        err = uv_check_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise check handle", err);
        }

        INIT_UV__Handle(self);
        self->on_check = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Check", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_check(UV::Check self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_check, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Check self)
    CODE:
        CHECKCALL(uv_check_start(self->h, on_check_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
stop(UV::Check self)
    CODE:
        CHECKCALL(uv_check_stop(self->h));

MODULE = UV             PACKAGE = UV::Idle

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Idle self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_idle_t);

        err = uv_idle_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise idle handle", err);
        }

        INIT_UV__Handle(self);
        self->on_idle = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Idle", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_idle(UV::Idle self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_idle, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Idle self)
    CODE:
        CHECKCALL(uv_idle_start(self->h, on_idle_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
stop(UV::Idle self)
    CODE:
        CHECKCALL(uv_idle_stop(self->h));

MODULE = UV             PACKAGE = UV::Pipe

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Pipe self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_pipe_t);

        err = uv_pipe_init(loop->loop, self->h, 0);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialse pipe handle", err);
        }

        INIT_UV__Handle(self);
        INIT_UV__Stream(self);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Pipe", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

void
_open(UV::Pipe self, int fd)
    CODE:
        CHECKCALL(uv_pipe_open(self->h, fd));

void
bind(UV::Pipe self, char *name)
    CODE:
        CHECKCALL(uv_pipe_bind(self->h, name));

SV *
connect(UV::Pipe self, char *path, SV *cb)
    INIT:
        UV__Req_connect req;
    CODE:
        NEW_UV__Req(req, uv_connect_t);
        INIT_UV__Req(req);

        uv_pipe_connect(req->r, self->h, path, (uv_connect_cb)on_req_cb);

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

SV *
getpeername(UV::Pipe self)
    ALIAS:
        getpeername = 0
        getsockname = 1
    INIT:
        size_t len;
        int err;
    CODE:
        RETVAL = newSV(256);
        len = SvLEN(RETVAL);

        err = (ix == 0) ?
            uv_pipe_getpeername(self->h, SvPVX(RETVAL), &len) :
            uv_pipe_getsockname(self->h, SvPVX(RETVAL), &len);
        if(err != 0) {
            SvREFCNT_dec(RETVAL);
            croak("Couldn't %s from pipe handle (%d): %s", (ix == 0) ? "getpeername" : "getsockname",
                err, uv_strerror(err));
        }

        SvCUR_set(RETVAL, len);
        SvPOK_on(RETVAL);
    OUTPUT:
        RETVAL

void
chmod(UV::Pipe self, int flags)
    CODE:
        CHECKCALL(uv_pipe_chmod(self->h, flags));

MODULE = UV             PACKAGE = UV::Poll

SV *
_new(char *class, UV::Loop loop, int fd, bool is_socket)
    INIT:
        UV__Poll self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_poll_t);

        if(is_socket) {
            err = uv_poll_init_socket(loop->loop, self->h, _MAKE_SOCK(fd));
            if (err != 0) {
                Safefree(self);
                THROWERR("Couldn't initialise poll handle for socket", err);
            }
        }
        else {
            err = uv_poll_init(loop->loop, self->h, fd);
            if (err != 0) {
                Safefree(self);
                THROWERR("Couldn't initialise poll handle for non-socket", err);
            }
        }

        INIT_UV__Handle(self);
        self->on_poll = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Poll", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_poll(UV::Poll self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_poll, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Poll self, int events = UV_READABLE)
    CODE:
        CHECKCALL(uv_poll_start(self->h, events, on_poll_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
stop(UV::Poll self)
    CODE:
        CHECKCALL(uv_poll_stop(self->h));

MODULE = UV             PACKAGE = UV::Prepare

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Prepare self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_prepare_t);

        err = uv_prepare_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise prepare handle", err);
        }

        INIT_UV__Handle(self);
        self->on_prepare = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Prepare", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_prepare(UV::Prepare self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_prepare, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Prepare self)
    CODE:
        CHECKCALL(uv_prepare_start(self->h, on_prepare_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
stop(UV::Prepare self)
    CODE:
        CHECKCALL(uv_prepare_stop(self->h));

MODULE = UV             PACKAGE = UV::Process

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Process self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_process_t);
        self->loop = loop->loop;

        INIT_UV__Handle(self);
        self->on_exit = NULL;

        Zero(&self->options, 1, uv_process_options_t);

        self->options.exit_cb = &on_exit_cb;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Process", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_exit(UV::Process self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_exit, cb);
    OUTPUT:
        RETVAL

void
_set_file(UV::Process self, char *file)
    CODE:
        self->options.file = savepv(file);

void
_set_args(UV::Process self, SV *args)
    INIT:
        AV *argsav;
        U32 i;
    CODE:
        if(!SvROK(args) || SvTYPE(SvRV(args)) != SVt_PVAV)
            croak("Expected args as ARRAY reference");

        argsav = (AV *)SvRV(args);

        Newx(self->options.args, AvFILL(argsav) + 3, char *);
        self->options.args[0] = NULL;
        for(i = 0; i <= AvFILL(argsav); i++)
            self->options.args[i+1] = savepv(SvPVbyte_nolen(AvARRAY(argsav)[i]));
        self->options.args[i+1] = NULL;

void
_set_env(UV::Process self, SV *env)
    INIT:
        HV *envhv;
        I32 nkeys, i, dummy;
        HE *iter;
        SV *tmp;
    CODE:
        if(!SvROK(env) || SvTYPE(SvRV(env)) != SVt_PVHV)
            croak("Expected env as HASH reference");

        envhv = (HV *)SvRV(env);
        nkeys = hv_iterinit(envhv);

        Newx(self->options.env, nkeys + 1, char *);
        tmp = sv_newmortal();

        i = 0;
        while((iter = hv_iternext(envhv))) {
            sv_setpvf(tmp, "%s=%s",
                hv_iterkey(iter, &dummy), SvPVbyte_nolen(HeVAL(iter)));

            self->options.env[i++] = SvPVX(tmp);
            SvPVX(tmp) = NULL;
            SvLEN(tmp) = 0;
        }
        self->options.env[i] = NULL;

void
_set_stdio_h(UV::Process self, int fd, SV *arg)
    INIT:
        uv_stdio_container_t *cont;
        int flags = 0;
        SV *fdarg = arg;
    CODE:
        if(self->options.stdio_count < (fd+1)) {
            int n = self->options.stdio_count;
            if(n < (fd+1)) n = (fd+1);
            if(n < 3)      n = 3;

            Renew(self->options.stdio, n, uv_stdio_container_t);
            int i;
            for(i = self->options.stdio_count; i < n; i++)
                self->options.stdio[i].flags = UV_IGNORE;

            self->options.stdio_count = n;
        }

        cont = self->options.stdio + fd;

        if(SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVHV) {
            fprintf(stderr, "TODO: grab extra values from hash\n");
        }

        if(!SvROK(fdarg)) {
            /* FD by stream number */
            cont->data.fd = SvIV(arg);
            flags |= UV_INHERIT_FD;
        }
        else if(SvTYPE(SvRV(fdarg)) == SVt_PVGV) {
            /* FD by globref */
            cont->data.fd = PerlIO_fileno(IoOFP(GvIO(SvRV(fdarg))));
            flags |= UV_INHERIT_FD;
        }
        else {
            croak("Unsure what to do with _set_stdio_h fd argument %" SVf, SVfARG(arg));
        }

        cont->flags = flags;

void
_set_setuid(UV::Process self, int uid)
    CODE:
        self->options.flags |= UV_PROCESS_SETUID;
        self->options.uid = uid;

void
_set_setgid(UV::Process self, int gid)
    CODE:
        self->options.flags |= UV_PROCESS_SETGID;
        self->options.uid = gid;


void
_spawn(UV::Process self)
    INIT:
        int err;
    CODE:
        if(!self->options.file)
            croak("Require 'file' to spawn a UV::Process");
        if(!self->options.args)
            croak("Require 'args' to spawn a UV::Process");

        if(!self->options.args[0])
            self->options.args[0] = savepv(self->options.file);

        err = uv_spawn(self->loop, self->h, &self->options);
        if (err != 0) {
            THROWERR("Couldn't spawn process", err);
        }

void
kill(UV::Process self, int signum)
    CODE:
        CHECKCALL(uv_process_kill(self->h, signum));

int
pid(UV::Process self)
    CODE:
        RETVAL = self->h->pid;
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Signal

SV *
_new(char *class, UV::Loop loop, int signum)
    INIT:
        UV__Signal self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_signal_t);

        err = uv_signal_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise signal handle", err);
        }

        INIT_UV__Handle(self);
        self->signum = signum; /* need to remember this until start() time */
        self->on_signal = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Signal", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_signal(UV::Signal self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_signal, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Signal self)
    CODE:
        CHECKCALL(uv_signal_start(self->h, on_signal_cb, self->signum));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
stop(UV::Signal self)
    CODE:
        CHECKCALL(uv_signal_stop(self->h));

MODULE = UV             PACKAGE = UV::Stream

SV *
_on_read(UV::Stream self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_read, cb);
    OUTPUT:
        RETVAL

SV *
_on_connection(UV::Stream self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_connection, cb);
    OUTPUT:
        RETVAL

void
_listen(UV::Stream self, int backlog)
    CODE:
        CHECKCALL(uv_listen(self->h, backlog, on_connection_cb));

void
_accept(UV::Stream self, UV::Stream client)
    CODE:
        CHECKCALL(uv_accept(self->h, client->h));

SV *
shutdown(UV::Stream self, SV *cb)
    INIT:
        UV__Req_shutdown req;
        int err;
    CODE:
        NEW_UV__Req(req, uv_shutdown_t);
        INIT_UV__Req(req);

        err = uv_shutdown(req->r, self->h, (uv_shutdown_cb)on_req_cb);

        if(err != 0) {
            Safefree(req);
            THROWERR("Couldn't shutdown", err);
        }

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

SV *
read_start(UV::Stream self)
    CODE:
        CHECKCALL(uv_read_start(self->h, on_alloc_cb, on_read_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
read_stop(UV::Stream self)
    CODE:
        CHECKCALL(uv_read_stop(self->h));

SV *
write(UV::Stream self, SV *s, SV *cb)
    INIT:
        UV__Req_write req;
        uv_buf_t buf[1];
        int err;
    CODE:
        NEW_UV__Req(req, uv_write_t);
        INIT_UV__Req(req);

        buf[0].len  = SvCUR(s);
        buf[0].base = savepvn(SvPVX(s), buf[0].len);

        req->s = buf[0].base;

        err = uv_write(req->r, self->h, buf, 1, (uv_write_cb)on_req_cb);

        if(err != 0) {
            Safefree(req->s);
            Safefree(req);
            THROWERR("Couldn't write", err);
        }

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Timer

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__Timer self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_timer_t);

        err = uv_timer_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise timer handle", err);
        }

        INIT_UV__Handle(self);
        self->on_timer = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Timer", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_timer(UV::Timer self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_timer, cb);
    OUTPUT:
        RETVAL

SV *
_start(UV::Timer self, UV timeout, UV repeat)
    CODE:
        CHECKCALL(uv_timer_start(self->h, on_timer_cb, timeout, repeat));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

UV
_get_repeat(UV::Timer self)
    CODE:
        RETVAL = uv_timer_get_repeat(self->h);
    OUTPUT:
        RETVAL

void
_set_repeat(UV::Timer self, UV repeat)
    CODE:
        uv_timer_set_repeat(self->h, repeat);

void
again(UV::Timer self)
    CODE:
        CHECKCALL(uv_timer_again(self->h));

void
stop(UV::Timer self)
    CODE:
        CHECKCALL(uv_timer_stop(self->h));

MODULE = UV             PACKAGE = UV::TCP

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__TCP self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_tcp_t);

        err = uv_tcp_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise tcp handle", err);
        }

        INIT_UV__Handle(self);
        INIT_UV__Stream(self);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::TCP", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

void
_open(UV::TCP self, int fd)
    CODE:
#ifdef HAVE_MSWIN32
        /* Not supported currently, because libuv would want overlapped IO on
         * sockets and perl does not create those. See also
         *   https://github.com/p5-UV/p5-UV/issues/38
         */
        croak("UV::TCP->open is not currently supported on Windows");
#endif
        CHECKCALL(uv_tcp_open(self->h, _MAKE_SOCK(fd)));

void
nodelay(UV::TCP self, bool enable)
    CODE:
        CHECKCALL(uv_tcp_nodelay(self->h, enable));

void
keepalive(UV::TCP self, bool enable, unsigned int delay = 0)
    CODE:
        if(enable && items < 3)
            croak_xs_usage(cv, "self, enable=true, delay");

        CHECKCALL(uv_tcp_keepalive(self->h, enable, delay));

void
simultaneous_accepts(UV::TCP self, bool enable)
    CODE:
        CHECKCALL(uv_tcp_simultaneous_accepts(self->h, enable));

void
bind(UV::TCP self, SV *addr, int flags = 0)
    CODE:
        if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
            croak("Expected a packed socket address for addr");

        CHECKCALL(uv_tcp_bind(self->h, (struct sockaddr *)SvPVX(addr), flags));

SV *
connect(UV::TCP self, SV *addr, SV *cb)
    INIT:
        UV__Req_connect req;
    CODE:
        NEW_UV__Req(req, uv_connect_t);
        INIT_UV__Req(req);

        if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
            croak("Expected a packed socket address for addr");

        uv_tcp_connect(req->r, self->h, (struct sockaddr *)SvPVX(addr), (uv_connect_cb)on_req_cb);

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

SV *
getpeername(UV::TCP self)
    ALIAS:
        getpeername = 0
        getsockname = 1
    INIT:
        int len;
        int err;
    CODE:
        len = sizeof(struct sockaddr_storage);
        RETVAL = newSV(len);

        err = (ix == 0) ?
            uv_tcp_getpeername(self->h, (struct sockaddr *)SvPVX(RETVAL), &len) :
            uv_tcp_getsockname(self->h, (struct sockaddr *)SvPVX(RETVAL), &len);
        if(err != 0) {
            SvREFCNT_dec(RETVAL);
            croak("Couldn't %s from tcp handle (%d): %s", (ix == 0) ? "getpeername" : "getsockname",
                err, uv_strerror(err));
        }

        SvCUR_set(RETVAL, len);
        SvPOK_on(RETVAL);
    OUTPUT:
        RETVAL

void
_close_reset(UV::TCP self)
    CODE:
        CHECKCALL(uv_tcp_close_reset(self->h, on_close_cb));

MODULE = UV             PACKAGE = UV::TTY

SV *
_new(char *class, UV::Loop loop, int fd)
    INIT:
        UV__TTY self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_tty_t);

        err = uv_tty_init(loop->loop, self->h, fd, 0);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialise tty handle", err);
        }

        INIT_UV__Handle(self);
        INIT_UV__Stream(self);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::TTY", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

void
set_mode(UV::TTY self, int mode)
    CODE:
        CHECKCALL(uv_tty_set_mode(self->h, mode));

void
get_winsize(UV::TTY self)
    INIT:
        int width, height;
    PPCODE:
        CHECKCALL(uv_tty_get_winsize(self->h, &width, &height));
        EXTEND(SP, 2);
        mPUSHi(width);
        mPUSHi(height);
        XSRETURN(2);

MODULE = UV             PACKAGE = UV::UDP

SV *
_new(char *class, UV::Loop loop)
    INIT:
        UV__UDP self;
        int err;
    CODE:
        NEW_UV__Handle(self, uv_udp_t);

        err = uv_udp_init(loop->loop, self->h);
        if (err != 0) {
            Safefree(self);
            THROWERR("Couldn't initialse udp handle", err);
        }

        INIT_UV__Handle(self);
        self->on_recv = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::UDP", self);
        self->selfrv = SvRV(RETVAL); /* no inc */
    OUTPUT:
        RETVAL

SV *
_on_recv(UV::UDP self, SV *cb = NULL)
    CODE:
        RETVAL = do_callback_accessor(&self->on_recv, cb);
    OUTPUT:
        RETVAL

void
_open(UV::UDP self, int fd)
    CODE:
#ifdef HAVE_MSWIN32
        /* Not supported currently, because libuv would want overlapped IO on
         * sockets and perl does not create those. See also
         *   https://github.com/p5-UV/p5-UV/issues/38
         */
        croak("UV::UDP->open is not currently supported on Windows");
#endif
        CHECKCALL(uv_udp_open(self->h, _MAKE_SOCK(fd)));

void
bind(UV::UDP self, SV *addr, int flags = 0)
    CODE:
        if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
            croak("Expected a packed socket address for addr");

        CHECKCALL(uv_udp_bind(self->h, (struct sockaddr *)SvPVX(addr), flags));

SV *
connect(UV::UDP self, SV *addr)
    CODE:
        if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
            croak("Expected a packed socket address for addr");

        CHECKCALL(uv_udp_connect(self->h, (struct sockaddr *)SvPVX(addr)));

SV *
getpeername(UV::UDP self)
    ALIAS:
        getpeername = 0
        getsockname = 1
    INIT:
        int len;
        int err;
    CODE:
        len = sizeof(struct sockaddr_storage);
        RETVAL = newSV(len);

        err = (ix == 0) ?
            uv_udp_getpeername(self->h, (struct sockaddr *)SvPVX(RETVAL), &len) :
            uv_udp_getsockname(self->h, (struct sockaddr *)SvPVX(RETVAL), &len);
        if(err != 0) {
            SvREFCNT_dec(RETVAL);
            croak("Couldn't %s from udp handle (%d): %s", (ix == 0) ? "getpeername" : "getsockname",
                err, uv_strerror(err));
        }

        SvCUR_set(RETVAL, len);
        SvPOK_on(RETVAL);
    OUTPUT:
        RETVAL

SV *
recv_start(UV::UDP self)
    CODE:
        CHECKCALL(uv_udp_recv_start(self->h, on_alloc_cb, on_recv_cb));
        RETVAL = SvREFCNT_inc(ST(0));
    OUTPUT:
        RETVAL

void
recv_stop(UV::UDP self)
    CODE:
        CHECKCALL(uv_udp_recv_stop(self->h));

SV *
send(UV::UDP self, SV *s, ...)
    INIT:
        UV__Req_udp_send req;
        uv_buf_t buf[1];
        int err;
        SV *addr;
        struct sockaddr *sockaddr = NULL;
        SV *cb;
    CODE:
        if(items > 4)
            croak_xs_usage(cv, "self, s, [from], cb");
        else if(items == 4) {
            addr = ST(2);
            cb   = ST(3);
        }
        else if(SvTYPE(SvRV(ST(2))) == SVt_PVCV) {
            addr = NULL;
            cb   = ST(2);
        }
        else {
            addr = ST(2);
            cb   = NULL;
        }

        if(addr) {
            if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
                croak("Expected a packed socket address for addr");
            sockaddr = (struct sockaddr *)SvPVX(addr);
        }

        NEW_UV__Req(req, uv_udp_send_t);
        INIT_UV__Req(req);

        buf[0].len  = SvCUR(s);
        buf[0].base = savepvn(SvPVX(s), buf[0].len);

        req->s = buf[0].base;

        err = uv_udp_send(req->r, self->h, buf, 1, sockaddr,
            (uv_udp_send_cb)on_req_cb);

        if(err != 0) {
            Safefree(req->s);
            Safefree(req);
            THROWERR("Couldn't send", err);
        }

        if(cb)
            req->cb = newSVsv(cb);
        else
            req->cb = NULL;

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

void
set_broadcast(UV::UDP self, bool on)
    CODE:
        CHECKCALL(uv_udp_set_broadcast(self->h, on));

void
set_ttl(UV::UDP self, int ttl)
    CODE:
        CHECKCALL(uv_udp_set_ttl(self->h, ttl));

void
set_multicast_loop(UV::UDP self, bool on)
    CODE:
        CHECKCALL(uv_udp_set_multicast_loop(self->h, on));

void
set_multicast_ttl(UV::UDP self, int ttl)
    CODE:
        CHECKCALL(uv_udp_set_multicast_ttl(self->h, ttl));

void
set_multicast_interface(UV::UDP self, SV *ifaddr)
    CODE:
        CHECKCALL(uv_udp_set_multicast_interface(self->h, SvPVbyte_nolen(ifaddr)));

void
set_membership(UV::UDP self, SV *mcaddr, SV *ifaddr, int membership)
    CODE:
        CHECKCALL(uv_udp_set_membership(
            self->h, SvPVbyte_nolen(mcaddr), SvPVbyte_nolen(ifaddr), membership));

void
set_source_membership(UV::UDP self, SV *mcaddr, SV *ifaddr, SV *srcaddr, int membership)
    CODE:
        CHECKCALL(uv_udp_set_source_membership(
            self->h, SvPVbyte_nolen(mcaddr), SvPVbyte_nolen(ifaddr), SvPVbyte_nolen(srcaddr), membership));

void
try_send(UV::UDP self, SV *s, ...)
    INIT:
        uv_buf_t buf[1];
        int err;
        SV *addr;
        struct sockaddr *sockaddr = NULL;
    CODE:
        if(items > 3)
            croak_xs_usage(cv, "self, s, [from]");
        else if(items == 3) {
            addr = ST(2);
        }
        else {
            addr = NULL;
        }

        if(addr) {
            if(!SvPOK(addr) || SvCUR(addr) < sizeof(struct sockaddr))
                croak("Expected a packed socket address for addr");
            sockaddr = (struct sockaddr *)SvPVX(addr);
        }

        buf[0].len  = SvCUR(s);
        buf[0].base = savepvn(SvPVX(s), buf[0].len);

        err = uv_udp_try_send(self->h, buf, 1, sockaddr);

        if(err < 0) {
            THROWERR("Couldn't send", err);
        }

UV
get_send_queue_size(UV::UDP self)
    ALIAS:
        get_send_queue_size  = 0
        get_send_queue_count = 1
    CODE:
        switch(ix) {
            case 0: RETVAL = uv_udp_get_send_queue_size(self->h);  break;
            case 1: RETVAL = uv_udp_get_send_queue_count(self->h); break;
        }
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Loop

SV *
_new(char *class, int want_default)
    INIT:
        UV__Loop self;
        int err;
    CODE:
        Newxc(self, sizeof(struct UV__Loop) + (!want_default * sizeof(uv_loop_t)),
            char, struct UV__Loop);

        if(want_default) {
            self->loop = uv_default_loop();
        }
        else {
            self->loop = (uv_loop_t *)((char *)self + sizeof(struct UV__Loop));
            err = uv_loop_init(self->loop);
            if(err != 0) {
                Safefree(self);
                THROWERR("Couldn't initialise loop", err);
            }
        }

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Loop", self);
    OUTPUT:
        RETVAL

bool
alive(UV::Loop self)
    CODE:
        RETVAL = uv_loop_alive(self->loop);
    OUTPUT:
        RETVAL

int
backend_fd(UV::Loop self)
    CODE:
        RETVAL = uv_backend_fd(self->loop);
    OUTPUT:
        RETVAL

int
backend_timeout(UV::Loop self)
    CODE:
        RETVAL = uv_backend_timeout(self->loop);
    OUTPUT:
        RETVAL

void
DESTROY(UV::Loop self)
    CODE:
        /* Don't allow closing the default loop */
        if(self->loop != uv_default_loop())
            uv_loop_close(self->loop);

void
configure(UV::Loop self, int option, int value)
    CODE:
        CHECKCALL(uv_loop_configure(self->loop, option, value));

bool
is_default(UV::Loop self)
    CODE:
        RETVAL = (self->loop == uv_default_loop());
    OUTPUT:
        RETVAL

UV
now(UV::Loop self)
    CODE:
        RETVAL = uv_now(self->loop);
    OUTPUT:
        RETVAL

int
run(UV::Loop self, int mode = UV_RUN_DEFAULT)
    CODE:
        RETVAL = uv_run(self->loop, mode);
    OUTPUT:
        RETVAL

void
stop(UV::Loop self)
    CODE:
        uv_stop(self->loop);

void
update_time(UV::Loop self)
    CODE:
        uv_update_time(self->loop);

SV *
_getaddrinfo(UV::Loop self, char *node, char *service, SV *flags, SV *family, SV *socktype, SV *protocol, SV *cb)
    INIT:
        UV__Req_getaddrinfo req;
        struct addrinfo hints = { 0 };
        int err;
    CODE:
        NEW_UV__Req(req, uv_getaddrinfo_t);
        INIT_UV__Req(req);

        hints.ai_flags    = SvOK(flags)    ? SvIV(flags)    : DEFAULT_AI_FLAGS;
        hints.ai_family   = SvOK(family)   ? SvIV(family)   : AF_UNSPEC;
        hints.ai_socktype = SvOK(socktype) ? SvIV(socktype) : 0;
        hints.ai_protocol = SvOK(protocol) ? SvIV(protocol) : 0;

        err = uv_getaddrinfo(self->loop, req->r, on_getaddrinfo_cb,
            node, service, &hints);
        if (err != 0) {
            Safefree(req);
            THROWERR("Couldn't getaddrinfo", err);
        }

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

SV *
getnameinfo(UV::Loop self, SV *addr, int flags, SV *cb)
    INIT:
        UV__Req_getnameinfo req;
        int err;
    CODE:
        NEW_UV__Req(req, uv_getnameinfo_t);
        INIT_UV__Req(req);

        err = uv_getnameinfo(self->loop, req->r, on_getnameinfo_cb,
            (struct sockaddr *)SvPV_nolen(addr), flags);
        if (err != 0) {
            Safefree(req);
            THROWERR("Couldn't getnameinfo", err);
        }

        req->cb = newSVsv(cb);

        RETVAL = newSV(0);
        sv_setref_pv(RETVAL, "UV::Req", req);
        req->selfrv = SvREFCNT_inc(SvRV(RETVAL));
    OUTPUT:
        RETVAL

MODULE = UV             PACKAGE = UV::Req

void
DESTROY(UV::Req req)
    CODE:
        switch(req->r->type) {
            case UV_CONNECT:
                SvREFCNT_dec(((UV__Req_connect)req)->cb);
                break;

            case UV_GETADDRINFO:
                SvREFCNT_dec(((UV__Req_getaddrinfo)req)->cb);
                break;

            case UV_GETNAMEINFO:
                SvREFCNT_dec(((UV__Req_getnameinfo)req)->cb);
                break;

            case UV_SHUTDOWN:
                SvREFCNT_dec(((UV__Req_shutdown)req)->cb);
                break;

            case UV_WRITE:
                Safefree(((UV__Req_write)req)->s);
                SvREFCNT_dec(((UV__Req_write)req)->cb);
                break;
        }

        Safefree(req);

void
cancel(UV::Req req)
    INIT:
        int err;
    CODE:
        err = uv_cancel(req->r);
        /* Cancellation is best-effort; don't consider it an error if we get
         * EBUSY */
        if((err != 0) && (err != UV_EBUSY))
            THROWERR("Couldn't cancel", err);

MODULE = UV             PACKAGE = UV::getaddrinfo_result

void
DESTROY(UV::getaddrinfo_result self)
    CODE:
        Safefree(self);

int
family(UV::getaddrinfo_result self)
    ALIAS:
        family   = 0
        socktype = 1
        protocol = 2
    CODE:
        switch(ix) {
            case 0: RETVAL = self->family;   break;
            case 1: RETVAL = self->socktype; break;
            case 2: RETVAL = self->protocol; break;
        }
    OUTPUT:
        RETVAL

SV *
addr(UV::getaddrinfo_result self)
    ALIAS:
        addr      = 0
        canonname = 1
    CODE:
        switch(ix) {
            case 0: RETVAL = newSVpvn((char *)self->addr, self->addrlen); break;
            case 1: RETVAL = self->canonname ? newSVpv(self->canonname, 0) : &PL_sv_undef; break;
        }
    OUTPUT:
        RETVAL