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

/**
 * @fileoverview Mock Radio Interface Layer (RIL) used for testing
 *
 * The following routines are defined in c++:
 *
 * Print a string to android log
 *   print(string)
 *
 * Read a file to a string.
 *   String readFileToString(String fileName)
 *
 * Read a file to a Buffer.
 *   Buffer readFileToBuffer(String fileName)
 *
 * Send an response unsolicited response to the framework.
 *   sendRilUnsolicitedResponse(Number responseNum, Buffer responseProtobuf)
 *
 * Send a completion request to the framework.
 *   sendRilRequestComplete(Number rilErrCode, Number reqNum,
 *                              String token, Buffer responseProtobuf)
 *
 * Send a complete request to the controller.
 *   sendCtrlRequestComplete(Number ctrlStatus, Number reqNum,
 *                              String token, Buffer responseProtobuf)
 *
 * Include the javascript file.
 *   include(string)
 *
 * The following objects are defined in c++
 *
 * Buffer is defined in node_buffer and provides a wrapper
 * for a buffer that can be shared between c++ and js.
 *   Buffer(length)
 *     Buffer::length()
 *     Buffer::data()
 *
 * Schema is defined in protobuf_v8 and converts between
 * a buffer and an object. A protobuf descriptor, ril.desc
 * and ctrl.desc, is used to drive the conversation.
 *     Object Schema::parse(Buffer protobuf)
 *     Buffer Schema::serialize(object)
 *
 * Worker is a thread which receives messages to be handled.
 * It is passed a function which is called once for each
 * message as it arrives. Call the add method to queue up
 * requests for the worker function to process.
 *   Object Worker(function (req))
 *      Worker::add(req);
 */

/**
 * Globals
 */

include("ril_vars.js");

var NULL_RESPONSE_STRING = '*magic-null*';

// The state of the radio, needed by currentState()
var gRadioState = RADIOSTATE_UNAVAILABLE;

// The state of the screen
var gScreenState = 0;

// The base band version
var gBaseBandVersion = 'mock-ril 0.1';

// define a global variable to access the global object
var globals = this;

// Empty Protobuf, defined here so we don't have
// to recreate an empty Buffer frequently
var emptyProtobuf = new Buffer();

// Get the ril description file and create a schema
var packageNameAndSeperator = 'ril_proto.';
var rilSchema = new Schema(readFileToBuffer('ril.desc'));
var ctrlSchema = new Schema(readFileToBuffer('ctrl.desc'));

/**
 * Print properties of an object
 */
function printProperties(obj, maxDepth, depth) {
    if (typeof maxDepth == 'undefined') {
        maxDepth = 1;
    }
    if (typeof depth == 'undefined') {
        depth = 1;
    }
    if (depth == 1) {
        print('printProperties:');
    }
    for (var property in obj) {
        try {
            if ((typeof obj[property] == 'object')
                    && (depth < maxDepth)) {
                printProperties(obj[property], maxDepth, depth+1);
            } else {
                print(depth + ': ' + property + '=' + obj[property] +
                        ' type=' + typeof obj[property]);
            }
        } catch (err) {
            print('err=' + err)
        }
    }
}

// Test printProperties
if (false) {
    var myObject = { 'field1' : '1', 'field2' : '2', 'hello' : [ 'hi', 'there' ] };
    printProperties(myObject, 3);
}

/**
 * Include the components
 */

include("simulated_radio.js");
include("simulated_icc.js");
include("ctrl_server.js");

/**
 * Construct a new request which is passed to the
 * Worker handler method.
 */
function Request(reqNum, token, protobuf, schema, schemaName) {
    this.reqNum = reqNum;
    this.token = token;
    try {
        this.data = schema[packageNameAndSeperator + schemaName].parse(protobuf);
    } catch (err) {
        // not a valid protobuf in the request
        this.data = null;
    }
}

/**
 * Dispatch incoming requests from RIL to the appropriate component.
 */
function onRilRequest(reqNum, token, requestProtobuf) {
    try {
        //print('onRilRequest E: reqNum=' + reqNum + ' token=' + token);

        /**
         * Validate parameters
         */
        rilErrCode = RIL_E_SUCCESS;
        if (typeof reqNum != 'number') {
            print('onRilRequest: reqNum is not a number');
            rilErrCode = RIL_E_GENERIC_FAILURE;
        }
        if (typeof token != 'number') {
            print('onRilRequest: token is not a number');
            rilErrCode = RIL_E_GENERIC_FAILURE;
        }
        if (typeof requestProtobuf != 'object') {
            print('onRilRequest: requestProtobuf is not an object');
            rilErrCode = RIL_E_GENERIC_FAILURE;
        }
        if (rilErrCode != RIL_E_SUCCESS) {
            sendRilRequestComplete(rilErrCode, reqNum, token);
            return 'onRilRequest X: invalid parameter';
        }

        try {
            //print('onRilRequest: get entry from dispatchTable reqNum=' + reqNum);
            entry = dispatchTable[reqNum];
            if (typeof entry == 'undefined') {
                throw ('entry = dispatchTable[' + reqNum + '] was undefined');
            } else {
                req = new Request(reqNum, token, requestProtobuf, rilSchema, entry.schemaName);
                for(i = 0; i < entry.components.length; i++) {
                    entry.components[i].add(req);
                }
            }
        } catch (err) {
            print('onRilRequest: Unknown reqNum=' + reqNum + ' err=' + err);
            sendRilRequestComplete(RIL_E_REQUEST_NOT_SUPPORTED, reqNum, token);
        }
        // print('onRilRequest X: reqNum=' + reqNum + ' token=' + token);
    } catch (err) {
        print('onRilRequest X: Exception err=' + err);
        return('onRilRequest X: Exception err=' + err);
    }
    return 'onRilRequest X';
}

function onUnsolicitedTick(tick) {
    print('onUnsolicitedTick EX tick=' + tick);
    return 3;
}

/**
 * Dispatch table for requests
 *
 * Each table entry is index by the RIL_REQUEST_xxxx
 * and contains an array of components this request
 * is to be sent to and the name of the schema
 * that converts the incoming protobuf to the
 * appropriate request data.
 *
 * DispatchTable[RIL_REQUEST_xxx].components = Array of components
 * DisptachTable[RIL_REQUEST_xxx].Entry.schemaName = 'Name-of-schema';
 */
var dispatchTable = new Array();

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

/**
 * Start the mock rill after loading
 */
function startMockRil() {
    print("startMockRil E:");
    setRadioState(RADIOSTATE_SIM_READY);
    // send the signal strength after 5 seconds, wait until mock ril is started
    simulatedRadioWorker.addDelayed({
      'reqNum' : CMD_UNSOL_SIGNAL_STRENGTH}, 5000);
    print("startMockRil X:");
}

/**
 * Optional tests
 */
if (false) {
    include("mock_ril_tests.js");
}
