#include "pugsembed.h"
extern int _P5EMBED_INIT;

IV
pugs_tied ( SV *sv )
{
    const MAGIC *mg;
    const char how = (SvTYPE(sv) == SVt_PVHV || SvTYPE(sv) == SVt_PVAV)
		? PERL_MAGIC_tied : PERL_MAGIC_tiedscalar;

    if ((mg = SvTIED_mg(sv, how))) {
	SV *osv = SvTIED_obj(sv, mg);
        if (SvROK(osv)) {
            const char *pv = sv_reftype(SvRV(osv),TRUE);
            if (strncmp(pv, "pugs::", 6) == 0) {
                SV *derefSV = newSVpv("DEREF", 0);
                SV **rv;
                SV *stack[0];
                stack[0] = NULL;
                rv = perl5_apply(derefSV, osv, stack, NULL, G_SCALAR);
                if ((rv[0] == NULL) && SvROK(rv[1])) {
                    return SvIV((SV*)SvRV(rv[1]));
                }
            }
        }
    }

    return 0;
}

Val *
pugs_SvToVal ( SV *sv )
{
    svtype ty = SvTYPE(sv);
    IV tmp = 0;

    if (sv_isa(sv, "pugs")) {
        tmp = SvIV((SV*)SvRV(sv));
        return ((Val *)tmp);
    }
    else if (SvROK(sv)) {
        if (tmp = pugs_tied(SvRV(sv))) {
            return ((Val *)tmp);
        }
        else {
            return pugs_MkSvRef(sv);
        }
    }
    else if (ty == SVt_NULL) {
        return pugs_UndefVal();
    }
    else if (SvNIOKp(sv) && (sv_len(sv) != 0)) {
        if (SvNOK(sv)) {
            return pugs_NvToVal(SvNVX(sv));
        }
        else {
            return pugs_IvToVal(SvIVX(sv));
        }
    }
    else if (SvPOKp(sv)) {
        STRLEN len = sv_len(sv);
        if (SvUTF8(sv)) {
            return pugs_PvnToValUTF8(SvPV_nolen(sv), (int)len);
        }
        else {
            return pugs_PvnToVal(SvPV_nolen(sv), (int)len);
        }
    }
    else {
        return pugs_MkSvRef(sv);
    }
}

SV *
pugs_MkValRef ( Val *val, char *typeStr )
{
    SV *sv = newSV(0);
    Val *isa[2];
    SV *stack[8];

    sv_setref_pv(sv, "pugs", val);

    if (!_P5EMBED_INIT) {
        fprintf(stderr, "MkValRef called before perl_init.\n");
    }

    isa[0] = NULL;

    /* fprintf(stderr, "query the type: got %s\n", typeStr); */

    if ((typeStr == NULL) || (*typeStr == '\0')) {
        SV *typeSV = pugs_Apply(pugs_PvnToVal("&WHAT", 5), val, isa, G_SCALAR);
        typeStr = SvPV_nolen(typeSV);
    }

    if ((typeStr != NULL) && (*typeStr != '\0')) {
        SV **rv;
        SV *typeSV = newSVpv(typeStr, 0);
        stack[0] = typeSV;
        stack[1] = NULL;
        rv = perl5_apply(newSVpv("can", 0), newSVpv("pugs::guts", 0), stack, NULL, G_SCALAR);
        if ((rv[0] == NULL) && SvTRUE( rv[1] )) {
            stack[0] = sv;
            rv = perl5_apply(typeSV, newSVpv("pugs::guts", 0), stack, NULL, G_SCALAR);
            if (rv[0] == NULL) {
                /* no error happened -- used the tied obj */
                sv = rv[1];
            }
            else {
                fprintf(stderr, "error in pugs::guts application on type: %s\n", typeStr);
                sv_dump(rv[0]);
            }
        }
        else {
            /* for scalar ref, should still turn into tied one */
#if PERL5_EMBED_DEBUG
            fprintf(stderr, "unknown type\n");
#endif
        }
    }

    return (sv);
}

Val *pugs_getenv ()
{
    SV** rv = hv_fetch(PL_modglobal, "PugsEnv", 7, 0);
    if (rv == NULL) {
        Perl_croak(aTHX_ "PugsEnv uninitialized; please call pugs_setenv() first. (hate software so much.)");
    }
    IV tmp = SvIV((SV*)SvRV(*rv));
    return ((Val *)tmp);
}

void pugs_setenv ( Val *env )
{
    if (env == NULL) { return; }

    SV *sv = newSV(0);
    sv_setref_pv(sv, "pugs", env);
    hv_store(PL_modglobal, "PugsEnv", 7, sv, 0);
}
