/*
* Copyright (C) 2014 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.
*/

extern "C"
void *ril_socket_process_requests_loop(void *arg);

#include "RilSocket.h"
#include <cutils/sockets.h>
#include <utils/Log.h>
#include <assert.h>
#define SOCKET_LISTEN_BACKLOG 0

int RilSocket::socketInit(void) {
    int ret;

    listenCb = &RilSocket::sSocketListener;
    commandCb = &RilSocket::sSocketRequestsHandler;
    listenFd = android_get_control_socket(name);

    //Start listening
    ret = listen(listenFd, SOCKET_LISTEN_BACKLOG);

    if (ret < 0) {
        RLOGE("Failed to listen on %s socket '%d': %s",
        name, listenFd, strerror(errno));
        return ret;
    }
    //Add listen event to the event loop
    ril_event_set(&listenEvent, listenFd, false, listenCb, this);
    rilEventAddWakeup_helper(&listenEvent);
    return ret;
}

void RilSocket::sSocketListener(int fd, short flags, void *param) {
    RilSocket *theSocket = (RilSocket *) param;
    MySocketListenParam listenParam;
    listenParam.socket = theSocket;
    listenParam.sListenParam.type = RIL_SAP_SOCKET;

    listenCallback_helper(fd, flags, (void*)&listenParam);
}

void RilSocket::onNewCommandConnect() {
    pthread_attr_t attr;
    PthreadPtr pptr = ril_socket_process_requests_loop;
    int result;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    //Start socket request processing loop thread
    result = pthread_create(&socketThreadId, &attr, pptr, this);
    if(result < 0) {
        RLOGE("pthread_create failed with result:%d",result);
    }

    RLOGE("New socket command connected and socket request thread started");
}

void RilSocket::sSocketRequestsHandler(int fd, short flags, void *param) {
    socketClient *sc = (socketClient *) param;
    RilSocket *theSocket = sc->socketPtr;
    RecordStream *rs = sc->rs;

    theSocket->socketRequestsHandler(fd, flags, rs);
}

void RilSocket::socketRequestsHandler(int fd, short flags, RecordStream *p_rs) {
    int ret;
    assert(fd == commandFd);
    void *p_record;
    size_t recordlen;

    for (;;) {
        /* loop until EAGAIN/EINTR, end of stream, or other error */
        ret = record_stream_get_next(p_rs, &p_record, &recordlen);

        if (ret == 0 && p_record == NULL) {
            /* end-of-stream */
            break;
        } else if (ret < 0) {
            break;
        } else if (ret == 0) {
            pushRecord(p_record, recordlen);
        }
    }

    if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
        /* fatal error or end-of-stream */
        if (ret != 0) {
            RLOGE("error on reading command socket errno:%d\n", errno);
        } else {
            RLOGW("EOS.  Closing command socket.");
        }

        close(commandFd);
        commandFd = -1;

        ril_event_del(&callbackEvent);

        record_stream_free(p_rs);

        /* start listening for new connections again */

        rilEventAddWakeup_helper(&listenEvent);

        onCommandsSocketClosed();
    }
}

void RilSocket::setListenFd(int fd) {
    listenFd = fd;
}

void RilSocket::setCommandFd(int fd) {
    commandFd = fd;
}

int RilSocket::getListenFd(void) {
    return listenFd;
}

int RilSocket::getCommandFd(void) {
    return commandFd;
}

void RilSocket::setListenCb(ril_event_cb cb) {
    listenCb = cb;
}

void RilSocket::setCommandCb(ril_event_cb cb) {
    commandCb = cb;
}

ril_event_cb RilSocket::getListenCb(void) {
    return listenCb;
}

ril_event_cb RilSocket::getCommandCb(void) {
    return commandCb;
}

void RilSocket::setListenEvent(ril_event event) {
    listenEvent = event;
}

void RilSocket::setCallbackEvent(ril_event event) {
    callbackEvent = event;
}

ril_event* RilSocket::getListenEvent(void)  {
    return &listenEvent;
}

ril_event* RilSocket::getCallbackEvent(void) {
    return &callbackEvent;
}

extern "C"
void *ril_socket_process_requests_loop(void *arg) {
    RilSocket *socket = (RilSocket *)arg;
    socket->processRequestsLoop();
    return NULL;
}