#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "JavaScript.h"

#ifndef FUN_OBJECT(fun)
#define FUN_OBJECT(fun) (jsval)(fun->object)
#endif

typedef PJS_PerlArray * JavaScript__PerlArray;
typedef PJS_PerlHash *  JavaScript__PerlHash;
typedef PJS_PerlSub *   JavaScript__PerlSub;
typedef PJS_Class *     JavaScript__PerlClass;
typedef PJS_Function *  JavaScript__PerlFunction;
typedef PJS_Context *   JavaScript__Context;

MODULE = JavaScript     PACKAGE = JavaScript
PROTOTYPES: DISABLE

char *
js_get_engine_version()
    CODE:
        RETVAL = (char *) JS_GetImplementationVersion();
    OUTPUT:
        RETVAL

SV*
js_does_support_utf8()
    CODE:
#ifdef JS_C_STRINGS_ARE_UTF8
        RETVAL = &PL_sv_yes;
#else
        RETVAL = &PL_sv_no;
#endif
    OUTPUT:
        RETVAL

SV*
js_does_support_e4x()
    CODE:
#ifdef JS_ENABLE_E4X
        RETVAL = &PL_sv_yes;
#else
        RETVAL = &PL_sv_no;
#endif
    OUTPUT:
        RETVAL

SV*
js_does_support_threading()
    CODE:
#ifdef JS_THREADING
        RETVAL = &PL_sv_yes;
#else
        RETVAL = &PL_sv_no;
#endif
    OUTPUT:
        RETVAL

MODULE = JavaScript     PACKAGE = JavaScript::Runtime

PJS_Runtime *
jsr_create(maxbytes)
    int maxbytes
    PREINIT:
        PJS_Runtime *rt;
    CODE:
        rt = PJS_CreateRuntime(maxbytes);
        RETVAL = rt;
    OUTPUT:
        RETVAL

void
jsr_destroy(rt)
    PJS_Runtime *rt
    CODE:
        PJS_DestroyRuntime(rt);
        
void
jsr_add_interrupt_handler(rt,handler)
    PJS_Runtime *rt;
    PJS_TrapHandler *handler;
    CODE:
        PJS_AddTrapHandler(rt, handler);

void
jsr_remove_interrupt_handler(rt,handler)
    PJS_Runtime *rt;
    PJS_TrapHandler *handler;
    CODE:
        PJS_RemoveTrapHandler(rt, handler);
    
PJS_TrapHandler *
jsr_init_perl_interrupt_handler(cb)
    SV *cb;
    PREINIT:
        PJS_TrapHandler *handler;
    CODE:
        Newz(1, handler, 1, PJS_TrapHandler);
        handler->handler = PJS_perl_trap_handler;
        handler->data = (SV *) SvREFCNT_inc(cb);
        RETVAL = handler;
    OUTPUT:
        RETVAL
        
void
jsr_destroy_perl_interrupt_handler(handler)
    PJS_TrapHandler *handler;
    CODE:
        SvREFCNT_dec(handler->data);
        Safefree(handler);
    
MODULE = JavaScript     PACKAGE = JavaScript::Context

JavaScript::Context 
jsc_create(rt)
    PJS_Runtime *rt;
    CODE:
        RETVAL = PJS_CreateContext(rt);
    OUTPUT:
        RETVAL

IV
jsc_ptr(cx)
    JavaScript::Context cx;
    CODE:
        RETVAL = (IV) cx;
    OUTPUT:
        RETVAL
        
int
jsc_destroy(cx)
    JavaScript::Context cx;
    CODE:
        PJS_DestroyContext(cx);
        RETVAL = 0;
    OUTPUT:
        RETVAL

const char *
jsc_get_version(cx)
    JavaScript::Context cx;
    CODE:
        RETVAL = JS_VersionToString(JS_GetVersion(PJS_GetJSContext(cx)));
    OUTPUT:
        RETVAL

void
jsc_set_version(cx, version)
    JavaScript::Context cx;
    const char *version;
    CODE:
        JS_SetVersion(PJS_GetJSContext(cx), JS_StringToVersion(version));
        
void
jsc_set_branch_handler(cx, handler)
    JavaScript::Context cx;
    SV *handler;
    CODE:
        if (!SvOK(handler)) {
            /* Remove handler */
            if (cx->branch_handler != NULL) {
                SvREFCNT_dec(cx->branch_handler);
            }

            cx->branch_handler = NULL;
            JS_SetBranchCallback(PJS_GetJSContext(cx), NULL);
        }
        else if (SvROK(handler) && SvTYPE(SvRV(handler)) == SVt_PVCV) {
            if (cx->branch_handler != NULL) {
                SvREFCNT_dec(cx->branch_handler);
            }

            cx->branch_handler = SvREFCNT_inc(handler);
            JS_SetBranchCallback(PJS_GetJSContext(cx), PJS_branch_handler);
        }

