#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "const-c.inc"

#include <ucl.h>

bool implicit_unicode;
int ucl_string_flags;

static ucl_object_t *
_iterate_perl (pTHX_ SV* obj) {
    if ( SvTYPE(obj) == SVt_NULL ) {
        return ucl_object_new();
    }
    else if (
        sv_isobject(obj) && (
               sv_isa(obj, "JSON::PP::Boolean")
            || sv_isa(obj, "Types::Serialiser::BooleanBase")
            || sv_isa(obj, "JSON::XS::Boolean")
            || sv_isa(obj, "Data::MessagePack::Boolean")
            || sv_isa(obj, "boolean")
            || sv_isa(obj, "Mojo::JSON::_Bool")
        )
    ) {
        return ucl_object_frombool( SvTRUE(obj) );
    }
    else if ( SvROK(obj) && SvTYPE(SvRV(obj)) == SVt_PVAV ) {
        ucl_object_t *top, *elm;
        top = ucl_object_typed_new(UCL_ARRAY);
        size_t nums_len = av_len((AV*)SvRV(obj)) + 1;
        for (int i = 0; i < nums_len; i++) {
            SV** num_sv_ptr = av_fetch((AV*)SvRV(obj), i, FALSE);
            SV* num_sv = num_sv_ptr ? *num_sv_ptr : &PL_sv_undef;
            elm = _iterate_perl(aTHX_ num_sv);
            ucl_array_append(top, elm);
        }
        return top;
    }
    else if ( SvROK(obj) && SvTYPE(SvRV(obj)) == SVt_PVHV ) {
        ucl_object_t *top, *elm;
        char *keystr = NULL;
        top = ucl_object_typed_new(UCL_OBJECT);
        HV *hash= (HV*)SvRV(obj);
        HE *iter;
        hv_iterinit(hash);
        int *len;
        for (iter=hv_iternext(hash); iter; iter=hv_iternext(hash)) {
            char* key;
            key = HeKEY(iter);
            elm = _iterate_perl(aTHX_ HeVAL(iter));
            ucl_object_insert_key(top, elm, key, 0, true);
        }
        return top;
    }
    else if ( SvIOK(obj) ) {
        return ucl_object_fromint(SvIV(obj));
    }
    else if ( SvNOK(obj) ) {
        return ucl_object_fromdouble(SvNV(obj));
    }
    else if ( SvPOK(obj) || SvPOKp(obj)) {
        // https://github.com/vstakhov/libucl/blob/master/doc/api.md#ucl_object_fromstring_common
        return ucl_object_fromstring_common( SvPV_nolen(obj), strlen(SvPV_nolen(obj)), ucl_string_flags );
    }
    else if ( !SvOK(obj) ) { // undef
        return ucl_object_new();
    }

    croak("unknown type %d", SvTYPE(obj) );
}

SV *
_ucl_type (pTHX_ ucl_object_t const *obj)
{
    switch (obj->type) {
        case UCL_INT:
            return newSViv((long long)ucl_object_toint (obj));
        case UCL_FLOAT:
            return newSVnv(ucl_object_todouble (obj));
        case UCL_STRING: {
            SV *sv = newSVpv(ucl_object_tostring (obj), 0);
            if (implicit_unicode) {
                SvUTF8_on(sv);
            }
            return sv;
        }
        case UCL_BOOLEAN: {
            SV* rv = newSV_type(SVt_IV);
            sv_setref_iv(rv, "JSON::PP::Boolean", ucl_object_toboolean(obj) ? 1 : 0);
            return rv;
        }
        case UCL_TIME:
            return newSVnv(ucl_object_todouble (obj));
        case UCL_NULL:
            return &PL_sv_undef;
    }
    return NULL;
}

