/*
 * This file was generated automatically by ExtUtils::ParseXS version 3.51 from the
 * contents of NGHTTP2.xs. Do not edit this file, edit NGHTTP2.xs instead.
 *
 *    ANY CHANGES MADE HERE WILL BE LOST!
 *
 */

#line 1 "NGHTTP2.xs"
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <nghttp2/nghttp2.h>
#include <string.h>

typedef struct {
    nghttp2_session *session;
    SV *on_headers_cb;
    SV *on_data_cb;
    SV *on_trailers_cb;
    SV *on_stream_close_cb;
} perl_nghttp2_session_t;

typedef struct {
    const char *buf;
    size_t len;
    size_t pos;
} perl_data_source_t;

static perl_nghttp2_session_t *get_session_ptr(SV *sv) {
    dTHX;
    if (!sv || !SvROK(sv)) return NULL;
    SV *inner = SvRV(sv);
    if (!inner) return NULL;
    return (perl_nghttp2_session_t*)INT2PTR(void*, SvIV(inner));
}

static ssize_t my_data_read_callback(nghttp2_session *session, int32_t stream_id,
                                     uint8_t *buf, size_t length,
                                     uint32_t *data_flags,
                                     nghttp2_data_source *source,
                                     void *user_data) {
    perl_data_source_t *src = (perl_data_source_t*)source->ptr;
    if (!src) return 0;
    size_t rem = src->len - src->pos;
    size_t copy_len = (rem < length) ? rem : length;
    if (copy_len > 0) {
        memcpy(buf, src->buf + src->pos, copy_len);
        src->pos += copy_len;
    }
    if (src->pos >= src->len) {
        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
        Safefree(src);
        source->ptr = NULL;
    }
    return (ssize_t)copy_len;
}

static int my_on_header_callback(nghttp2_session *session,
                                  const nghttp2_frame *frame,
                                  const uint8_t *name, size_t namelen,
                                  const uint8_t *value, size_t valuelen,
                                  uint8_t flags, void *user_data) {
    dTHX;
    perl_nghttp2_session_t *sess = (perl_nghttp2_session_t*)user_data;
    if (!sess) return 0;

    if (frame->hd.type == NGHTTP2_HEADERS) {
        int32_t stream_id = frame->hd.stream_id;
        SV *cb = NULL;
        if (frame->headers.cat == NGHTTP2_HCAT_HEADERS || frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
            cb = sess->on_headers_cb;
        } else {
            cb = sess->on_trailers_cb ? sess->on_trailers_cb : sess->on_headers_cb;
        }

        if (cb && SvOK(cb)) {
            dSP;
            ENTER;
            SAVETMPS;
            PUSHMARK(SP);
            mXPUSHi(stream_id);

            AV *av = newAV();
            av_push(av, newSVpvn((const char*)name, namelen));
            av_push(av, newSVpvn((const char*)value, valuelen));

            mXPUSHs(newRV_noinc((SV*)av));
            PUTBACK;
            call_sv(cb, G_DISCARD);
            FREETMPS;
            LEAVE;
        }
    }
    return 0;
}

static int my_on_data_chunk_recv_callback(nghttp2_session *session,
                                           uint8_t flags, int32_t stream_id,
                                           const uint8_t *data, size_t len,
                                           void *user_data) {
    dTHX;
    perl_nghttp2_session_t *sess = (perl_nghttp2_session_t*)user_data;
    if (!sess) return 0;

    if (sess->on_data_cb && SvOK(sess->on_data_cb)) {
        dSP;
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        mXPUSHi(stream_id);
        mXPUSHs(newSVpvn((const char*)data, len));
        PUTBACK;
        call_sv(sess->on_data_cb, G_DISCARD);
        FREETMPS;
        LEAVE;
    }
    return 0;
}

static int my_on_stream_close_callback(nghttp2_session *session,
                                        int32_t stream_id,
                                        uint32_t error_code,
                                        void *user_data) {
    dTHX;
    perl_nghttp2_session_t *sess = (perl_nghttp2_session_t*)user_data;
    if (!sess) return 0;

    if (sess->on_stream_close_cb && SvOK(sess->on_stream_close_cb)) {
        dSP;
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        mXPUSHi(stream_id);
        mXPUSHi(error_code);
        PUTBACK;
        call_sv(sess->on_stream_close_cb, G_DISCARD);
        FREETMPS;
        LEAVE;
    }
    return 0;
}

#line 146 "NGHTTP2.c"
#ifndef PERL_UNUSED_VAR
#  define PERL_UNUSED_VAR(var) if (0) var = var
#endif

#ifndef dVAR
#  define dVAR		dNOOP
#endif


