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

/*
 * Contains helper routines dealing with syncronous access to a non-blocking
 * sokets.
 */

#include "qemu-common.h"
#include "errno.h"
#include "iolooper.h"
#include "sockets.h"
#include "android/utils/debug.h"
#include "android/sync-utils.h"
#include "android/utils/system.h"

#define  D(...)  do {  if (VERBOSE_CHECK(init)) dprint(__VA_ARGS__); } while (0)

struct SyncSocket {
    // Helper for performing synchronous I/O on the socket.
    IoLooper* iolooper;

    /* Opened socket handle. */
    int fd;
};

SyncSocket*
syncsocket_init(int fd)
{
    SyncSocket* sync_socket;
    ANEW0(sync_socket);

    socket_set_nonblock(fd);
    sync_socket->iolooper = iolooper_new();
    sync_socket->fd = fd;

    return sync_socket;
}

SyncSocket*
syncsocket_connect(int fd, SockAddress* sockaddr, int timeout)
{
    IoLooper* looper;
    int connect_status;
    SyncSocket* sync_socket = NULL;

    socket_set_nonblock(fd);

    for(;;) {
        connect_status = socket_connect(fd, sockaddr);
        if (connect_status >= 0) {
            // Connected. Create IoLooper for the helper.
            looper = iolooper_new();
            break;
        }

        if (errno == EINPROGRESS || errno == EAGAIN || errno == EWOULDBLOCK) {
            // Connection is in progress. Wait till it's finished.
            looper = iolooper_new();
            iolooper_add_write(looper, fd);
            connect_status = iolooper_wait(looper, timeout);
            if (connect_status > 0) {
                iolooper_del_write(looper, fd);
                break;
            } else {
                iolooper_free(looper);
                return NULL;
            }
        } else if (errno != EINTR) {
            return NULL;
        }
    }

    // We're now connected. Lets initialize SyncSocket instance
    // for this connection.
    sync_socket = malloc(sizeof(SyncSocket));
    if (sync_socket == NULL) {
        derror("PANIC: not enough memory\n");
        exit(1);
    }

    sync_socket->iolooper = looper;
    sync_socket->fd = fd;

    return sync_socket;
}

void
syncsocket_close(SyncSocket* ssocket)
{
    if (ssocket != NULL && ssocket->fd >= 0) {
        if (ssocket->iolooper != NULL) {
            iolooper_reset(ssocket->iolooper);
        }
        socket_close(ssocket->fd);
        ssocket->fd = -1;
    }
}

void
syncsocket_free(SyncSocket* ssocket)
{
    if (ssocket != NULL) {
        if (ssocket->iolooper != NULL) {
            iolooper_free(ssocket->iolooper);
        }
        free(ssocket);
    }
}

int
syncsocket_start_read(SyncSocket* ssocket)
{
    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }
    iolooper_add_read(ssocket->iolooper, ssocket->fd);
    return 0;
}

int
syncsocket_stop_read(SyncSocket* ssocket)
{
    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }
    iolooper_del_read(ssocket->iolooper, ssocket->fd);
    return 0;
}

int
syncsocket_start_write(SyncSocket* ssocket)
{
    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }
    iolooper_add_write(ssocket->iolooper, ssocket->fd);
    return 0;
}

int
syncsocket_stop_write(SyncSocket* ssocket)
{
    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }
    iolooper_del_write(ssocket->iolooper, ssocket->fd);
    return 0;
}

ssize_t
syncsocket_read_absolute(SyncSocket* ssocket,
                         void* buf,
                         size_t size,
                         int64_t deadline)
{
    int ret;

    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }

    ret = iolooper_wait_absolute(ssocket->iolooper, deadline);
    if (ret > 0) {
        if (!iolooper_is_read(ssocket->iolooper, ssocket->fd)) {
            D("%s: Internal error, iolooper_is_read() not set!", __FUNCTION__);
            return -1;
        }
        do {
            ret = socket_recv(ssocket->fd, buf, size);
        } while( ret < 0 && errno == EINTR);
    } else if (ret == 0) {
        // Timed out
        errno = ETIMEDOUT;
        ret = -1;
    }
    return ret;
}

ssize_t
syncsocket_read(SyncSocket* ssocket, void* buf, size_t size, int timeout)
{
    return syncsocket_read_absolute(ssocket, buf, size, iolooper_now() + timeout);
}

ssize_t
syncsocket_write_absolute(SyncSocket* ssocket,
                          const void* buf,
                          size_t size,
                          int64_t deadline)
{
    int ret;
    size_t written = 0;

    if (ssocket == NULL || ssocket->fd < 0 || ssocket->iolooper == NULL) {
        errno = EINVAL;
        return -1;
    }

    do {
        ret = iolooper_wait_absolute(ssocket->iolooper, deadline);
        if (ret < 0) {
            return ret;
        } else if (ret == 0) {
            // Timeout.
            errno = ETIMEDOUT;
            return -1;
        }
        if (!iolooper_is_write(ssocket->iolooper, ssocket->fd)) {
            D("%s: Internal error, iolooper_is_write() not set!", __FUNCTION__);
            return -1;
        }

        do {
            ret = socket_send(ssocket->fd, (const char*)buf + written, size - written);
        } while( ret < 0 && errno == EINTR);

        if (ret > 0) {
            written += ret;
        } else if (ret < 0) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                return -1;
            }
        } else {
            // Disconnected.
            errno = ECONNRESET;
            return -1;
        }
    } while (written < size);
    return (int)written;
}

ssize_t
syncsocket_write(SyncSocket* ssocket, const void* buf, size_t size, int timeout)
{
    return syncsocket_write_absolute(ssocket, buf, size, iolooper_now() + timeout);
}

ssize_t
syncsocket_read_line_absolute(SyncSocket* ssocket,
                              char* buffer,
                              size_t size,
                              int64_t deadline)
{
    size_t read_chars = 0;

    while (read_chars < size) {
        char ch;
        int ret = syncsocket_read_absolute(ssocket, &ch, 1, deadline);
        if (ret <= 0) {
            return ret;
        }
        buffer[read_chars++] = ch;
        if (ch == '\n') {
            return read_chars;
        }
    }

    /* Not enough room in the input buffer!*/
    errno = ENOMEM;
    return -1;
}

ssize_t
syncsocket_read_line(SyncSocket* ssocket, char* buffer, size_t size, int timeout)
{
    return syncsocket_read_line_absolute(ssocket, buffer, size,
                                         iolooper_now() + timeout);
}

int
syncsocket_get_socket(SyncSocket* ssocket)
{
    return (ssocket != NULL) ? ssocket->fd : -1;
}