/**
 * 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 <alloca.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/endian.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cutils/sockets.h>

#include "logging.h"
#include "node_buffer.h"
#include "status.h"
#include "util.h"
#include "worker.h"

#include "msgheader.pb.h"

#include "ctrl.pb.h"
#include "ctrl_server.h"

//#define CONTROL_SERVER_DEBUG
#ifdef  CONTROL_SERVER_DEBUG

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

#else

#define DBG(...)

#endif

#define MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET 54311
#define MOCK_RIL_CONTROL_SERVER_SOCKET 54312

using communication::MsgHeader;

class CtrlServerThread;
static CtrlServerThread *g_ctrl_server;

class CtrlServerThread : public WorkerThread {
  private:
    #define SOCKET_NAME_MOCK_RIL_CST_STOPPER "mock-ril-cst-stopper"
    v8::Handle<v8::Context> context_;
    int server_accept_socket_;
    int server_to_client_socket_;
    int stop_server_fd_;
    int stop_client_fd_;
    int stopper_fd_;
    fd_set rd_fds_;
    fd_set wr_fds_;
    bool done_;

    Buffer *ObtainBuffer(int length) {
        Buffer *b = Buffer::New(length);
        return b;
    }

    int WriteAll(int s, void *data, int length) {
        int ret_value;
        uint8_t *bytes = (uint8_t *)data;
        int count = length;

        while (length > 0) {
            ret_value = send(s, bytes, length, 0);
            if (ret_value < 0) {
                return STATUS_ERR;
            }
            if (ret_value == 0) {
                return STATUS_CLIENT_CLOSED_CONNECTION;
            }
            bytes += ret_value;
            length -= ret_value;
        }

        return STATUS_OK;
    }

    int ReadAll(int s, void *data, int length) {
        int ret_value;
        uint8_t *bytes = (uint8_t *)data;
        int count = length;

        while (length != 0) {
            ret_value = recv(s, bytes, length, 0);
            if (ret_value < 0) {
                return STATUS_ERR;
            }
            if (ret_value == 0) {
                return STATUS_CLIENT_CLOSED_CONNECTION;
            }
            bytes += ret_value;
            length -= ret_value;
        }

        return STATUS_OK;
    }

    int ReadMessage(MsgHeader *mh, Buffer **pBuffer) {
        int status;
        int32_t len_msg_header;

        // Reader header length
        status = ReadAll(server_to_client_socket_, &len_msg_header, sizeof(len_msg_header));
        len_msg_header = letoh32(len_msg_header);
        DBG("rm: read len_msg_header=%d  status=%d", len_msg_header, status);
        if (status != STATUS_OK) return status;

        // Read header into an array allocated on the stack and unmarshall
        uint8_t *msg_header_raw = (uint8_t *)alloca(len_msg_header);
        status = ReadAll(server_to_client_socket_, msg_header_raw, len_msg_header);
        DBG("rm: read msg_header_raw=%p  status=%d", msg_header_raw, status);
        if (status != STATUS_OK) return status;
        mh->ParseFromArray(msg_header_raw, len_msg_header);

        // Read auxillary data
        Buffer *buffer;
        if (mh->length_data() > 0) {
            buffer = ObtainBuffer(mh->length_data());
            status = ReadAll(server_to_client_socket_, buffer->data(), buffer->length());
            DBG("rm: read protobuf status=%d", status);
            if (status != STATUS_OK) return status;
        } else {
            DBG("rm: NO protobuf");
            buffer = NULL;
        }

        *pBuffer = buffer;
        return STATUS_OK;
    }

  public:
    int WriteMessage(MsgHeader *mh, Buffer *buffer) {
        int status;
        uint32_t i;
        uint64_t l;

        // Set length of data
        if (buffer == NULL) {
            mh->set_length_data(0);
        } else {
            mh->set_length_data(buffer->length());
        }

        // Serialize header
        uint32_t len_msg_header = mh->ByteSize();
        uint8_t *msg_header_raw = (uint8_t *)alloca(len_msg_header);
        mh->SerializeToArray(msg_header_raw, len_msg_header);

        // Write length in little endian followed by the header
        i = htole32(len_msg_header);
        status = WriteAll(server_to_client_socket_, &i, 4);
        DBG("wm: write len_msg_header=%d status=%d", len_msg_header, status);
        if (status != 0) return status;
        status = WriteAll(server_to_client_socket_, msg_header_raw, len_msg_header);
        DBG("wm: write msg_header_raw=%p  status=%d", msg_header_raw, status);
        if (status != 0) return status;

        // Write data
        if (mh->length_data() > 0) {
            status = WriteAll(server_to_client_socket_, buffer->data(), buffer->length());
            DBG("wm: protobuf data=%p len=%d status=%d",
                    buffer->data(), buffer->length(), status);
            if (status != 0) return status;
        }

        return STATUS_OK;
    }

    CtrlServerThread(v8::Handle<v8::Context> context) :
            context_(context),
            server_accept_socket_(-1),
            server_to_client_socket_(-1),
            done_(false) {
    }

    virtual int Run() {
        DBG("CtrlServerThread::Run E");

        // Create a server socket.
        server_accept_socket_ = socket_inaddr_any_server(
            MOCK_RIL_CONTROL_SERVER_SOCKET, SOCK_STREAM);
        if (server_accept_socket_ < 0) {
            LOGE("CtrlServerThread::Run error creating server_accept_socket_ '%s'",
                    strerror(errno));
            return STATUS_ERR;
        }

        // Create a server socket that will be used for stopping
        stop_server_fd_ = socket_loopback_server(
                MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET, SOCK_STREAM);
        if (stop_server_fd_ < 0) {
            LOGE("CtrlServerThread::Run error creating stop_server_fd_ '%s'",
                    strerror(errno));
            return STATUS_ERR;
        }

        // Create a client socket that will be used for sending a stop
        stop_client_fd_ = socket_loopback_client(
                MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET, SOCK_STREAM);
        if (stop_client_fd_ < 0) {
            LOGE("CtrlServerThread::Run error creating stop_client_fd_ '%s'",
                    strerror(errno));
            return STATUS_ERR;
        }

        // Accept the connection of the stop_client_fd_
        stopper_fd_ = accept(stop_server_fd_, NULL, NULL);
        if (stopper_fd_ < 0) {
            LOGE("CtrlServerThread::Run error accepting stop_client_fd '%s'",
                    strerror(errno));
            return STATUS_ERR;
        }

        // Run the new thread
        int ret_value = WorkerThread::Run(NULL);
        DBG("CtrlServerThread::Run X");
        return ret_value;
    }

    virtual void Stop() {
        DBG("CtrlServerThread::Stop E");
        if (BeginStopping()) {
            done_ = true;
            int rv = send(stop_client_fd_, &done_, sizeof(done_), 0);
            if (rv <= 0) {
                LOGE("CtrlServerThread::Stop could not send stop"
                            "WE WILL PROBABLY HANG");
            }
            WaitUntilStopped();
        }
        DBG("CtrlServerThread::Stop X");
    }

    virtual bool isRunning() {
        bool rv = done_ || WorkerThread::isRunning();
        return rv;
    }

    int WaitOnSocketOrStopping(fd_set *rfds, int s) {
        DBG("WaitOnSocketOrStopping E s=%d stopper_fd_=%d", s, stopper_fd_);
        FD_ZERO(rfds);
        FD_SET(s, rfds);
        FD_SET(stopper_fd_, rfds);
        int fd_number = s > stopper_fd_ ? s + 1 : stopper_fd_ + 1;
        v8::Unlocker unlocker;
        int rv = select(fd_number, rfds, NULL, NULL, NULL);
        v8::Locker locker;
        DBG("WaitOnSocketOrStopping X rv=%d s=%d stopper_fd_=%d", rv, s, stopper_fd_);
        return rv;
    }

    int sendToCtrlServer(MsgHeader *mh, Buffer *buffer) {
        DBG("sendToCtrlServer E: cmd=%d token=%lld", mh->cmd(), mh->token());

        int status = STATUS_OK;
        v8::HandleScope handle_scope;
        v8::TryCatch try_catch;
        try_catch.SetVerbose(true);

        // Get the onRilRequest Function
        v8::Handle<v8::String> name = v8::String::New("onCtrlServerCmd");
        v8::Handle<v8::Value> onCtrlServerCmdFunctionValue =
                context_->Global()->Get(name);
        v8::Handle<v8::Function> onCtrlServerCmdFunction =
                v8::Handle<v8::Function>::Cast(onCtrlServerCmdFunctionValue);

        // Create the CmdValue and TokenValue
        v8::Handle<v8::Value> v8CmdValue = v8::Number::New(mh->cmd());
        v8::Handle<v8::Value> v8TokenValue = v8::Number::New(mh->token());

        // Invoke onRilRequest
        const int argc = 3;
        v8::Handle<v8::Value> buf;
        if (mh->length_data() == 0) {
            buf = v8::Undefined();
        } else {
            buf = buffer->handle_;
        }
        v8::Handle<v8::Value> argv[argc] = {
                v8CmdValue, v8TokenValue, buf };
        v8::Handle<v8::Value> result =
            onCtrlServerCmdFunction->Call(context_->Global(), argc, argv);
        if (try_catch.HasCaught()) {
            ReportException(&try_catch);
            status = STATUS_ERR;
        } else {
            v8::String::Utf8Value result_string(result);
            DBG("sendToCtrlServer result=%s", ToCString(result_string));
            status = STATUS_OK;
        }

        if (status != STATUS_OK) {
            LOGE("sendToCtrlServer Error: status=%d", status);
            // An error report complete now
            mh->set_length_data(0);
            mh->set_status(ril_proto::CTRL_STATUS_ERR);
            g_ctrl_server->WriteMessage(mh, NULL);
        }

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

    virtual void * Worker(void *param) {
        DBG("CtrlServerThread::Worker E param=%p stopper_fd_=%d",
                param, stopper_fd_);

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

        while (isRunning()) {
            int ret_value;

            // Wait on either server_accept_socket_ or stopping
            DBG("CtrlServerThread::Worker wait on server for a client");
            WaitOnSocketOrStopping(&rd_fds_, server_accept_socket_);
            if (isRunning() != true) {
                break;
            }

            if (FD_ISSET(server_accept_socket_, &rd_fds_)) {
                server_to_client_socket_ = accept(server_accept_socket_, NULL, NULL);
                DBG("CtrlServerThread::Worker accepted server_to_client_socket_=%d isRunning()=%d",
                        server_to_client_socket_, isRunning());

                int status;
                Buffer *buffer;
                MsgHeader mh;
                while ((server_to_client_socket_ > 0) && isRunning()) {
                    DBG("CtrlServerThread::Worker wait on client for message");
                    WaitOnSocketOrStopping(&rd_fds_, server_to_client_socket_);
                    if (isRunning() != true) {
                        break;
                    }

                    status = ReadMessage(&mh, &buffer);
                    if (status != STATUS_OK) break;

                    if (mh.cmd() == ril_proto::CTRL_CMD_ECHO) {
                        LOGD("CtrlServerThread::Worker echo");
                        status = WriteMessage(&mh, buffer);
                        if (status != STATUS_OK) break;
                    } else {
                        DBG("CtrlServerThread::Worker sendToCtrlServer");
                        status = sendToCtrlServer(&mh, buffer);
                        if (status != STATUS_OK) break;
                    }
                }
                close(server_to_client_socket_);
                server_to_client_socket_ = -1;
            }
        }
        close(stop_server_fd_);
        stop_server_fd_ = -1;

        close(stop_client_fd_);
        stop_client_fd_ = -1;

        close(stopper_fd_);
        stopper_fd_ = -1;

        close(server_accept_socket_);
        server_accept_socket_ = -1;

        DBG("CtrlServerThread::Worker X param=%p", param);
        return NULL;
    }
};

/**
 * Send a control request complete response.
 */