/* This stuff is not part of the API! You have been warned. */
#ifndef PERL_VERSION_DECIMAL
#  define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
#endif
#ifndef PERL_DECIMAL_VERSION
#  define PERL_DECIMAL_VERSION \
	  PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
#endif
#ifndef PERL_VERSION_GE
#  define PERL_VERSION_GE(r,v,s) \
	  (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
#endif
#ifndef PERL_VERSION_LE
#  define PERL_VERSION_LE(r,v,s) \
	  (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
#endif

/* XS_INTERNAL is the explicit static-linkage variant of the default
 * XS macro.
 *
 * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
 * "STATIC", ie. it exports XSUB symbols. You probably don't want that
 * for anything but the BOOT XSUB.
 *
 * See XSUB.h in core!
 */


/* TODO: This might be compatible further back than 5.10.0. */
#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
#  undef XS_EXTERNAL
#  undef XS_INTERNAL
#  if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
#    define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
#    define XS_INTERNAL(name) STATIC XSPROTO(name)
#  endif
#  if defined(__SYMBIAN32__)
#    define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
#    define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
#  endif
#  ifndef XS_EXTERNAL
#    if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
#      define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
#      define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
#    else
#      ifdef __cplusplus
#        define XS_EXTERNAL(name) extern "C" XSPROTO(name)
#        define XS_INTERNAL(name) static XSPROTO(name)
#      else
#        define XS_EXTERNAL(name) XSPROTO(name)
#        define XS_INTERNAL(name) STATIC XSPROTO(name)
#      endif
#    endif
#  endif
#endif

/* perl >= 5.10.0 && perl <= 5.15.1 */


/* The XS_EXTERNAL macro is used for functions that must not be static
 * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
 * macro defined, the best we can do is assume XS is the same.
 * Dito for XS_INTERNAL.
 */
#ifndef XS_EXTERNAL
#  define XS_EXTERNAL(name) XS(name)
#endif
#ifndef XS_INTERNAL
#  define XS_INTERNAL(name) XS(name)
#endif

/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
 * internal macro that we're free to redefine for varying linkage due
 * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
 * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
 */

#undef XS_EUPXS
#if defined(PERL_EUPXS_ALWAYS_EXPORT)
#  define XS_EUPXS(name) XS_EXTERNAL(name)
#else
   /* default to internal */
#  define XS_EUPXS(name) XS_INTERNAL(name)
#endif

#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)

/* prototype to pass -Wmissing-prototypes */
STATIC void
S_croak_xs_usage(const CV *const cv, const char *const params);

STATIC void
S_croak_xs_usage(const CV *const cv, const char *const params)
{
    const GV *const gv = CvGV(cv);

    PERL_ARGS_ASSERT_CROAK_XS_USAGE;

    if (gv) {
        const char *const gvname = GvNAME(gv);
        const HV *const stash = GvSTASH(gv);
        const char *const hvname = stash ? HvNAME(stash) : NULL;

        if (hvname)
	    Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params);
        else
	    Perl_croak_nocontext("Usage: %s(%s)", gvname, params);
    } else {
        /* Pants. I don't think that it should be possible to get here. */
	Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
    }
}
#undef  PERL_ARGS_ASSERT_CROAK_XS_USAGE

#define croak_xs_usage        S_croak_xs_usage

#endif

/* NOTE: the prototype of newXSproto() is different in versions of perls,
 * so we define a portable version of newXSproto()
 */
#ifdef newXS_flags
#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
#else
#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
#endif /* !defined(newXS_flags) */

#if PERL_VERSION_LE(5, 21, 5)
#  define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)
#else
#  define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)
#endif

#line 290 "NGHTTP2.c"

XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_new); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_new)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "class_name");
    {
	SV *	RETVAL;
	char *	class_name = (char *)SvPV_nolen(ST(0))
;
#line 141 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess;
    nghttp2_session_callbacks *callbacks;

    Newxz(sess, 1, perl_nghttp2_session_t);

    nghttp2_session_callbacks_new(&callbacks);
    nghttp2_session_callbacks_set_on_header_callback(callbacks, my_on_header_callback);
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, my_on_data_chunk_recv_callback);
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, my_on_stream_close_callback);

    nghttp2_session_client_new(&(sess->session), callbacks, sess);
    nghttp2_session_callbacks_del(callbacks);

    nghttp2_submit_settings(sess->session, NGHTTP2_FLAG_NONE, NULL, 0);

    SV *obj = sv_newmortal();
    sv_setref_pv(obj, class_name ? class_name : "Google::gRPC::Engine::NGHTTP2", (void*)sess);
    RETVAL = newSVsv(obj);