SV *
_iterate_ucl (pTHX_ ucl_object_t const *obj) {
    const ucl_object_t *tmp;
    ucl_object_iter_t it = NULL;

    tmp = obj;

    while ((obj = ucl_object_iterate (tmp, &it, false))) {
        SV *val;

        val = _ucl_type(aTHX_ obj);
        if (!val) {
            SV *key = NULL;

            if (obj->key != NULL) {
                key = newSVpv(ucl_object_key(obj),0);
            }

            if (obj->type == UCL_OBJECT) {
                const ucl_object_t *cur;
                ucl_object_iter_t it_obj = NULL;

                val = sv_2mortal((SV*)newHV());

                while ((cur = ucl_object_iterate (obj, &it_obj, true))) {
                    SV *key = newSVpv(ucl_object_key(cur),0);
                    if (implicit_unicode)
                        SvUTF8_on(key);
                    hv_store_ent((HV*)val, sv_2mortal(key), _iterate_ucl(aTHX_ cur), 0);
                }
                val = newRV(val);
            } else if (obj->type == UCL_ARRAY) {
                const ucl_object_t *cur;
                ucl_object_iter_t it_obj = NULL;

                val = sv_2mortal((SV*)newAV());

                while ((cur = ucl_object_iterate (obj, &it_obj, true))) {
                    av_push((AV*)val, newRV((SV*)_iterate_ucl(aTHX_ cur)));
                }
                val = newRV(val);
            } else if (obj->type == UCL_USERDATA) {
                val = newSVpv(obj->value.ud, 0);
            }
        }
        return val;
    }

    croak("unhandled type");
}

SV *
_load_ucl(pTHX_ struct ucl_parser *parser) {
    SV *ret;
    const char *err = ucl_parser_get_error(parser);

    if (err) {
        croak(err);
    }
    else {
        ucl_object_t *uclobj = ucl_parser_get_object(parser);
        ret = _iterate_ucl(aTHX_ uclobj);
    }

    return ret;
}


MODULE = Config::UCL       PACKAGE = Config::UCL

INCLUDE: const-xs.inc
PROTOTYPES: ENABLED

SV *
_ucl_dump(SV *sv, bool _implicit_unicode, ucl_emitter_t emitter)
    CODE:
        implicit_unicode = _implicit_unicode;
	ucl_object_t *root = NULL;
	root = _iterate_perl(aTHX_ sv);
	if (root) {
            RETVAL = newSVpv((char *)ucl_object_emit(root, emitter), 0);
            if (implicit_unicode)
                SvUTF8_on(RETVAL);
            ucl_object_unref(root);
	}
    OUTPUT:
        RETVAL

bool
ucl_validate(SV *schema_sv, SV *data_sv)
    CODE:
        ucl_object_t *data, *schema;
        bool r;
        struct ucl_schema_error err;

        SV *schema_error = get_sv("Config::UCL::ucl_schema_error", 0);
        sv_setpv(schema_error, "\0");

        schema = _iterate_perl(aTHX_ schema_sv);
        if (!schema) {
            croak("aaa");
            RETVAL = NULL;
            return;
        }

        data = _iterate_perl(aTHX_ data_sv);
        if (!data) {
            croak("aaa");
            RETVAL = NULL;
            return;
        }

        // validation
        r = ucl_object_validate(schema, data, &err);
        ucl_object_unref(schema);
        ucl_object_unref(data);

        if (!r) {
            //PyErr_SetString (SchemaError, err.msg);
            sv_setpv(schema_error, err.msg);
            RETVAL = false;
        }
        else {
            RETVAL = true;
        }
    OUTPUT:
        RETVAL

MODULE = Config::UCL       PACKAGE = Config::UCL::Parser
 
struct ucl_parser *
new(char *klass, int flags)
    CODE:
	struct ucl_parser *parser = ucl_parser_new(flags);
        RETVAL = parser;
    OUTPUT:
        RETVAL

void
ucl_parser_register_variable (struct ucl_parser *parser, const char *var, const char *value);

bool
ucl_parser_set_filevars(struct ucl_parser *parser, const char *filename, bool need_expand)

bool
ucl_parser_add_string(struct ucl_parser *parser, const char *data, size_t len);

bool
ucl_parser_add_chunk_full (struct ucl_parser *parser, const unsigned char *data, size_t len, unsigned priority, enum ucl_duplicate_strategy strat, enum ucl_parse_type parse_type);

bool
ucl_parser_add_file(struct ucl_parser *parser, const char *filename);

bool
ucl_parser_add_file_full (struct ucl_parser *parser, const char *filename, unsigned priority, enum ucl_duplicate_strategy strat, enum ucl_parse_type parse_type);

void
DESTROY(struct ucl_parser *parser)
    CODE:
        ucl_parser_free(parser);

SV *
ucl_load(struct ucl_parser *parser, bool _implicit_unicode, int _ucl_string_flags)
    CODE:
        implicit_unicode = _implicit_unicode;
        ucl_string_flags = _ucl_string_flags;
        RETVAL = _load_ucl(aTHX_ parser);
    OUTPUT:
        RETVAL