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

XH_INLINE void
_xh_h2x_lx(xh_h2x_ctx_t *ctx, xh_char_t *key, I32 key_len, SV *value, xh_int_t flag)
{
    xh_uint_t type;

    value = xh_h2x_resolve_value(ctx, value, &type);

    if (ctx->opts.cdata[0] != '\0' && xh_strcmp(key, ctx->opts.cdata) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY || !(type & XH_H2X_T_SCALAR)) return;
        xh_xml_write_cdata(&ctx->writer, value);
    }
    else if (ctx->opts.text[0] != '\0' && xh_strcmp(key, ctx->opts.text) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY || !(type & XH_H2X_T_SCALAR)) return;
        xh_xml_write_content(&ctx->writer, value);
    }
    else if (ctx->opts.comm[0] != '\0' && xh_strcmp(key, ctx->opts.comm) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY) return;

        if (type & XH_H2X_T_SCALAR) {
            xh_xml_write_comment(&ctx->writer, value);
        }
        else {
            xh_xml_write_comment(&ctx->writer, NULL);
        }
    }
    else if (ctx->opts.attr[0] != '\0') {
        if (xh_strncmp(key, ctx->opts.attr, ctx->opts.attr_len) == 0) {
            if (!(flag & XH_H2X_F_ATTR_ONLY)) return;

            key     += ctx->opts.attr_len;
            key_len -= ctx->opts.attr_len;

            if (type & XH_H2X_T_SCALAR) {
                xh_xml_write_attribute(&ctx->writer, key, key_len, value);
            }
            else {
                xh_xml_write_attribute(&ctx->writer, key, key_len, NULL);
            }
        }
        else {
            if (flag & XH_H2X_F_ATTR_ONLY) return;

            if (type & XH_H2X_T_NOT_NULL) {
                /* '<tag' */
                xh_xml_write_start_tag(&ctx->writer, key, key_len);
                /* ' attr1="..." attr2="..."' */
                xh_h2x_lx(ctx, value, XH_H2X_F_ATTR_ONLY);
                /* '>' */
                xh_xml_write_end_tag(&ctx->writer);

                xh_h2x_lx(ctx, value, XH_H2X_F_NONE);

                xh_xml_write_end_node(&ctx->writer, key, key_len);
            }
            else {
                xh_xml_write_empty_node(&ctx->writer, key, key_len);
            }
        }
    }
    else {
        if (type & XH_H2X_T_NOT_NULL) {
            /* '<tag>' */
            xh_xml_write_start_node(&ctx->writer, key, key_len);

            xh_h2x_lx(ctx, value, XH_H2X_F_NONE);

            /* '</tag>' */
            xh_xml_write_end_node(&ctx->writer, key, key_len);
        }
        else {
            xh_xml_write_empty_node(&ctx->writer, key, key_len);
        }
    }
}

void
xh_h2x_lx(xh_h2x_ctx_t *ctx, SV *value, xh_int_t flag)
{
    SV             *hash_value;
    xh_char_t      *key;
    I32             key_len;
    size_t          len, i;
    xh_uint_t       type;
    xh_sort_hash_t *sorted_hash;

    value = xh_h2x_resolve_value(ctx, value, &type);

    if (type & XH_H2X_T_SCALAR) {
        if (flag & XH_H2X_F_ATTR_ONLY) goto FINISH;
        xh_xml_write_content(&ctx->writer, value);
    }
    else if (type & XH_H2X_T_HASH) {
        len = HvUSEDKEYS((HV *) value);

        if (len > 1 && ctx->opts.canonical) {
            sorted_hash = xh_sort_hash((HV *) value, len);
            for (i = 0; i < len; i++) {
                _xh_h2x_lx(ctx, sorted_hash[i].key, sorted_hash[i].key_len, sorted_hash[i].value, flag);
            }
            free(sorted_hash);
        }
        else {
            hv_iterinit((HV *) value);
            while ((hash_value = hv_iternextsv((HV *) value, (char **) &key, &key_len))) {
                _xh_h2x_lx(ctx, key, key_len, hash_value, flag);
            }
        }
    }
    else if (type & XH_H2X_T_ARRAY) {
        len = av_len((AV *) value) + 1;
        for (i = 0; i < len; i++) {
            xh_h2x_lx(ctx, *av_fetch((AV *) value, i, 0), flag);
        }
    }

FINISH:
    ctx->depth--;
}