#line 321 "NGHTTP2.c"
	RETVAL = sv_2mortal(RETVAL);
	ST(0) = RETVAL;
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_DESTROY); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_DESTROY)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "self_sv");
    {
	SV *	self_sv = ST(0)
;
#line 165 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess = get_session_ptr(self_sv);
    if (sess) {
        if (sess->session) nghttp2_session_del(sess->session);
        if (sess->on_headers_cb) SvREFCNT_dec(sess->on_headers_cb);
        if (sess->on_data_cb) SvREFCNT_dec(sess->on_data_cb);
        if (sess->on_trailers_cb) SvREFCNT_dec(sess->on_trailers_cb);
        if (sess->on_stream_close_cb) SvREFCNT_dec(sess->on_stream_close_cb);
        Safefree(sess);
    }
#line 348 "NGHTTP2.c"
    }
    XSRETURN_EMPTY;
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_is_xs); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_is_xs)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "self_sv");
    {
	int	RETVAL;
	dXSTARG;
	SV *	self_sv = ST(0)
;
#line 178 "NGHTTP2.xs"
    RETVAL = 1;
#line 367 "NGHTTP2.c"
	XSprePUSH;
	PUSHi((IV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_set_callbacks); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_set_callbacks)
{
    dVAR; dXSARGS;
    if (items != 5)
       croak_xs_usage(cv,  "self_sv, on_headers, on_data, on_trailers, on_stream_close");
    {
	SV *	self_sv = ST(0)
;
	SV *	on_headers = ST(1)
;
	SV *	on_data = ST(2)
;
	SV *	on_trailers = ST(3)
;
	SV *	on_stream_close = ST(4)
;
#line 185 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess = get_session_ptr(self_sv);
    if (!sess) croak("invalid session pointer");

    if (SvOK(on_headers)) {
        if (sess->on_headers_cb) SvREFCNT_dec(sess->on_headers_cb);
        sess->on_headers_cb = newSVsv(on_headers);
    }
    if (SvOK(on_data)) {
        if (sess->on_data_cb) SvREFCNT_dec(sess->on_data_cb);
        sess->on_data_cb = newSVsv(on_data);
    }
    if (SvOK(on_trailers)) {
        if (sess->on_trailers_cb) SvREFCNT_dec(sess->on_trailers_cb);
        sess->on_trailers_cb = newSVsv(on_trailers);
    }
    if (SvOK(on_stream_close)) {
        if (sess->on_stream_close_cb) SvREFCNT_dec(sess->on_stream_close_cb);
        sess->on_stream_close_cb = newSVsv(on_stream_close);
    }
#line 412 "NGHTTP2.c"
    }
    XSRETURN_EMPTY;
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_submit_request); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2__xs_submit_request)
{
    dVAR; dXSARGS;
    if (items != 4)
       croak_xs_usage(cv,  "self_sv, headers_av_ref, data_sv, end_stream");
    {
	int	RETVAL;
	dXSTARG;
	SV *	self_sv = ST(0)
;
	SV *	headers_av_ref = ST(1)
;
	SV *	data_sv = ST(2)
;
	int	end_stream = (int)SvIV(ST(3))
;
#line 208 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess = get_session_ptr(self_sv);
    if (!sess) croak("invalid session pointer");

    if (!SvROK(headers_av_ref) || SvTYPE(SvRV(headers_av_ref)) != SVt_PVAV) {
        croak("headers must be an array reference");
    }
    AV *av = (AV*)SvRV(headers_av_ref);
    SSize_t len = av_len(av) + 1;
    if (len % 2 != 0) {
        croak("headers array must contain key/value pairs");
    }
    size_t num_nv = len / 2;
    nghttp2_nv *nva;
    Newxz(nva, num_nv, nghttp2_nv);

    size_t i;
    for (i = 0; i < num_nv; i++) {
        SV **k_ptr = av_fetch(av, i * 2, 0);
        SV **v_ptr = av_fetch(av, i * 2 + 1, 0);
        STRLEN k_len, v_len;
        const char *k_str = k_ptr ? SvPVbyte(*k_ptr, k_len) : "";
        const char *v_str = v_ptr ? SvPVbyte(*v_ptr, v_len) : "";

        nva[i].name = (uint8_t*)k_str;
        nva[i].namelen = k_len;
        nva[i].value = (uint8_t*)v_str;
        nva[i].valuelen = v_len;
        nva[i].flags = NGHTTP2_NV_FLAG_NONE;
    }

    nghttp2_data_provider data_prd;
    nghttp2_data_provider *p_prd = NULL;

    if (data_sv && SvOK(data_sv)) {
        perl_data_source_t *src;
        Newxz(src, 1, perl_data_source_t);
        STRLEN d_len;
        src->buf = SvPVbyte(data_sv, d_len);
        src->len = d_len;
        src->pos = 0;

        data_prd.source.ptr = src;
        data_prd.read_callback = my_data_read_callback;
        p_prd = &data_prd;
    }

    int32_t stream_id = nghttp2_submit_request(sess->session, NULL, nva, num_nv, p_prd, NULL);
    Safefree(nva);

    if (stream_id < 0) {
        croak("nghttp2_submit_request failed: %s", nghttp2_strerror(stream_id));
    }
    RETVAL = (int)stream_id;
#line 489 "NGHTTP2.c"
	XSprePUSH;
	PUSHi((IV)RETVAL);
    }
    XSRETURN(1);
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_feed_input); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_feed_input)
{
    dVAR; dXSARGS;
    if (items != 2)
       croak_xs_usage(cv,  "self_sv, input_sv");
    {
	SV *	self_sv = ST(0)
;
	SV *	input_sv = ST(1)
;
#line 267 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess = get_session_ptr(self_sv);
    if (!sess) croak("invalid session pointer");

    if (input_sv && SvOK(input_sv)) {
        STRLEN len;
        const char *buf = SvPVbyte(input_sv, len);
        if (len > 0 && sess->session) {
            ssize_t rv = nghttp2_session_mem_recv(sess->session, (const uint8_t*)buf, len);
            if (rv < 0) {
                croak("nghttp2_session_mem_recv failed: %s", nghttp2_strerror((int)rv));
            }
        }
    }
#line 522 "NGHTTP2.c"
    }
    XSRETURN_EMPTY;
}


XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_get_output); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_Google__gRPC__Engine__NGHTTP2_get_output)
{
    dVAR; dXSARGS;
    if (items != 1)
       croak_xs_usage(cv,  "self_sv");
    {
	SV *	RETVAL;
	SV *	self_sv = ST(0)
;
#line 284 "NGHTTP2.xs"
    perl_nghttp2_session_t *sess = get_session_ptr(self_sv);
    if (!sess) croak("invalid session pointer");

    const uint8_t *data_ptr = NULL;
    ssize_t len;
    SV *res = newSVpvn("", 0);
    while (sess->session && (len = nghttp2_session_mem_send(sess->session, &data_ptr)) > 0) {
        sv_catpvn(res, (const char*)data_ptr, len);
    }
    RETVAL = res;
#line 549 "NGHTTP2.c"
	RETVAL = sv_2mortal(RETVAL);
	ST(0) = RETVAL;
    }
    XSRETURN(1);
}

#ifdef __cplusplus
extern "C" {
#endif
XS_EXTERNAL(boot_Google__gRPC); /* prototype to pass -Wmissing-prototypes */
XS_EXTERNAL(boot_Google__gRPC)
{
#if PERL_VERSION_LE(5, 21, 5)
    dVAR; dXSARGS;
#else
    dVAR; dXSBOOTARGSXSAPIVERCHK;
#endif
#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */
    char* file = __FILE__;
#else
    const char* file = __FILE__;
#endif

    PERL_UNUSED_VAR(file);

    PERL_UNUSED_VAR(cv); /* -W */
    PERL_UNUSED_VAR(items); /* -W */
#if PERL_VERSION_LE(5, 21, 5)
    XS_VERSION_BOOTCHECK;
#  ifdef XS_APIVERSION_BOOTCHECK
    XS_APIVERSION_BOOTCHECK;
#  endif
#endif

        newXS_deffile("Google::gRPC::Engine::NGHTTP2::_xs_new", XS_Google__gRPC__Engine__NGHTTP2__xs_new);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::DESTROY", XS_Google__gRPC__Engine__NGHTTP2_DESTROY);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::is_xs", XS_Google__gRPC__Engine__NGHTTP2_is_xs);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::_xs_set_callbacks", XS_Google__gRPC__Engine__NGHTTP2__xs_set_callbacks);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::_xs_submit_request", XS_Google__gRPC__Engine__NGHTTP2__xs_submit_request);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::feed_input", XS_Google__gRPC__Engine__NGHTTP2_feed_input);
        newXS_deffile("Google::gRPC::Engine::NGHTTP2::get_output", XS_Google__gRPC__Engine__NGHTTP2_get_output);
#if PERL_VERSION_LE(5, 21, 5)
#  if PERL_VERSION_GE(5, 9, 0)
    if (PL_unitcheckav)
        call_list(PL_scopestack_ix, PL_unitcheckav);
#  endif
    XSRETURN_YES;
#else
    Perl_xs_boot_epilog(aTHX_ ax);
#endif
}

#ifdef __cplusplus
}
#endif
