/**
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <v8.h>
#include <telephony/ril.h>

#include "ctrl_server.h"
#include "logging.h"
#include "node_buffer.h"
#include "node_object_wrap.h"
#include "node_util.h"
#include "protobuf_v8.h"
#include "responses.h"
#include "status.h"
#include "util.h"
#include "worker.h"
#include "worker_v8.h"

#include "js_support.h"

//#define JS_SUPPORT_DEBUG
#ifdef  JS_SUPPORT_DEBUG

#define DBG(...) LOGD(__VA_ARGS__)

#else

#define DBG(...)

#endif

/**
 * Current Radio state
 */
RIL_RadioState gRadioState = RADIO_STATE_UNAVAILABLE;

v8::Handle<v8::Value> RadioStateGetter(v8::Local<v8::String> property,
        const v8::AccessorInfo& info) {
    return v8::Integer::New((int)gRadioState);
}

void RadioStateSetter(v8::Local<v8::String> property,
        v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
    gRadioState = RIL_RadioState(value->Int32Value());
}

// A javascript sleep for a number of milli-seconds
v8::Handle<v8::Value> MsSleep(const v8::Arguments& args) {
    if (args.Length() != 1) {
        DBG("MsSleep: expecting milli-seconds to sleep");
    } else {
        v8::Handle<v8::Value> v8MsValue(args[0]->ToObject());
        int ms = int(v8MsValue->NumberValue());
        v8::Unlocker unlocker;
        usleep(ms * 1000);
        v8::Locker locker;
    }
    return v8::Undefined();
}

// A javascript print function
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
    bool first = true;
    const int str_size = 1000;
    char* str = new char[str_size];
    int offset = 0;
    for (int i = 0; i < args.Length(); i++) {
        v8::HandleScope handle_scope;
        if (first) {
            first = false;
        } else {
            offset += snprintf(&str[offset], str_size, " ");
        }
        v8::String::Utf8Value strUtf8(args[i]);
        const char* cstr = ToCString(strUtf8);
        offset += snprintf(&str[offset], str_size, "%s", cstr);
    }
    LOGD("%s", str);
    delete [] str;
    return v8::Undefined();
}

int ReadFile(const char *fileName, char** data, size_t *length) {
    int status;
    char* buffer = NULL;
    size_t fileLength = 0;
    FILE *f;

    DBG("ReadFile E fileName=%s", fileName);

    f = fopen(fileName, "rb");
    if (f == NULL) {
        DBG("Could not fopen '%s'", fileName);
        status = STATUS_COULD_NOT_OPEN_FILE;
    } else {
        // Determine the length of the file
        fseek(f, 0, SEEK_END);
        fileLength = ftell(f);
        DBG("fileLength=%d", fileLength);
        rewind(f);

        // Read file into a buffer
        buffer = new char[fileLength+1];
        size_t readLength = fread(buffer, 1, fileLength, f);
        if (readLength != fileLength) {
            DBG("Couldn't read entire file");
            delete [] buffer;
            buffer = NULL;
            status = STATUS_COULD_NOT_READ_FILE;
        } else {
            DBG("File read");
            buffer[fileLength] = 0;
            status = STATUS_OK;
        }
        fclose(f);
    }

    if (length != NULL) {
        *length = fileLength;
    }
    *data = buffer;
    DBG("ReadFile X status=%d", status);
    return status;
}

char *CreateFileName(const v8::Arguments& args) {
    v8::String::Utf8Value fileNameUtf8Value(args[0]);
    const char* fileName = ToCString(fileNameUtf8Value);
    const char* directory = "/sdcard/data/";

    int fullPathLength = strlen(directory) + strlen(fileName) + 1;
    char * fullPath = new char[fullPathLength];
    strncpy(fullPath, directory, fullPathLength);
    strncat(fullPath, fileName, fullPathLength);
    return fullPath;
}

