#include "JS.h"

void propagate2JS(
    pTHX_
    PJS_Context *pcx,
    JSObject *obj
) {
    JSContext *cx = PJS_getJScx(pcx);
    if(PJS_getFlag(pcx, "ReflectExceptions")) {
	jsval rval;
	SV* cp = newSVsv(ERRSV);
	if(!PJS_ReflectPerl2JS(aTHX_ cx, obj, cp, &rval)) 
	    croak("Can't convert perl error into JSVAL");
	JS_SetPendingException(cx, rval);
	sv_setsv(ERRSV, &PL_sv_undef);            
	sv_free(cp);
    } else {
	JS_ClearPendingException(cx);
    }
}

PJS_EXTERN SV *
PJS_CallPerlMethod(
    pTHX_
    JSContext *cx,
    const char *method,
    ...
) {
    dSP;
    va_list ap;
    SV *arg, *ret;
    PJS_Context *pcx = PJS_GET_CONTEXT(cx);

    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    
    sv_setiv(save_scalar(PJS_Context_SV), PTR2IV(pcx));

    va_start(ap, method);
    while( (arg = va_arg(ap, SV*)) ) XPUSHs(arg);
    va_end(ap);
    PUTBACK;

    call_method(method, G_SCALAR | G_EVAL);
    ret = newSVsv(*PL_stack_sp--);

    FREETMPS;
    LEAVE;

    if (SvTRUE(ERRSV)) {
	sv_free(ret); // Don't want leaks
	propagate2JS(aTHX_ pcx, NULL);
	return NULL;
    }

    return sv_2mortal(ret);
}

PJS_EXTERN JSBool
PJS_Call_sv_with_jsvals_rsv(
    pTHX_
    JSContext *cx,
    JSObject *obj,
    SV *code,
    SV *caller, /* Will be disposed inside */
    uintN argc,
    jsval *argv,
    SV **rsv,
    I32 flag
) {
    dSP;
    JSBool ok = JS_TRUE;
    uintN arg;
    I32 rcount = caller ? 1 : 0;
    PJS_Context *pcx = PJS_GET_CONTEXT(cx);
    
    if(SvROK(code) && SvTYPE(SvRV(code)) == SVt_PVCV) {
        ENTER; SAVETMPS;
        PUSHMARK(SP) ;

	sv_setiv(save_scalar(PJS_Context_SV), PTR2IV(pcx));
	
	EXTEND(SP, argc + rcount);
	PUTBACK;
        
	/* From here we are working with the global stack,
	 * a) at PUSH time we can fail, so we need to abort the call
	 * b) Want to avoid copying local <=> global SP at every single PUSH
	 *
	 * Before 'call_sv', rcount is the number of SVs pushed so far
	 */
        if(caller) *++PL_stack_sp = sv_2mortal(caller);

	if(argv && !(flag & G_NOARGS)) {
	    /* HACK: We use G_NOARGS as a guard against use argv[-1] to get This.
	     * Needed for the use in PJS_invoke_perl_property_setter where given
	     * argc is faked
	     */
	    SV *This;
	    ok = PJS_ReflectJS2Perl(aTHX_ cx, argv[-1], &This, 0);
	    if(ok) sv_setsv(save_scalar(PJS_This), sv_2mortal(This));
	    else goto forget;
	}
	else flag &= ~G_NOARGS;

        for(arg = 0; arg < argc; arg++) {
            SV *sv;
            ok = PJS_ReflectJS2Perl(aTHX_ cx, argv[arg], &sv, 1);
            if(!ok) {
		rcount += arg;
                goto forget;
	    }
	    *++PL_stack_sp = sv_2mortal(sv);
        }
        
        rcount = call_sv(code, flag | G_EVAL);

	if(rsv) {
	    if(flag == G_SCALAR || rcount == 1)
		*rsv = SvREFCNT_inc_simple_NN(*PL_stack_sp);
	    else
		*rsv = newRV((SV *)av_make(rcount, PL_stack_sp-rcount+1));

	    SAVEMORTALIZESV(*rsv);
	}

	forget:
	PL_stack_sp -= rcount;
        FREETMPS; LEAVE;

        if(ok && SvTRUE(ERRSV)) {
	    propagate2JS(aTHX_ pcx, obj);
	    ok = JS_FALSE;
        }
    }
    else croak("Not a coderef");
    return ok;
}

PJS_EXTERN JSBool
PJS_Call_sv_with_jsvals(
    pTHX_
    JSContext *cx,
    JSObject *obj,
    SV *code,
    SV *caller,
    uintN argc,
    jsval *argv,
    jsval *rval,
    I32 flag
) {
    SV *rsv;
    ENTER; SAVETMPS;
    JSBool ok = PJS_Call_sv_with_jsvals_rsv(aTHX_ cx, obj, code, caller, argc, argv,
                                             rval ? &rsv : NULL, flag);
    
    if(rval && ok) ok = PJS_ReflectPerl2JS(aTHX_ cx, obj, rsv, rval);
    FREETMPS; LEAVE;
    return ok;
}

PJS_EXTERN JSBool
PJS_Call_js_function(
    pTHX_
    JSContext *cx,
    JSObject *gobj,
    jsval func,
    AV *av,
    jsval *rval
) {
    jsval *arg_list;
    SV *val;
    int arg_count, i;
    JSBool res;
    
    arg_count = av_len(av);

    Newz(1, arg_list, arg_count + 1, jsval);
    if(!arg_list) {
	JS_ReportOutOfMemory(cx);
	return JS_FALSE;
    }

    for(i = 0; i <= arg_count; i++) {
        val = *av_fetch(av, i, 0);

        if (!PJS_ReflectPerl2JS(aTHX_ cx, gobj, val, &(arg_list[i]))) {
            Safefree(arg_list);
            croak("Can't convert argument number %d to jsval", i);
        }
    }
    res = JS_CallFunctionValue(cx, gobj, func, i, arg_list, rval);
    Safefree(arg_list);
    return res;
}