void
jsc_bind_function(cx, name, callback)
    JavaScript::Context cx;
    char *name;
    SV *callback;
    CODE:
        PJS_DefineFunction(cx, name, callback);

void
jsc_bind_class(cx, name, pkg, cons, fs, static_fs, ps, static_ps, flags)
    JavaScript::Context cx;
    char *name;
    char *pkg;
    SV *cons;
    HV *fs;
    HV *static_fs;
    HV *ps;
    HV *static_ps;
    U32 flags;
    CODE:
        PJS_bind_class(cx, name, pkg, cons, fs, static_fs, ps, static_ps, flags);

int
jsc_bind_value(cx, parent, name, object)
    JavaScript::Context     cx;
    char                    *parent;
    char                    *name;
    SV                      *object;
    PREINIT:
        jsval val, pval;
        JSObject *gobj, *pobj;
    CODE:
        gobj = JS_GetGlobalObject(PJS_GetJSContext(cx));

        if (strlen(parent)) {
            JS_EvaluateScript(PJS_GetJSContext(cx), gobj, parent, strlen(parent), "", 1, &pval);
            pobj = JSVAL_TO_OBJECT(pval);
        }
        else {
            pobj = JS_GetGlobalObject(PJS_GetJSContext(cx));
        }

        if (PJS_ConvertPerlToJSType(PJS_GetJSContext(cx), NULL, pobj, object, &val) == JS_FALSE) {
            val = JSVAL_VOID;
            XSRETURN_UNDEF;
        }
        if (JS_SetProperty(PJS_GetJSContext(cx), pobj, name, &val) == JS_FALSE) {
            XSRETURN_UNDEF;
        }
        RETVAL = val;
    OUTPUT:
        RETVAL

void
jsc_set_pending_exception(cx, exception)
    JavaScript::Context cx;
    SV                  *exception;
    PREINIT:
        jsval       js_exception;
        JSObject    *pobj;
    CODE:
        sv_setsv(ERRSV, &PL_sv_undef);
        JS_ClearPendingException(PJS_GetJSContext(cx));
        pobj = JS_GetGlobalObject(PJS_GetJSContext(cx));

        if(PJS_ConvertPerlToJSType(PJS_GetJSContext(cx), NULL, pobj, exception, &js_exception) == JS_FALSE) {
            js_exception = JSVAL_VOID;
            XSRETURN_UNDEF;
        }

        JS_SetPendingException(PJS_GetJSContext(cx), js_exception);

void
jsc_unbind_value(cx, parent, name)
    JavaScript::Context cx;
    char                *parent;
    char                *name;
    PREINIT:
        jsval val, pval;
        JSObject *gobj, *pobj;
    CODE:
        gobj = JS_GetGlobalObject(PJS_GetJSContext(cx));

        if (strlen(parent)) {
            JS_EvaluateScript(PJS_GetJSContext(cx), gobj, parent, strlen(parent), "", 1, &pval);
            pobj = JSVAL_TO_OBJECT(pval);
        }
        else {
            pobj = JS_GetGlobalObject(PJS_GetJSContext(cx));
        }
        /* TODO: Get property first and if it's an object decrease its refcount 
        if (JS_GetProperty(PJS_GetJSContext(cx), pobj, name, &val) == JS_FALSE) {
            croak("No property '%s' exists", name);
        }
        */
        
        if (JS_DeleteProperty(PJS_GetJSContext(cx), pobj, name) == JS_FALSE) {
            croak("Failed to unbind %s", name);
        }

jsval 
jsc_eval(cx, source, name)
    JavaScript::Context cx;
    char *source;
    char *name;
    PREINIT:
        jsval rval;
        JSContext *jcx;
        JSObject *gobj;
        JSScript *script;
        JSBool ok;
    CODE:
        sv_setsv(ERRSV, &PL_sv_undef);

        jcx = PJS_GetJSContext(cx);
        gobj = JS_GetGlobalObject(jcx);
#ifndef JSOPTION_DONT_REPORT_UNCAUGHT
        script = JS_CompileScript(jcx, gobj, source, strlen(source), name, 1);
        if (script == NULL) {
            PJS_report_exception(cx);
            XSRETURN_UNDEF;
        }
        ok = JS_ExecuteScript(jcx, gobj, script, &rval);

        if (ok == JS_FALSE) {
            PJS_report_exception(cx);
        }
        JS_DestroyScript(jcx, script);