v8::Handle<v8::Value> SendCtrlRequestComplete(const v8::Arguments& args) {
    DBG("SendCtrlRequestComplete E:");
    v8::HandleScope handle_scope;
    v8::Handle<v8::Value> retValue;

    void *data;
    size_t datalen;

    Buffer* buffer;
    MsgHeader mh;

    /**
     * Get the arguments. There should be at least 3, reqNum,
     * ril error code and token. Optionally a Buffer containing
     * the protobuf representation of the data to return.
     */
    if (args.Length() < 3) {
        // Expecting a reqNum, ERROR and token
        LOGE("SendCtrlRequestComplete X %d parameters"
             " expecting at least 3: status, reqNum, and token",
                args.Length());
        return v8::Undefined();
    }
    v8::Handle<v8::Value> v8CtrlStatus(args[0]->ToObject());
    mh.set_status(ril_proto::CtrlStatus(v8CtrlStatus->NumberValue()));
    DBG("SendCtrlRequestComplete: status=%d", mh.status());

    v8::Handle<v8::Value> v8ReqNum(args[1]->ToObject());
    mh.set_cmd(int(v8ReqNum->NumberValue()));
    DBG("SendCtrlRequestComplete: cmd=%d", mh.cmd());

    v8::Handle<v8::Value> v8Token(args[2]->ToObject());
    mh.set_token(int64_t(v8Token->NumberValue()));
    DBG("SendCtrlRequestComplete: token=%lld", mh.token());

    if (args.Length() >= 4) {
        buffer = ObjectWrap::Unwrap<Buffer>(args[3]->ToObject());
        mh.set_length_data(buffer->length());
        DBG("SendCtrlRequestComplete: mh.length_data=%d",
                mh.length_data());
    } else {
        mh.set_length_data(0);
        buffer = NULL;
        DBG("SendCtrlRequestComplete: NO PROTOBUF");
    }

    DBG("SendCtrlRequestComplete: WriteMessage");
    int status = g_ctrl_server->WriteMessage(&mh, buffer);

    DBG("SendCtrlRequestComplete E:");
    return v8::Undefined();
}

void ctrlServerInit(v8::Handle<v8::Context> context) {
    int status;

    g_ctrl_server = new CtrlServerThread(context);
    status = g_ctrl_server->Run();
    if (status != STATUS_OK) {
        LOGE("mock_ril control server could not start");
    } else {
        LOGD("CtrlServer started");
    }

#if 0
    LOGD("Test CtrlServerThread stop sleeping 10 seconds...");
    v8::Unlocker unlocker;
    sleep(10);
    LOGD("Test CtrlServerThread call Stop");
    g_ctrl_server->Stop();
    v8::Locker locker;

    // Restart
    g_ctrl_server = new CtrlServerThread(context);
    status = g_ctrl_server->Run();
    if (status != STATUS_OK) {
        LOGE("mock_ril control server could not start");
    } else {
        DBG("mock_ril control server started");
    }
#endif
}