// A javascript read file function arg[0] = filename
v8::Handle<v8::Value> ReadFileToString(const v8::Arguments& args) {
    DBG("ReadFileToString E");
    v8::HandleScope handle_scope;
    v8::Handle<v8::Value> retValue;

    if (args.Length() < 1) {
        // No file name return Undefined
        DBG("ReadFile X no argumens");
        return v8::Undefined();
    } else {
        char *fileName = CreateFileName(args);

        char *buffer;
        int status = ReadFile(fileName, &buffer);
        if (status == 0) {
            retValue = v8::String::New(buffer);
        } else {
            retValue = v8::Undefined();
        }
    }
    DBG("ReadFileToString X");
    return retValue;
}

// A javascript read file function arg[0] = filename
v8::Handle<v8::Value> ReadFileToBuffer(const v8::Arguments& args) {
    DBG("ReadFileToBuffer E");
    v8::HandleScope handle_scope;
    v8::Handle<v8::Value> retValue;

    if (args.Length() < 1) {
        // No file name return Undefined
        DBG("ReadFileToBuffer X no argumens");
        return v8::Undefined();
    } else {
        char *fileName = CreateFileName(args);

        char *buffer;
        size_t length;
        int status = ReadFile(fileName, &buffer, &length);
        if (status == 0) {
            Buffer *buf = Buffer::New(length);
            memmove(buf->data(), buffer, length);
            retValue = buf->handle_;
        } else {
            retValue = v8::Undefined();
        }
    }
    DBG("ReadFileToBuffer X");
    return retValue;
}

void ErrorCallback(v8::Handle<v8::Message> message,
        v8::Handle<v8::Value> data) {
    LogErrorMessage(message, "");
}

// Read, compile and run a javascript file
v8::Handle<v8::Value> Include(const v8::Arguments& args) {
    DBG("Include E");
    v8::HandleScope handle_scope;
    v8::Handle<v8::Value> retValue;
    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    if (args.Length() < 1) {
        // No file name return Undefined
        DBG("Include X no argumens");
        return v8::Undefined();
    } else {
        char *fileName = CreateFileName(args);

        char *buffer;
        int status = ReadFile(fileName, &buffer);
        if (status == 0) {
            runJs(v8::Context::GetCurrent(), &try_catch, fileName, buffer);
        } else {
            retValue = v8::Undefined();
        }
    }
    DBG("Include X");
    return retValue;
}


/**
 * Create a JsContext, must be called within a HandleScope?
 */
v8::Persistent<v8::Context> makeJsContext() {
    v8::HandleScope handle_scope;
    v8::TryCatch try_catch;

    // Add a Message listner to Catch errors as they occur
    v8::V8::AddMessageListener(ErrorCallback);

    // Create a template for the global object and
    // add the function template for print to it.
    v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
    global->SetAccessor(v8::String::New("gRadioState"),
            RadioStateGetter, RadioStateSetter);
    global->Set(v8::String::New("msSleep"), v8::FunctionTemplate::New(MsSleep));
    global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print));
    global->Set(v8::String::New("readFileToBuffer"),
            v8::FunctionTemplate::New(ReadFileToBuffer));
    global->Set(v8::String::New("readFileToString"),
            v8::FunctionTemplate::New(ReadFileToString));
    global->Set(v8::String::New("sendRilRequestComplete"),
            v8::FunctionTemplate::New(SendRilRequestComplete));
    global->Set(v8::String::New("sendRilUnsolicitedResponse"),
            v8::FunctionTemplate::New(SendRilUnsolicitedResponse));
    global->Set(v8::String::New("sendCtrlRequestComplete"),
            v8::FunctionTemplate::New(SendCtrlRequestComplete));
    global->Set(v8::String::New("include"), v8::FunctionTemplate::New(Include));
    WorkerV8ObjectTemplateInit(global);
    SchemaObjectTemplateInit(global);
    Buffer::InitializeObjectTemplate(global);

    // Create context with our globals and make it the current scope
    v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);


    if (try_catch.HasCaught()) {
        DBG("makeJsContext: Exception making the context");
        ReportException(&try_catch);
        try_catch.ReThrow();
    }

    return context;
}