#else
        ok = JS_EvaluateScript(jcx, gobj, source, strlen(source), name, 1, &rval);
        if (ok == JS_FALSE || JS_IsExceptionPending(jcx) == JS_TRUE) {
            PJS_report_exception(cx);
        }
        else {
            sv_setsv(ERRSV, &PL_sv_undef);
        }
#endif
        if (ok == JS_FALSE) {
            /* We must check ERRSV here because our trap_handler
               might have thrown the exception causing abort */
            XSRETURN_UNDEF;
        }
 
        RETVAL = rval;
    OUTPUT:
        RETVAL

void
jsc_free_root(cx, root)
    JavaScript::Context cx;
    SV *root;
    PREINIT:
         jsval *x;
    CODE:
         x = INT2PTR(jsval *, SvIV(root));
         JS_RemoveRoot(PJS_GetJSContext(cx), x);

jsval
jsc_call(cx, function, args)
    JavaScript::Context cx;
    SV *function;
    SV *args;
    PREINIT:
        jsval rval;
        jsval fval;
        char *name;
        STRLEN len;
        IV tmp;
        JSFunction *func;
    CODE:
        if (sv_isobject(function) && sv_derived_from(function, PJS_FUNCTION_PACKAGE)) {
            tmp = SvIV((SV*)SvRV(PJS_call_perl_method("content", function, NULL)));
            func = INT2PTR(JSFunction *, tmp);

            if (PJS_call_javascript_function(cx, FUN_OBJECT(func), args, &rval) == JS_FALSE) {
                /* Exception was thrown */
                XSRETURN_UNDEF;
            }
        }
        else {
            name = SvPV(function, len);

            if (JS_GetProperty(PJS_GetJSContext(cx), JS_GetGlobalObject(PJS_GetJSContext(cx)), name, &fval) == JS_FALSE) {
                croak("No function named '%s' exists", name);
            }
            
            if(JSVAL_IS_VOID(fval) || JSVAL_IS_NULL(fval)) {
                croak("Undefined subroutine %s called", name);
            }
            else if (JS_ValueToFunction(PJS_GetJSContext(cx), fval) != NULL) {
                if (PJS_call_javascript_function(cx, fval, args, &rval) == JS_FALSE) {
                    /* Exception was thrown */
                    XSRETURN_UNDEF;
                }
            }
            else {
                croak("Undefined subroutine %s called", name);
            }
        }

        RETVAL = rval;
    OUTPUT:
        RETVAL

SV *
jsc_call_in_context( cx, afunc, args, rcx, class )
    JavaScript::Context cx;
    SV *afunc
    SV *args;
    SV *rcx;
    char *class;
    PREINIT:
        jsval rval;
        jsval aval;
        JSFunction *func;
        int av_length;
        jsval *arg_list;
        jsval context;
        int cnt;
        AV *av;
        SV *val, *value;
        IV tmp;
        JSObject *jsobj;
    CODE:
        tmp = SvIV((SV *) SvRV(PJS_call_perl_method("content", afunc, NULL)));
        func = INT2PTR(JSFunction *,tmp);
        av = (AV *) SvRV(args);
        av_length = av_len(av);
        Newz(1, arg_list, av_length + 1, jsval);
        for(cnt = av_length + 1; cnt > 0; cnt--) {
            val = *av_fetch(av, cnt-1, 0);
            if (PJS_ConvertPerlToJSType(PJS_GetJSContext(cx), NULL, JS_GetGlobalObject(PJS_GetJSContext(cx)), val, &(arg_list[cnt - 1])) == JS_FALSE) {
                croak("cannot convert argument %i to JSVALs", cnt);
            }
        }
        if (PJS_ConvertPerlToJSType(PJS_GetJSContext(cx), NULL, JS_GetGlobalObject(PJS_GetJSContext(cx)), rcx, &context) == JS_FALSE) {
            croak("cannot convert JS context to JSVAL");
        }
        jsobj = JSVAL_TO_OBJECT(context);

        if (strlen(class) > 0) {
            if( JS_GetProperty(PJS_GetJSContext(cx), JS_GetGlobalObject(PJS_GetJSContext(cx)), class, &aval) == JS_FALSE ) {
                croak("cannot get property %s",class);
                Safefree(arg_list);
                XSRETURN_UNDEF;
            }
            JS_SetPrototype(PJS_GetJSContext(cx), jsobj, JSVAL_TO_OBJECT(aval));
        }
        if (!JS_CallFunction(PJS_GetJSContext(cx), jsobj, func, av_length+1, arg_list, &rval)) {
            fprintf(stderr, "error in call\n");
            Safefree(arg_list);
            XSRETURN_UNDEF;
        }
        value = newSViv(0);
        JSVALToSV(PJS_GetJSContext(cx), NULL, rval, &value);
        RETVAL = value;
        Safefree(arg_list);
    OUTPUT:
        RETVAL

