#ifdef __cplusplus
extern "C" {
#endif

#define PERL_NO_GET_CONTEXT /* we want efficiency */
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#ifdef __cplusplus
} /* extern "C" */
#endif

#include <string.h>

#define NEED_newSVpvn_flags
#include "ppport.h"

#include "../../hoedown/src/document.h"
#include "../../hoedown/src/html.h"

#define XS_STRUCT2OBJ(sv, class, obj) \
    sv = newSViv(PTR2IV(obj));  \
    sv = newRV_noinc(sv); \
    sv_bless(sv, gv_stashpv(class, 1)); \
    SvREADONLY_on(sv);

#define XS_STATE(type, x) \
    INT2PTR(type, SvROK(x) ? SvIV(SvRV(x)) : SvIV(x))

#define PUSHBUF(text) \
    if (text) { \
        mXPUSHp(text->data, text->size); \
    } else { \
        XPUSHs(&PL_sv_undef); \
    }

#define CB_HEADER(key) \
    SV* cb = *rcb; \
    \
    ENTER; \
    SAVETMPS; \
    \
    PUSHMARK(SP);

#define CB_FOOTER \
    PUTBACK; \
    \
    int count = call_sv(cb, G_SCALAR); \
    \
    SPAGAIN; \
    \
    if (count == 1) { \
        SV* ret = POPs; \
        if (ret != &PL_sv_undef) { \
            STRLEN l; \
            char * p = SvPV(ret, l); \
            hoedown_buffer_grow(ob, ob->size + l); \
            hoedown_buffer_put(ob, p, l); \
        } else {\
            is_null = 1;\
        } \
    } \
    \
    PUTBACK; \
    FREETMPS; \
    LEAVE;


#include "gen.callback.c"

#define TMH_CONST(name) \
    newCONSTSUB(stash, #name, newSViv(name)); \
    av_push(get_av("Text::Markdown::Hoedown::EXPORT", GV_ADD), newSVpv(#name, 0));

MODULE = Text::Markdown::Hoedown    PACKAGE = Text::Markdown::Hoedown PREFIX=hoedown_document_

BOOT:
    HV* stash = gv_stashpv("Text::Markdown::Hoedown", GV_ADD);

    /* block-level extensions */
    TMH_CONST(HOEDOWN_EXT_TABLES);
    TMH_CONST(HOEDOWN_EXT_FENCED_CODE);
    TMH_CONST(HOEDOWN_EXT_FOOTNOTES);

    /* span-level extensions */
    TMH_CONST(HOEDOWN_EXT_AUTOLINK);
    TMH_CONST(HOEDOWN_EXT_STRIKETHROUGH);
    TMH_CONST(HOEDOWN_EXT_UNDERLINE);
    TMH_CONST(HOEDOWN_EXT_HIGHLIGHT);
    TMH_CONST(HOEDOWN_EXT_QUOTE);
    TMH_CONST(HOEDOWN_EXT_SUPERSCRIPT);
    TMH_CONST(HOEDOWN_EXT_MATH);

    /* other flags */
    TMH_CONST(HOEDOWN_EXT_NO_INTRA_EMPHASIS);
    TMH_CONST(HOEDOWN_EXT_SPACE_HEADERS);
    TMH_CONST(HOEDOWN_EXT_MATH_EXPLICIT);

    /* negative flags */
    TMH_CONST(HOEDOWN_EXT_DISABLE_INDENTED_CODE);

    /* HTML */
    TMH_CONST(HOEDOWN_HTML_SKIP_HTML);
    TMH_CONST(HOEDOWN_HTML_ESCAPE);
    TMH_CONST(HOEDOWN_HTML_HARD_WRAP);
    TMH_CONST(HOEDOWN_HTML_USE_XHTML);

TYPEMAP: <<HERE

hoedown_renderer* T_H_RENDERER
hoedown_document* T_H_DOCUMENT

OUTPUT

T_H_DOCUMENT
    sv_setref_pv($arg, \"Text::Markdown::Hoedown::Markdown\", (void*)$var);

INPUT

T_H_DOCUMENT
    $var = INT2PTR($type, SvROK($arg) ? SvIV(SvRV($arg)) : SvIV($arg));

HERE

PROTOTYPES: DISABLE

MODULE = Text::Markdown::Hoedown    PACKAGE = Text::Markdown::Hoedown::Markdown

hoedown_document *
new(const char* klass, unsigned int extensions, size_t max_nesting, SV* renderer_sv)
CODE:
    hoedown_renderer* renderer = XS_STATE(hoedown_renderer*, renderer_sv);
    RETVAL = hoedown_document_new(renderer, extensions, max_nesting);
OUTPUT:
    RETVAL

SV*
render(hoedown_document *self, SV *src_sv)
PREINIT:
    struct hoedown_buffer* ob;
    const char *src;
    STRLEN src_len;
CODE:
    ob = hoedown_buffer_new(64);
    if (!ob) {
        croak("Cannot create new hoedown_buffer(malloc failed)");
    }

    src = SvPV(src_sv, src_len);
    hoedown_document_render(self, ob, src, src_len);

    SV* ret = newSVpv(hoedown_buffer_cstr(ob), 0);
    if (SvUTF8(src_sv)) {
        SvUTF8_on(ret);
    }
    hoedown_buffer_free(ob);
    RETVAL = ret;
OUTPUT:
    RETVAL

MODULE = Text::Markdown::Hoedown    PACKAGE = Text::Markdown::Hoedown::Renderer::HTML

void
new(const char* klass, unsigned int render_flags, int nesting_level)
PPCODE:
    hoedown_renderer * renderer = hoedown_html_renderer_new(
        render_flags, nesting_level
    );
    ST(0) = sv_newmortal();
    sv_setref_pv(ST(0), "Text::Markdown::Hoedown::Renderer::HTML", (void*)renderer);
    XSRETURN(1);

void
DESTROY(SV* this)
CODE:
    hoedown_renderer* self = INT2PTR(hoedown_renderer*, SvROK(this) ? SvIV(SvRV(this)) : SvIV(this));
    hoedown_html_renderer_free(self);

MODULE = Text::Markdown::Hoedown    PACKAGE = Text::Markdown::Hoedown::Renderer::HTMLTOC

void
new(const char* klass, int nesting_level)
PPCODE:
    hoedown_renderer * renderer = hoedown_html_toc_renderer_new(
        nesting_level
    );
    ST(0) = sv_newmortal();
    sv_setref_pv(ST(0), "Text::Markdown::Hoedown::Renderer::HTMLTOC", (void*)renderer);
    XSRETURN(1);

void
DESTROY(SV* this)
CODE:
    hoedown_renderer* self = INT2PTR(hoedown_renderer*, SvROK(this) ? SvIV(SvRV(this)) : SvIV(this));
    hoedown_html_renderer_free(self);

MODULE = Text::Markdown::Hoedown    PACKAGE = Text::Markdown::Hoedown::Renderer::Callback

void
new(const char* klass)
PPCODE:
    hoedown_renderer * renderer;
    Newxz(renderer, 1, hoedown_renderer);
    renderer->opaque = newHV();
    ST(0) = sv_newmortal();
    sv_setref_pv(ST(0), "Text::Markdown::Hoedown::Renderer::Callback", (void*)renderer);
    XSRETURN(1);

void
DESTROY(SV* this)
CODE:
    hoedown_renderer* self = INT2PTR(hoedown_renderer*, SvROK(this) ? SvIV(SvRV(this)) : SvIV(this));
    SvREFCNT_dec(self->opaque);
    Safefree(self);

INCLUDE: gen.callback.inc