#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <jsapi.h>
#include "encrypt.h"

static void js_error_handler(JSContext *ctx, const char *msg, JSErrorReport *er);
static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret);
JSClass globalObjectClass = {
    "global",
    0,

    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    JS_FinalizeStub,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};


char *encryptData(EncryptionTypes *type, const char *data){
    JSRuntime *rt=NULL;
    JSContext *cx=NULL;
    jsval *parameters=NULL;
    jsval ret;
    JSString *stdinData=NULL;
    char *stdinDataBuffer;


    rt = JS_NewRuntime(1073741824);
    if (rt == NULL){
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    cx = JS_NewContext(rt, 8192);
    if (cx == NULL){
        JS_DestroyRuntime(rt);
        printf("Failed to start Spidermonkey, cannot perform encryption.\n");
        return strdup(data);
    }

    JS_SetErrorReporter(cx, js_error_handler);

     /* Spidermonkey manages the memory space of it's strings, so we need to duplicate the passed string. 
      * We must allocate the memory using Spidermonkey's JS_malloc function though, so that it is safe for 
      * Spidermonkey to manage it.
      */
    stdinDataBuffer = JS_malloc(cx, strlen(data)+1);
    memset(stdinDataBuffer, 0, strlen(data)+1);
    memcpy(stdinDataBuffer, data, strlen(data));

    stdinData = JS_NewString(cx, stdinDataBuffer, strlen(stdinDataBuffer));
    
    parameters=JS_malloc(cx, sizeof(*parameters));
    parameters[0] = STRING_TO_JSVAL(stdinData);

    /* Execute the javascript file. */
    if (executeJSFunction(cx, type->filePath, type->functionName, 1, parameters, &ret) == JS_TRUE){
        JSString *retString = JS_ValueToString(cx, ret);
        char *encryptedString = JS_GetStringBytes(retString);
        char *returnedString;


        /* We duplicate the string returned by JS_GetStringBytes because when we clean up the 
         * Runtime and Context the string pointed to by encryptedString will be cleaned up and 
         * invalid.  Therefore we need our own copy if we are going to use it after the cleanup.
         */
        returnedString = strdup(encryptedString);

        JS_free(cx, parameters);
        JS_DestroyContext(cx);
        JS_DestroyRuntime(rt);
        return returnedString;
    }

    /* Cleanup Spidermonkey */
    JS_free(cx, parameters);
    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    return strdup(data);
}


static JSBool executeJSFunction(JSContext *cx, const char *file, const char *function, uintN argc, jsval *argv, jsval *ret){
    char *expandedFilename = expandTilda(file);
    struct stat fileDetails;
    JSObject *global=NULL;
    JSScript *script=NULL;


    if (stat(expandedFilename, &fileDetails) == -1){
        printf("Encryption code file '%s' does not exist.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }


    /* Create a global object */
    global = JS_NewObject(cx, &globalObjectClass, NULL, NULL);
    if (global == NULL){
        printf("Execution of encryptor failed: Cannot create global object.\n");
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_InitStandardClasses(cx, global);
    
    script = JS_CompileFile(cx, global, expandedFilename);
    if (script == NULL){
        printf("Execution of encryptor failed: Cannot compile file '%s'.\n", expandedFilename);
        free(expandedFilename);
        return JS_FALSE;
    }

    if (JS_ExecuteScript(cx, global, script, ret) == JS_FALSE){
        printf("Execution of encryptor failed: Cannot execute script file '%s'\n", expandedFilename);
        JS_DestroyScript(cx, script);
        free(expandedFilename);
        return JS_FALSE;
    }

    if (JS_CallFunctionName(cx, global, function, argc, argv, ret) == JS_FALSE){
        printf("Execution of encryptor failed: Cannot execute function '%s' with string parameter.\n", function);
        JS_DestroyScript(cx, script);
        free(expandedFilename);
        return JS_FALSE;
    }

    JS_DestroyScript(cx, script);
    
    free(expandedFilename);
    return JS_TRUE;
}

static void js_error_handler(JSContext *ctx, const char *msg, JSErrorReport *er){
    char *pointer=NULL;
    char *line=NULL;
    int len;

    if (er->linebuf != NULL){
        len = er->tokenptr - er->linebuf + 1;
        pointer = malloc(len);
        memset(pointer, '-', len);
        pointer[len-1]='\0';
        pointer[len-2]='^';

        len = strlen(er->linebuf)+1;
        line = malloc(len);
        strncpy(line, er->linebuf, len);
        line[len-1] = '\0';
    }
    else {
        len=0;
        pointer = malloc(1);
        line = malloc(1);
        pointer[0]='\0';
        line[0] = '\0';
    }

    while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n')){ line[len-1]='\0'; len--; }

    printf("JS Error: %s\nFile: %s:%u\n", msg, er->filename, er->lineno);
    if (line[0]){
      printf("%s\n%s\n", line, pointer);
    }

    free(pointer);
    free(line);
}