#ifdef XH_HAVE_DOM
XH_INLINE void
_xh_h2d_lx(xh_h2x_ctx_t *ctx, xmlNodePtr rootNode, xh_char_t *key, I32 key_len, SV *value, xh_int_t flag)
{
    xh_uint_t      type;

    value = xh_h2x_resolve_value(ctx, value, &type);

    if (ctx->opts.cdata[0] != '\0' && xh_strcmp(key, ctx->opts.cdata) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY || !(type & XH_H2X_T_SCALAR)) return;
        xh_dom_new_cdata(ctx, rootNode, value);
    }
    else if (ctx->opts.text[0] != '\0' && xh_strcmp(key, ctx->opts.text) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY || !(type & XH_H2X_T_SCALAR)) return;
        xh_dom_new_content(ctx, rootNode, value);
    }
    else if (ctx->opts.comm[0] != '\0' && xh_strcmp(key, ctx->opts.comm) == 0) {
        if (flag & XH_H2X_F_ATTR_ONLY) return;

        if (!type) {
            xh_dom_new_comment(ctx, rootNode, NULL);
        }
        else if (type & XH_H2X_T_SCALAR) {
            xh_dom_new_comment(ctx, rootNode, value);
        }
    }
    else if (ctx->opts.attr[0] != '\0') {
        if (xh_strncmp(key, ctx->opts.attr, ctx->opts.attr_len) == 0) {
            if (!(flag & XH_H2X_F_ATTR_ONLY)) return;

            key     += ctx->opts.attr_len;
            key_len -= ctx->opts.attr_len;

            if (type & XH_H2X_T_SCALAR) {
                xh_dom_new_attribute(ctx, rootNode, key, key_len, value);
            }
            else {
                xh_dom_new_attribute(ctx, rootNode, key, key_len, NULL);
            }
        }
        else {
            if (flag & XH_H2X_F_ATTR_ONLY) return;
            rootNode = xh_dom_new_node(ctx, rootNode, key, key_len, NULL, type & XH_H2X_T_RAW);
            if (type & XH_H2X_T_NOT_NULL) {
                xh_h2d_lx(ctx, rootNode, value, XH_H2X_F_ATTR_ONLY);
                xh_h2d_lx(ctx, rootNode, value, XH_H2X_F_NONE);
            }
        }
    }
    else {
        rootNode = xh_dom_new_node(ctx, rootNode, key, key_len, NULL, type & XH_H2X_T_RAW);
        if (type & XH_H2X_T_NOT_NULL) {
            xh_h2d_lx(ctx, rootNode, value, XH_H2X_F_NONE);
        }
    }
}

void
xh_h2d_lx(xh_h2x_ctx_t *ctx, xmlNodePtr rootNode, SV *value, xh_int_t flag)
{
    SV             *hash_value;
    xh_char_t      *key;
    I32             key_len;
    size_t          len, i;
    xh_uint_t       type;
    xh_sort_hash_t *sorted_hash;

    value = xh_h2x_resolve_value(ctx, value, &type);

    if (type & XH_H2X_T_SCALAR) {
        if (flag & XH_H2X_F_ATTR_ONLY) goto FINISH;
        xh_dom_new_content(ctx, rootNode, value);
    }
    else if (type & XH_H2X_T_HASH) {
        len = HvUSEDKEYS((HV *) value);
        hv_iterinit((HV *) value);

        if (len > 1 && ctx->opts.canonical) {
            sorted_hash = xh_sort_hash((HV *) value, len);
            for (i = 0; i < len; i++) {
                _xh_h2d_lx(ctx, rootNode, sorted_hash[i].key, sorted_hash[i].key_len, sorted_hash[i].value, flag);
            }
            free(sorted_hash);
        }
        else {
            while ((hash_value = hv_iternextsv((HV *) value, (char **) &key, &key_len))) {
                _xh_h2d_lx(ctx, rootNode, key, key_len, hash_value, flag);
            }
        }
    }
    else if (type & XH_H2X_T_ARRAY) {
        len = av_len((AV *) value) + 1;
        for (i = 0; i < len; i++) {
            xh_h2d_lx(ctx, rootNode, *av_fetch((AV *) value, i, 0), flag);
        }
    }

FINISH:
    ctx->depth--;
}
#endif
