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

#include <JavaScript.h>

#define PJS_ERROR_OPCODELILMIT_EXCEEDED "JavaScript::Error::OpcodeLimitExceeded"

struct PJS_Runtime_OpcodeCounting {
    U32 count;
    U32 limit;
};

typedef struct PJS_Runtime_OpcodeCounting PJS_Runtime_OpcodeCounting;

static JSTrapStatus opcodecounting_interrupt_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *data) {
    PJS_Runtime_OpcodeCounting *opcount = (PJS_Runtime_OpcodeCounting *) data;
    
    opcount->count++;
    
    if (opcount->limit > 0 && opcount->count > opcount->limit) {
        sv_setsv(ERRSV, newRV_inc(newSViv(opcount->limit)));
        sv_bless(ERRSV, gv_stashpvn(PJS_ERROR_OPCODELILMIT_EXCEEDED, strlen(PJS_ERROR_OPCODELILMIT_EXCEEDED), TRUE));
        return JSTRAP_ERROR;
    }
    
    return JSTRAP_CONTINUE;
}

MODULE = JavaScript::Runtime::OpcodeCounting        PACKAGE = JavaScript::Runtime::OpcodeCounting

PJS_TrapHandler *
jsr_init()
    PREINIT:
        PJS_TrapHandler            *handler;
        PJS_Runtime_OpcodeCounting *opcount;
    CODE:
        Newz(1, opcount, 1, PJS_Runtime_OpcodeCounting);
        if (opcount == NULL) {
            croak("Failed to allocate memory for PJS_Runtime_OpcodeCounting");
        }
        
        opcount->count = 0;
        opcount->limit = 0;
        
        Newz(1, handler, 1, PJS_TrapHandler);
        if (handler == NULL) {
            Safefree(opcount);
            croak("Failed to allocate memory for PJS_TrapHandler");
        }
        handler->handler = opcodecounting_interrupt_handler;
        handler->data = (void *) opcount;
        RETVAL = handler;
    OUTPUT:
        RETVAL
        
void
jsr_destroy(handler)
    PJS_TrapHandler *handler;
    CODE:
        Safefree(handler->data);
        Safefree(handler);
        
I32
jsr_get_opcount(handler)
    PJS_TrapHandler *handler;
    PREINIT:
        PJS_Runtime_OpcodeCounting *opcount;
    CODE:
        opcount = (PJS_Runtime_OpcodeCounting *) handler->data;
        RETVAL = opcount->count;
    OUTPUT:
        RETVAL
        
void
jsr_set_opcount(handler,count)
    PJS_TrapHandler *handler;
    I32         count;
    PREINIT:
        PJS_Runtime_OpcodeCounting *opcount;
    CODE:
        opcount = (PJS_Runtime_OpcodeCounting *) handler->data;
        opcount->count = count;
        
I32
jsr_get_opcount_limit(handler)
    PJS_TrapHandler *handler;
    PREINIT:
        PJS_Runtime_OpcodeCounting *opcount;
    CODE:
        opcount = (PJS_Runtime_OpcodeCounting *) handler->data;
        RETVAL = opcount->limit;
    OUTPUT:
        RETVAL
        
void
jsr_set_opcount_limit(handler,limit)
    PJS_TrapHandler *handler;
    I32         limit;
    PREINIT:
        PJS_Runtime_OpcodeCounting *opcount;
    CODE:
        opcount = (PJS_Runtime_OpcodeCounting *) handler->data;
        opcount->limit = limit;