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

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


#include "ril.pb.h"

#include "logging.h"
#include "js_support.h"
#include "mock_ril.h"
#include "node_buffer.h"
#include "node_object_wrap.h"
#include "node_util.h"
#include "protobuf_v8.h"
#include "status.h"
#include "util.h"
#include "worker.h"

#include "requests.h"

//#define REQUESTS_DEBUG
#ifdef  REQUESTS_DEBUG

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

#else

#define DBG(...)

#endif


/**
 * Request has no data so create an empty Buffer
 */
int ReqWithNoData(Buffer **pBuffer,
        const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    static Buffer *emptyBuffer = Buffer::New(0L);

    DBG("ReqWithNoData E");
    *pBuffer = emptyBuffer;
    status = STATUS_OK;

    DBG("ReqWithNoData X status=%d", status);
    return status;
}

/**
 * request for RIL_REQUEST_ENTER_SIM_PIN  // 2
 */
int ReqEnterSimPin(Buffer **pBuffer,
        const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;

    DBG("ReqEnterSimPin E");
    if (datalen < sizeof(int)) {
        LOGE("ReqEnterSimPin: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqEnterSimPin *req = new ril_proto::ReqEnterSimPin();
        DBG("ReqEnterSimPin: pin = %s", ((const char **)data)[0]);
        req->set_pin((((char **)data)[0]));
        buffer = Buffer::New(req->ByteSize());
        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
    }
    DBG("ReqEnterSimPin X status=%d", status);
    return status;
}

/**
 * request for RIL_REQUEST_DIAL  // 10
 */
int ReqDial(Buffer **pBuffer,
            const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;

    DBG("ReqDial E");
    DBG("data=%p datalen=%d t=%p", data, datalen, t);

    if (datalen < sizeof(int)) {
        LOGE("ReqDial: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqDial *req = new ril_proto::ReqDial();

        // cast the data to RIL_Dial
        RIL_Dial *rilDial = (RIL_Dial *)data;
        DBG("ReqDial: rilDial->address =%s, rilDial->clir=%d", rilDial->address, rilDial->clir);

        req->set_address(rilDial->address);
        req->set_clir(rilDial->clir);
        ril_proto::RilUusInfo *uusInfo = (ril_proto::RilUusInfo *)(&(req->uus_info()));

        if (rilDial->uusInfo != NULL) {
            DBG("ReqDial: print uusInfo:");
            DBG("rilDial->uusInfo->uusType = %d, "
                "rilDial->uusInfo->uusDcs =%d, "
                "rilDial->uusInfo->uusLength=%d, "
                "rilDial->uusInfo->uusData = %s",
                rilDial->uusInfo->uusType,
                rilDial->uusInfo->uusDcs,
                rilDial->uusInfo->uusLength,
                rilDial->uusInfo->uusData);

            uusInfo->set_uus_type((ril_proto::RilUusType)rilDial->uusInfo->uusType);
            uusInfo->set_uus_dcs((ril_proto::RilUusDcs)rilDial->uusInfo->uusDcs);
            uusInfo->set_uus_length(rilDial->uusInfo->uusLength);
            uusInfo->set_uus_data(rilDial->uusInfo->uusData);
        } else {
            DBG("uusInfo is NULL");
        }

        DBG("ReqDial: after set the request");
        DBG("req->ByetSize=%d", req->ByteSize());
        buffer = Buffer::New(req->ByteSize());
        DBG("buffer size=%d", buffer->length());

        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
        DBG("ReqDial X, buffer->length()=%d", buffer->length());
    }
    DBG("ReqDial X status = %d", status);
    return status;
}

/**
 * request for RIL_REQUEST_HANGUP    // 12
 */
int ReqHangUp(Buffer **pBuffer,
        const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;

    DBG("ReqHangUp E");
    if (datalen < sizeof(int)) {
        LOGE("ReqHangUp: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqHangUp *req = new ril_proto::ReqHangUp();
        DBG("ReqHangUp: connection_index=%d", ((int *)data)[0]);
        req->set_connection_index(((int *)data)[0]);
        buffer = Buffer::New(req->ByteSize());
        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
    }
    DBG("ReqHangUp X status=%d", status);
    return status;
}

/**
 * request for RIL_REQUEST_SEPARATE_CONNECTION    // 52
 */
int ReqSeparateConnection (Buffer **pBuffer,
                           const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;
    v8::HandleScope handle_scope;

    DBG("ReqSeparateConnection E");
    if (datalen < sizeof(int)) {
        LOGE("ReqSetMute: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqSeparateConnection *req = new ril_proto::ReqSeparateConnection();
        DBG("ReqSeparateConnection: index=%d", ((int *)data)[0]);
        req->set_index(((int *)data)[0]);
        DBG("ReqSeparateConnection: req->ByetSize=%d", req->ByteSize());
        buffer = Buffer::New(req->ByteSize());
        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
    }
    DBG("ReqSeparateConnection X status=%d", status);
    return status;
}

/**
 * request for RIL_REQUEST_SET_MUTE      // 53
 */
int ReqSetMute(Buffer **pBuffer,
               const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;
    v8::HandleScope handle_scope;

    DBG("ReqSetMute E");
    if (datalen < sizeof(int)) {
        LOGE("ReqSetMute: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqSetMute *req = new ril_proto::ReqSetMute();
        DBG("ReqSetMute: state=%d", ((int *)data)[0]);
        req->set_state(((int *)data)[0]);
        DBG("ReqSetMute: req->ByetSize=%d", req->ByteSize());
        buffer = Buffer::New(req->ByteSize());
        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
    }
    DBG("ReqSetMute X status=%d", status);
    return status;
}

/**
 * request for RIL_REQUEST_SCREEN_STATE  // 61
 */
int ReqScreenState(Buffer **pBuffer,
        const void *data, const size_t datalen, const RIL_Token t) {
    int status;
    Buffer *buffer;
    v8::HandleScope handle_scope;

    DBG("ReqScreenState E data=%p datalen=%d t=%p",
         data, datalen, t);
    if (datalen < sizeof(int)) {
        LOGE("ReqScreenState: data to small err size < sizeof int");
        status = STATUS_BAD_DATA;
    } else {
        ril_proto::ReqScreenState *req = new ril_proto::ReqScreenState();
        DBG("ReqScreenState: state=%d", ((int *)data)[0]);
        req->set_state(((int *)data)[0]);
        DBG("ReqScreenState: req->ByteSize()=%d", req->ByteSize());
        buffer = Buffer::New(req->ByteSize());
        DBG("ReqScreenState: serialize");
        req->SerializeToArray(buffer->data(), buffer->length());
        delete req;
        *pBuffer = buffer;
        status = STATUS_OK;
    }
    DBG("ReqScreenState X status=%d", status);
    return status;
}

/**
 * Map from indexed by cmd and used to convert Data to Protobuf.
 */
typedef int (*ReqConversion)(Buffer** pBuffer, const void *data,
                const size_t datalen, const RIL_Token t);
typedef std::map<int, ReqConversion> ReqConversionMap;
ReqConversionMap rilReqConversionMap;

int callOnRilRequest(v8::Handle<v8::Context> context, int cmd,
                   const void *buffer, RIL_Token t) {
    DBG("callOnRilRequest E: cmd=%d", cmd);

    int status;
    v8::HandleScope handle_scope;
    v8::TryCatch try_catch;

    // Get the onRilRequest Function
    v8::Handle<v8::String> name = v8::String::New("onRilRequest");
    v8::Handle<v8::Value> onRilRequestFunctionValue = context->Global()->Get(name);
    v8::Handle<v8::Function> onRilRequestFunction =
        v8::Handle<v8::Function>::Cast(onRilRequestFunctionValue);

    // Create the cmd and token
    v8::Handle<v8::Value> v8RequestValue = v8::Number::New(cmd);
    v8::Handle<v8::Value> v8TokenValue = v8::Number::New(int64_t(t));

    // Invoke onRilRequest
    const int argc = 3;
    v8::Handle<v8::Value> argv[argc] = {
            v8RequestValue, v8TokenValue, ((Buffer *)buffer)->handle_ };
    v8::Handle<v8::Value> result =
        onRilRequestFunction->Call(context->Global(), argc, argv);
    if (try_catch.HasCaught()) {
        LOGE("callOnRilRequest error");
        ReportException(&try_catch);
        status = STATUS_ERR;
    } else {
        v8::String::Utf8Value result_string(result);
        DBG("callOnRilRequest result=%s", ToCString(result_string));
        status = STATUS_OK;
    }

    DBG("callOnRilRequest X: status=%d", status);
    return status;
}

RilRequestWorkerQueue::RilRequestWorkerQueue(v8::Handle<v8::Context> context) {
    DBG("RilRequestWorkerQueue E:");

    context_ = context;
    pthread_mutex_init(&free_list_mutex_, NULL);

    DBG("RilRequestWorkerQueue X:");
}

RilRequestWorkerQueue::~RilRequestWorkerQueue() {
    DBG("~RilRequestWorkerQueue E:");
    Request *req;
    pthread_mutex_lock(&free_list_mutex_);
    while(free_list_.size() != 0) {
        req = free_list_.front();
        delete req;
        free_list_.pop();
    }
    pthread_mutex_unlock(&free_list_mutex_);
    pthread_mutex_destroy(&free_list_mutex_);
    DBG("~RilRequestWorkerQueue X:");
}

/**
 * Add a request to the processing queue.
 * Data is serialized to a protobuf before adding to the queue.
 */
void RilRequestWorkerQueue::AddRequest (const int request,
        const void *data, const size_t datalen, const RIL_Token token) {
    DBG("RilRequestWorkerQueue:AddRequest: %d E", request);

    v8::Locker locker;
    v8::HandleScope handle_scope;
    v8::Context::Scope context_scope(context_);

    int status;

    // Convert the data to a protobuf before inserting it into the request queue (serialize data)
    Buffer *buffer = NULL;
    ReqConversionMap::iterator itr;
    itr = rilReqConversionMap.find(request);
    if (itr != rilReqConversionMap.end()) {
        status = itr->second(&buffer, data, datalen, token);
    } else {
        LOGE("RilRequestWorkerQueue:AddRequest: X unknown request %d", request);
        status = STATUS_UNSUPPORTED_REQUEST;
    }

    if (status == STATUS_OK) {
        // Add serialized request to the queue
        Request *req;
        pthread_mutex_lock(&free_list_mutex_);
        DBG("RilRequestWorkerQueue:AddRequest: return ok, buffer = %p, buffer->length()=%d",
            buffer, buffer->length());
        if (free_list_.size() == 0) {
            req = new Request(request, buffer, token);
            pthread_mutex_unlock(&free_list_mutex_);
        } else {
            req = free_list_.front();
            free_list_.pop();
            pthread_mutex_unlock(&free_list_mutex_);
            req->Set(request, buffer, token);
        }
        // add the request
        Add(req);
    } else {
        DBG("RilRequestWorkerQueue:AddRequest: return from the serialization, status is not OK");
        // An error report complete now
        RIL_Errno rilErrCode = (status == STATUS_UNSUPPORTED_REQUEST) ?
                 RIL_E_REQUEST_NOT_SUPPORTED : RIL_E_GENERIC_FAILURE;
        s_rilenv->OnRequestComplete(token, rilErrCode, NULL, 0);
    }

    DBG("RilRequestWorkerQueue::AddRequest: X"
         " request=%d data=%p datalen=%d token=%p",
            request, data, datalen, token);
}

void RilRequestWorkerQueue::Process(void *p) {

    Request *req = (Request *)p;
    DBG("RilRequestWorkerQueue::Process: E"
         " request=%d buffer=%p, bufferlen=%d t=%p",
            req->request_, req->buffer_, req->buffer_->length(), req->token_);

    v8::Locker locker;
    v8::HandleScope handle_scope;
    v8::Context::Scope context_scope(context_);
    callOnRilRequest(context_, req->request_,
                          req->buffer_, req->token_);

    pthread_mutex_lock(&free_list_mutex_);
    free_list_.push(req);
    pthread_mutex_unlock(&free_list_mutex_);
}

int requestsInit(v8::Handle<v8::Context> context, RilRequestWorkerQueue **rwq) {
    LOGD("requestsInit E");

    rilReqConversionMap[RIL_REQUEST_GET_SIM_STATUS] = ReqWithNoData; // 1
    rilReqConversionMap[RIL_REQUEST_ENTER_SIM_PIN] = ReqEnterSimPin; // 2
    rilReqConversionMap[RIL_REQUEST_GET_CURRENT_CALLS] = ReqWithNoData; // 9
    rilReqConversionMap[RIL_REQUEST_DIAL] = ReqDial;   // 10
    rilReqConversionMap[RIL_REQUEST_GET_IMSI] = ReqWithNoData; // 11
    rilReqConversionMap[RIL_REQUEST_HANGUP] = ReqHangUp; // 12
    rilReqConversionMap[RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND] = ReqWithNoData; // 13
    rilReqConversionMap[RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = ReqWithNoData; // 14
    rilReqConversionMap[RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = ReqWithNoData; // 15
    rilReqConversionMap[RIL_REQUEST_CONFERENCE] = ReqWithNoData;  // 16
    rilReqConversionMap[RIL_REQUEST_LAST_CALL_FAIL_CAUSE] = ReqWithNoData;  // 18
    rilReqConversionMap[RIL_REQUEST_SIGNAL_STRENGTH] = ReqWithNoData; // 19
    rilReqConversionMap[RIL_REQUEST_REGISTRATION_STATE] = ReqWithNoData; // 20
    rilReqConversionMap[RIL_REQUEST_GPRS_REGISTRATION_STATE] = ReqWithNoData; // 21
    rilReqConversionMap[RIL_REQUEST_OPERATOR] = ReqWithNoData; // 22
    rilReqConversionMap[RIL_REQUEST_GET_IMEI] = ReqWithNoData; // 38
    rilReqConversionMap[RIL_REQUEST_GET_IMEISV] = ReqWithNoData; // 39
    rilReqConversionMap[RIL_REQUEST_ANSWER] = ReqWithNoData; // 40
    rilReqConversionMap[RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE] = ReqWithNoData; // 45
    rilReqConversionMap[RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = ReqWithNoData; // 46
    rilReqConversionMap[RIL_REQUEST_BASEBAND_VERSION] = ReqWithNoData; // 51
    rilReqConversionMap[RIL_REQUEST_SEPARATE_CONNECTION] = ReqSeparateConnection; // 52
    rilReqConversionMap[RIL_REQUEST_SET_MUTE] = ReqSetMute; // 53
    rilReqConversionMap[RIL_REQUEST_SCREEN_STATE] = ReqScreenState; // 61

    *rwq = new RilRequestWorkerQueue(context);
    int status = (*rwq)->Run();

    LOGD("requestsInit X: status=%d", status);
    return status;
}

/**
 * Subroutine to test a single RIL request
 */
void testRilRequest(v8::Handle<v8::Context> context, int request, const void *data,
                    const size_t datalen, const RIL_Token t) {
    Buffer *buffer = NULL;
    ReqConversionMap::iterator itr;
    int status;

    LOGD("testRilRequest: request=%d", request);

    itr = rilReqConversionMap.find(request);
    if (itr != rilReqConversionMap.end()) {
        status = itr->second(&buffer, data, sizeof(data), (void *)0x12345677);
    } else {
        LOGE("testRequests X unknown request %d", request);
        status = STATUS_UNSUPPORTED_REQUEST;
    }
    if (status == STATUS_OK) {
        callOnRilRequest(context, request, buffer, (void *)0x12345677);
    } else {
        LOGE("testRilRequest X, serialize error");
    }
}

void testRequests(v8::Handle<v8::Context> context) {
    LOGD("testRequests E: ********");

    v8::TryCatch try_catch;

    char *buffer;
    const char *fileName= "/sdcard/data/mock_ril.js";
    int status = ReadFile(fileName, &buffer);
    if (status == 0) {
        runJs(context, &try_catch, fileName, buffer);
        Buffer *buffer = NULL;
        ReqConversionMap::iterator itr;
        int status;
        int request;

        if (!try_catch.HasCaught()) {
            {
                const int data[1] = { 1 };
                testRilRequest(context, RIL_REQUEST_SIGNAL_STRENGTH, data, sizeof(data),
                               (void *)0x12345677);
            }
            {
                const char *data[1] = { "winks-pin" };
                testRilRequest(context, RIL_REQUEST_ENTER_SIM_PIN, data, sizeof(data),
                               (void *)0x12345677);
            }
            {
                const int data[1] = { 1 };
                testRilRequest(context, RIL_REQUEST_HANGUP, data, sizeof(data),
                               (void *)0x12345677);
            }
            {
                const int data[1] = { 1 };
                testRilRequest(context, RIL_REQUEST_SCREEN_STATE, data, sizeof(data),
                               (void *)0x12345677);
            }
            {
                const int data[1] = { 1 };
                testRilRequest(context, RIL_REQUEST_GET_SIM_STATUS, data, sizeof(data),
                               (void *)0x12345677);
            }
            {
                RilRequestWorkerQueue *rwq = new RilRequestWorkerQueue(context);
                if (rwq->Run() == STATUS_OK) {
                    const int data[1] = { 1 };
                    rwq->AddRequest(RIL_REQUEST_SCREEN_STATE,
                                    data, sizeof(data), (void *)0x1234567A);
                    rwq->AddRequest(RIL_REQUEST_SIGNAL_STRENGTH,
                                    data, sizeof(data), (void *)0x1234567A);
                    // Sleep to let it be processed
                    v8::Unlocker unlocker;
                    sleep(3);
                    v8::Locker locker;
                }
                delete rwq;
            }
        }
    }

    LOGD("testRequests X: ********\n");
}