int
jsc_can(cx, func_name)
    JavaScript::Context cx;
    char *func_name;
    PREINIT:
        jsval val;
        JSObject *object;
    CODE:
        RETVAL = 0;

        if (JS_GetProperty(PJS_GetJSContext(cx), JS_GetGlobalObject(PJS_GetJSContext(cx)), func_name, &val)) {
            if (JSVAL_IS_OBJECT(val)) {
                JS_ValueToObject(PJS_GetJSContext(cx), val, &object);
                if (strcmp(OBJ_GET_CLASS(PJS_GetJSContext(cx), object)->name, "Function") == 0 &&
                    JS_ValueToFunction(PJS_GetJSContext(cx), val) != NULL) {
                    RETVAL = 1;
                }
            }
        }
    OUTPUT:
        RETVAL

U32
jsc_get_options(cx)
    JavaScript::Context cx;
    CODE:
        RETVAL = JS_GetOptions(cx->cx);
    OUTPUT:
        RETVAL
    
void
jsc_toggle_options(cx, options)
    JavaScript::Context cx;
    U32         options;
    CODE:
        JS_ToggleOptions(cx->cx, options);
    
MODULE = JavaScript     PACKAGE = JavaScript::Script

jsval
jss_execute(psc)
    PJS_Script *psc;
    PREINIT:
        PJS_Context *cx;
        jsval rval;
    CODE:
        cx = psc->cx;
        if(!JS_ExecuteScript(PJS_GetJSContext(cx), JS_GetGlobalObject(PJS_GetJSContext(cx)), psc->script, &rval)) {
            XSRETURN_UNDEF;
        }
        RETVAL = rval;
    OUTPUT:
        RETVAL

PJS_Script *
jss_compile(cx, source)
    JavaScript::Context cx;
    char *source;
    PREINIT:
        PJS_Script *psc;
        uintN line = 0; /* May be uninitialized by some compilers */
    CODE:
        Newz(1, psc, 1, PJS_Script);
        if(psc == NULL) {
            croak("Failed to allocate memory for PJS_Script");
        }

        psc->cx = cx;
        psc->script = JS_CompileScript(PJS_GetJSContext(cx), JS_GetGlobalObject(PJS_GetJSContext(cx)), source, strlen(source), "Perl", line);

        if(psc->script == NULL) {
            Safefree(psc);
            XSRETURN_UNDEF;
        }

        RETVAL = psc;
    OUTPUT:
        RETVAL

MODULE = JavaScript     PACKAGE = JavaScript::PerlArray

JavaScript::PerlArray
new(pkg)
    const char *pkg;
    PREINIT:
        PJS_PerlArray *array;
    CODE:
        array = PJS_NewPerlArray();
        RETVAL = array;
    OUTPUT:
        RETVAL

AV *
get_ref(obj)
    JavaScript::PerlArray obj;
    CODE:
        RETVAL = obj->av;
    OUTPUT:
        RETVAL

void
DESTROY(obj)
    JavaScript::PerlArray obj;
    CODE:
        if (obj->av != NULL) {
            SvREFCNT_dec(obj->av);
        }
        obj->av = NULL;
        Safefree(obj);

MODULE = JavaScript     PACKAGE = JavaScript::PerlHash

JavaScript::PerlHash
new(pkg)
    const char *pkg;
    PREINIT:
        PJS_PerlHash *hash;
    CODE:
        hash = PJS_NewPerlHash();
        RETVAL = hash;
    OUTPUT:
        RETVAL

HV *
get_ref(obj)
    JavaScript::PerlHash obj;
    CODE:
        RETVAL = obj->hv;
    OUTPUT:
        RETVAL

void
DESTROY(obj)
    JavaScript::PerlHash obj;
    CODE:
        if (obj->hv != NULL) {
            SvREFCNT_dec(obj->hv);
        }
        obj->hv = NULL;
        Safefree(obj);

MODULE = JavaScript     PACKAGE = JavaScript::PerlSub

void
DESTROY(obj)
    JavaScript::PerlSub obj;
    CODE:
      Safefree(obj);

MODULE = JavaScript     PACKAGE = JavaScript::PerlClass

void
DESTROY(cls)
    JavaScript::PerlClass cls;
    CODE:
        PJS_free_class(cls);


MODULE = JavaScript     PACKAGE = JavaScript::PerlFunction

void
DESTROY(func)
    JavaScript::PerlFunction func;
    CODE:
        PJS_DestroyFunction(func);