/*
 * 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.
 */

// The LOG_TAG should start with "RIL" so it shows up in the  radio log
#define LOG_TAG "RIL-MOCK"

#include <telephony/ril.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <alloca.h>
#include <getopt.h>
#include <sys/socket.h>
#include <cutils/sockets.h>
#include <termios.h>

#include <queue>

#include <utils/Log.h>

#include "t.pb.h"
#include <v8.h>

#define MOCK_RIL_VER_STRING "Android Mock-ril 0.1"

/**
 * Forward declarations
 */
static void onRequest (int request, void *data, size_t datalen, RIL_Token t);
static RIL_RadioState currentState();
static int onSupports (int requestCode);
static void onCancel (RIL_Token t);
static const char *getVersion();

extern const char * requestToString(int request);

/*** Static Variables ***/
static const RIL_RadioFunctions s_callbacks = {
    RIL_VERSION,
    onRequest,
    currentState,
    onSupports,
    onCancel,
    getVersion
};

static const struct RIL_Env *s_rilenv;

static RIL_RadioState sState = RADIO_STATE_UNAVAILABLE;

/**
 * Call from RIL to us to make a RIL_REQUEST
 *
 * Must be completed with a call to RIL_onRequestComplete()
 *
 * RIL_onRequestComplete() may be called from any thread, before or after
 * this function returns.
 * * Will always be called from the same thread, so returning here implies
 * that the radio is ready to process another command (whether or not
 * the previous command has completed).
 */
static void onRequest (int request, void *data, size_t datalen, RIL_Token t)
{
    LOGD("onRequest: request=%d data=%p datalen=%d token=%p",
            request, data, datalen, t);
    s_rilenv->OnRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
}

/**
 * Synchronous call from the RIL to us to return current radio state.
 * RADIO_STATE_UNAVAILABLE should be the initial state.
 */
static RIL_RadioState currentState()
{
    LOGD("currentState: sState=%d", sState);
    return sState;
}

/**
 * Call from RIL to us to find out whether a specific request code
 * is supported by this implementation.
 *
 * Return 1 for "supported" and 0 for "unsupported"
 */

static int
onSupports (int requestCode)
{
    LOGD("onSupports: nothing supported at the moment, return 0");
    return 0;
}

static void onCancel (RIL_Token t)
{
    LOGD("onCancel: ignorning");
}

static const char * getVersion(void)
{
    LOGD("getVersion: return '%s'", MOCK_RIL_VER_STRING);
    return MOCK_RIL_VER_STRING;
}

static void * mainLoop(void *param)
{

    // Test using STLport
    std::queue<void *> q;

    LOGD("before push q.size=%d", q.size());
    q.push(param);
    LOGD("after push q.size=%d", q.size());
    void *p = q.front();
    if (p == param) {
        LOGD("q.push succeeded");
    } else {
        LOGD("q.push failed");
    }
    q.pop();
    LOGD("after pop q.size=%d", q.size());

    // Test a simple protobuf
    LOGD("create T");
    T* t = new T();
    LOGD("set_id(1)");
    t->set_id(1);
    int id = t->id();
    LOGD("id=%d", id);
    delete t;

    LOGD("mainLoop E");

    for (int i=0;;i++) {
        sleep(10);
        LOGD("mainLoop: looping %d", i);
    }

    LOGD("mainLoop X");

    return NULL;
}

// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
  return *value ? *value : "<string conversion failed>";
}

// Report an exception
void LogErrorMessage(v8::Handle<v8::Message> message, const char *alternate_message) {
  v8::HandleScope handle_scope;
  if (message.IsEmpty()) {
    // V8 didn't provide any extra information about this error; just
    // print the exception.
    LOGD("%s", alternate_message);
  } else {
    v8::String::Utf8Value filename(message->GetScriptResourceName());
    const char* filename_string = ToCString(filename);
    int linenum = message->GetLineNumber();
    LOGD("file:%s line:%i", filename_string, linenum);

    // Print line of source code.
    v8::String::Utf8Value sourceline(message->GetSourceLine());
    const char* sourceline_string = ToCString(sourceline);
    LOGD("%s", sourceline_string);

    // Print location information under source line
    int start = message->GetStartColumn();
    int end = message->GetEndColumn();
    char *error_string = new char[end];
    memset(error_string, ' ', start);
    memset(&error_string[start], '^', end - start);
    error_string[end] = 0;
    LOGD("%s", error_string);
    delete [] error_string;
  }
}

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

// Report an exception
void ReportException(v8::TryCatch* try_catch) {
    v8::HandleScope handle_scope;

    v8::String::Utf8Value exception(try_catch->Exception());
    v8::Handle<v8::Message> msg = try_catch->Message();
    if (msg.IsEmpty()) {
       // Why is try_catch->Message empty?
       // is always empty on compile errors
    }
    LogErrorMessage(msg, ToCString(exception));
}

void testV8() {
    LOGD("testV8 E:");
    v8::HandleScope handle_scope;
    v8::TryCatch try_catch;

    // Catch errors as they occur as using ReportException doesn't work??
    v8::Handle<v8::Value> mydata(v8::String::New("hello"));
    v8::V8::AddMessageListener(ErrorCallback, mydata);

    // Create context and make it the current scope
    v8::Handle<v8::Context> context = v8::Context::New();
    v8::Context::Scope context_scope(context);

    // Compile the source
    v8::Handle<v8::String> source = v8::String::New("\n\n    'Hi\n\n");
    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    if (script.IsEmpty()) {
        ReportException(&try_catch);
    } else {
        // Run the resulting script
        v8::Handle<v8::Value> result = script->Run();
        if (result.IsEmpty()) {
            ReportException(&try_catch);
        } else {
            v8::String::Utf8Value result_string(result);
            LOGD("testV8 result=%s", ToCString(result_string));
        }
    }
    LOGD("testV8 X:");
}

pthread_t s_tid_mainloop;

const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
{
    int ret;
    pthread_attr_t attr;

    LOGD("RIL_Init E");

    testV8();

    s_rilenv = env;

    ret = pthread_attr_init (&attr);
    if (ret != 0) {
        LOGE("RIL_Init X: pthread_attr_init failed err=%s", strerror(ret));
        return NULL;
    }
    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (ret != 0) {
        LOGE("RIL_Init X: pthread_attr_setdetachstate failed err=%s", strerror(ret));
        return NULL;
    }
    ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
    if (ret != 0) {
        LOGE("RIL_Init X: pthread_create failed err=%s", strerror(ret));
        return NULL;
    }

    LOGD("RIL_Init X");
    return &s_callbacks;
}