#ifndef _XH_H2X_H_
#define _XH_H2X_H_

#include "xh_config.h"
#include "xh_core.h"

#define XH_H2X_F_NONE                   0
#define XH_H2X_F_SIMPLE                 1
#define XH_H2X_F_COMPLEX                2
#define XH_H2X_F_CONTENT                4
#define XH_H2X_F_ATTR_ONLY              8

#define XH_H2X_T_SCALAR                 1
#define XH_H2X_T_HASH                   2
#define XH_H2X_T_ARRAY                  4
#define XH_H2X_T_BLESSED                8
#define XH_H2X_T_RAW                    16
#define XH_H2X_T_NOT_NULL               (XH_H2X_T_SCALAR | XH_H2X_T_ARRAY | XH_H2X_T_HASH)

#define XH_H2X_STASH_SIZE               16

typedef struct {
    xh_opts_t    opts;
    xh_int_t     depth;
    xh_writer_t  writer;
    xh_stack_t   stash;
    SV          *hash;
} xh_h2x_ctx_t;

XH_INLINE SV *
xh_h2x_call_method(SV *obj, GV *method)
{
    int  count;
    SV  *result = &PL_sv_undef;

    dSP;

    ENTER; SAVETMPS; PUSHMARK (SP);
    XPUSHs(sv_2mortal(newRV_inc(obj)));
    PUTBACK;

    count = call_sv((SV *) GvCV(method), G_SCALAR);

    SPAGAIN;

    if (count) {
        result = POPs;
        SvREFCNT_inc_void(result);
    }

    PUTBACK;

    FREETMPS; LEAVE;

    return result;
}

XH_INLINE SV *
xh_h2x_resolve_value(xh_h2x_ctx_t *ctx, SV *value, xh_uint_t *type)
{
    xh_int_t  nitems;
    GV       *method;
    SV       *tmpsv, *rv;
#if PERL_VERSION <= 8
    MAGIC    *mg;
#endif

    *type = 0;

    while ( SvOK(value) && SvROK(value) ) {
        if (++ctx->depth > ctx->opts.max_depth)
            croak("Maximum recursion depth exceeded");

        rv    = value;
        value = SvRV(value);
        *type = 0;

        if (SvOBJECT(value)) {
            if (SvGMAGICAL(value))
                mg_get(value);

            if (XH_AMAGIC(value) && (tmpsv = XH_AMG_CALL_STRING(rv)) != NULL) {
                value = tmpsv;
                SvREFCNT_inc_void(value);
                xh_stash_push(&ctx->stash, value);
            }
            else if ((method = gv_fetchmethod_autoload(SvSTASH(value), "toString", 0)) != NULL) {
                dSP;

                ENTER; SAVETMPS; PUSHMARK(SP);
                XPUSHs(sv_2mortal(newRV_inc(value)));
                PUTBACK;

                nitems = call_sv((SV *) GvCV(method), G_SCALAR);

                SPAGAIN;

                if (nitems == 1) {
                    value = POPs;
                    PUTBACK;

                    SvREFCNT_inc_void(value);

                    xh_stash_push(&ctx->stash, value);

                    FREETMPS; LEAVE;
                }
                else {
                    value = &PL_sv_undef;
                }

                *type |= XH_H2X_T_RAW;
            }
        }
        else if( SvTYPE(value) == SVt_PVCV ) {
            dSP;

            ENTER; SAVETMPS; PUSHMARK (SP);

            nitems = call_sv(value, G_SCALAR|G_NOARGS);

            SPAGAIN;

            if (nitems == 1) {
                value = POPs;

                SvREFCNT_inc_void(value);

                xh_stash_push(&ctx->stash, value);

                PUTBACK;

                FREETMPS;
                LEAVE;
            }
            else {
                value = &PL_sv_undef;
            }
        }
    }

    if (SvTYPE(value) == SVt_PVHV) {
        *type |= XH_H2X_T_HASH;
    }
    else if (SvTYPE(value) == SVt_PVAV) {
        *type |= XH_H2X_T_ARRAY;
    }
    else if (!SvOK(value)) {
        *type = 0;
    }
    else {
        *type |= XH_H2X_T_SCALAR;
    }

    if (SvOBJECT(value))
        *type |= XH_H2X_T_BLESSED;

    return value;
}

SV *xh_h2x(xh_h2x_ctx_t *ctx);
void xh_h2x_native(xh_h2x_ctx_t *ctx, xh_char_t *key, I32 key_len, SV *value);
xh_int_t xh_h2x_native_attr(xh_h2x_ctx_t *ctx, xh_char_t *key, I32 key_len, SV *value, xh_int_t flag);
void xh_h2x_lx(xh_h2x_ctx_t *ctx, SV *value, xh_char_t *key, I32 key_len, xh_int_t flag);

#ifdef XH_HAVE_DOM
SV *xh_h2d(xh_h2x_ctx_t *ctx);
void xh_h2d_native(xh_h2x_ctx_t *ctx, xmlNodePtr rootNode, xh_char_t *key, I32 key_len, SV *value);
xh_int_t xh_h2d_native_attr(xh_h2x_ctx_t *ctx, xmlNodePtr rootNode, xh_char_t *key, I32 key_len, SV *value, xh_int_t flag);
void xh_h2d_lx(xh_h2x_ctx_t *ctx, xmlNodePtr rootNode, SV *value, xh_char_t *key, I32 key_len, xh_int_t flag);
#endif

XH_INLINE void
xh_h2x_destroy_ctx(xh_h2x_ctx_t *ctx)
{
    xh_destroy_opts(&ctx->opts);
}

XH_INLINE void
xh_h2x_init_ctx(xh_h2x_ctx_t *ctx, I32 ax, I32 items)
{
    xh_opts_t *opts = NULL;
    xh_int_t   nparam = 0;

    memset(ctx, 0, sizeof(xh_h2x_ctx_t));

    opts = (xh_opts_t *) xh_get_obj_param(&nparam, ax, items, "XML::Hash::XS");
    ctx->hash = xh_get_hash_param(&nparam, ax, items);
    xh_merge_opts(&ctx->opts, opts, nparam, ax, items);
}

#endif /* _XH_H2X_H_ */