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

#include "JavaScript_Env.h"

#include "PJS_Context.h"
#include "PJS_Property.h"
#include "PJS_Class.h"
#include "PJS_Types.h"
#include "PJS_Common.h"

void PJS_free_property(PJS_Property *pfunc) {
    dSP;
    if (pfunc == NULL) {
        return;
    }

    if (pfunc->getter != NULL) {
        SvREFCNT_dec(pfunc->getter);
    }

    if (pfunc->setter != NULL) {
        SvREFCNT_dec(pfunc->setter);
    }

    Safefree(pfunc);
}

void PJS_free_JSPropertySpec(JSPropertySpec *ps_list) {
    dSP;
    JSPropertySpec *ps;
    
    if (ps_list == NULL) {
        return;
    }

    for (ps = ps_list; ps->name; ps++) {
        Safefree(ps->name);
    }

    Safefree(ps_list);
}

JSBool PJS_invoke_perl_property_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
    dSP;
    PJS_Context *pcx;
    PJS_Class *pcls;
    PJS_Property *pprop;
    SV *caller;
    char *name;
    jsint slot;
    U8 invocation_mode;

    if (!JSVAL_IS_INT(id)) {
        return JS_TRUE;
    }
    
    if((pcx = PJS_GET_CONTEXT(cx)) == NULL) {
        JS_ReportError(cx, "Can't find context %d", cx);
        return JS_FALSE;
    }

    if (JS_TypeOfValue(cx, OBJECT_TO_JSVAL(obj)) == JSTYPE_OBJECT) {
        /* Called as instsance */
        JSClass *clasp = PJS_GET_CLASS(cx, obj);
        name = (char *) clasp->name;
        invocation_mode = 1;
    }
    else {
        /* Called as static */
        JSFunction *parent_jfunc = JS_ValueToFunction(cx, OBJECT_TO_JSVAL(obj));
        if (parent_jfunc == NULL) {
            JS_ReportError(cx, "Failed to extract class for static property getter");
            return JS_FALSE;
        }
        name = (char *) JS_GetFunctionName(parent_jfunc);
        invocation_mode = 0;
    }
    
    if ((pcls = PJS_GetClassByName(pcx, name)) == NULL) {
        JS_ReportError(cx, "Can't find class '%s'", name);
        return JS_FALSE;
    }

    slot = JSVAL_TO_INT(id);

    if ((pprop = PJS_get_property_by_id(pcls,  (int8) slot)) == NULL) {
        JS_ReportError(cx, "Can't find property handler");
        return JS_FALSE;
    }

    if (pprop->getter == NULL) {
        JS_ReportError(cx, "Property is write-only");
        return JS_FALSE;
    }

    if (invocation_mode) {
        caller = (SV *) JS_GetPrivate(cx, obj);
    }
    else {
        caller = newSVpv(pcls->pkg, 0);
    }

    if (perl_call_sv_with_jsvals(cx, obj, pprop->getter,
                                 caller, 0, NULL, vp) < 0) {
        return JS_FALSE;
    }

    return JS_TRUE;
}

JSBool PJS_invoke_perl_property_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
    dSP;
    PJS_Context *pcx;
    PJS_Class *pcls;
    PJS_Property *pprop;
    SV *caller;
    char *name;
    jsint slot;
    U8 invocation_mode;

    if (!JSVAL_IS_INT(id)) {
        return JS_TRUE;
    }
    
    if((pcx = PJS_GET_CONTEXT(cx)) == NULL) {
        JS_ReportError(cx, "Can't find context %d", cx);
        return JS_FALSE;
    }

    if (JS_TypeOfValue(cx, OBJECT_TO_JSVAL(obj)) == JSTYPE_OBJECT) {
        /* Called as instsance */
        JSClass *clasp = PJS_GET_CLASS(cx, obj);
        name = (char *) clasp->name;
        invocation_mode = 1;
    }
    else {
        /* Called as static */
        JSFunction *parent_jfunc = JS_ValueToFunction(cx, OBJECT_TO_JSVAL(obj));
        if (parent_jfunc == NULL) {
            JS_ReportError(cx, "Failed to extract class for static property getter");
            return JS_FALSE;
        }
        name = (char *) JS_GetFunctionName(parent_jfunc);
        invocation_mode = 0;
    }
    
    if ((pcls = PJS_GetClassByName(pcx, name)) == NULL) {
        JS_ReportError(cx, "Can't find class '%s'", name);
        return JS_FALSE;
    }

    slot = JSVAL_TO_INT(id);

    if ((pprop = PJS_get_property_by_id(pcls,  (int8) slot)) == NULL) {
        JS_ReportError(cx, "Can't find property handler");
        return JS_FALSE;
    }

    if (pprop->setter == NULL) {
        JS_ReportError(cx, "Property is read-only");
        return JS_FALSE;
    }

    if (invocation_mode) {
        caller = (SV *) JS_GetPrivate(cx, obj);
    }
    else {
        caller = newSVpv(pcls->pkg, 0);
    }

    if (perl_call_sv_with_jsvals(cx, obj, pprop->setter,
                                 caller, 1, vp, NULL) < 0) {
        return JS_FALSE;
    }

    return JS_TRUE;
}