/**
 * Run some javascript code.
 */
void runJs(v8::Handle<v8::Context> context, v8::TryCatch *try_catch,
        const char *fileName, const char *code) {
    v8::HandleScope handle_scope;

    // Compile the source
    v8::Handle<v8::Script> script = v8::Script::Compile(
                v8::String::New(code), v8::String::New(fileName));
    if (try_catch->HasCaught()) {
        LOGE("-- Compiling the source failed");
    } else {
        // Run the resulting script
        v8::Handle<v8::Value> result = script->Run();
        if (try_catch->HasCaught()) {
            LOGE("-- Running the script failed");
        }
    }
}

void testRadioState(v8::Handle<v8::Context> context) {
    LOGD("testRadioState E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    runJs(context, &try_catch, "local-string",
        "for(i = 0; i < 10; i++) {\n"
        "  gRadioState = i;\n"
        "  print('gRadioState=' + gRadioState);\n"
        "}\n"
        "gRadioState = 1;\n"
        "print('last gRadioState=' + gRadioState);\n");
    LOGD("testRadioState X:");
}

void testMsSleep(v8::Handle<v8::Context> context) {
    LOGD("testMsSleep E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    runJs(context, &try_catch, "local-string",
        "for(i = 0; i < 10; i++) {\n"
        "  sleeptime = i * 200\n"
        "  print('msSleep ' + sleeptime);\n"
        "  msSleep(sleeptime);\n"
        "}\n");
    LOGD("testMsSleep X:");
}

void testPrint(v8::Handle<v8::Context> context) {
    LOGD("testPrint E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    runJs(context, &try_catch, "local-string", "print(\"Hello\")");
    LOGD("testPrint X:");
}

void testCompileError(v8::Handle<v8::Context> context) {
    LOGD("testCompileError E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    // +++ generate a compile time error
    runJs(context, &try_catch, "local-string", "+++");
    LOGD("testCompileError X:");
}

void testRuntimeError(v8::Handle<v8::Context> context) {
    LOGD("testRuntimeError E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    // Runtime error
    runJs(context, &try_catch, "local-string",
        "function hello() {\n"
        "  print(\"Hi there\");\n"
        "}\n"
        "helloo()");
    LOGD("testRuntimeError X:");
}

void testReadFile() {
    char *buffer;
    size_t length;
    int status;

    LOGD("testReadFile E:");

    status = ReadFile("/sdcard/data/no-file", &buffer, &length);
    LOGD("testReadFile expect status != 0, status=%d, buffer=%p, length=%d",
            status, buffer, length);

    LOGD("testReadFile X:");
}


void testReadFileToStringBuffer(v8::Handle<v8::Context> context) {
    LOGD("testReadFileToStringBuffer E:");
    v8::HandleScope handle_scope;

    v8::TryCatch try_catch;
    try_catch.SetVerbose(true);

    runJs(context, &try_catch, "local-string",
        "fileContents = readFileToString(\"mock_ril.js\");\n"
        "print(\"fileContents:\\n\" + fileContents);\n"
        "buffer = readFileToBuffer(\"ril.desc\");\n"
        "print(\"buffer.length=\" + buffer.length);\n");
    LOGD("testReadFileToStringBuffer X:");
}

void testJsSupport(v8::Handle<v8::Context> context) {
    LOGD("testJsSupport E: ********");
    testRadioState(context);
    testMsSleep(context);
    testPrint(context);
    testCompileError(context);
    testRuntimeError(context);
    testReadFile();
    testReadFileToStringBuffer(context);
    LOGD("testJsSupport X: ********\n");
}
