/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/
#include "sockets.h"
#include "sysdeps.h"
#include "vl.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif

#define  DEBUG  1

#define  D_ACTIVE  DEBUG

#if DEBUG
#define  D(...)  do { if (D_ACTIVE) fprintf(stderr, __VA_ARGS__); } while (0)
#else
#define  D(...)  ((void)0)
#endif

/** TIME
 **/

SysTime
sys_time_ms( void )
{
    return qemu_get_clock( rt_clock );
}

/** TIMERS
 **/

typedef struct SysTimerRec_ {
    QEMUTimer*    timer;
    QEMUTimerCB*  callback;
    void*         opaque;
    SysTimer      next;
} SysTimerRec;

#define  MAX_TIMERS  32

static SysTimerRec  _s_timers0[ MAX_TIMERS ];
static SysTimer     _s_free_timers;

static void
sys_init_timers( void )
{
    int  nn;
    for (nn = 0; nn < MAX_TIMERS-1; nn++)
        _s_timers0[nn].next = _s_timers0 + (nn+1);

    _s_free_timers = _s_timers0;
}

static SysTimer
sys_timer_alloc( void )
{
    SysTimer  timer = _s_free_timers;

    if (timer != NULL) {
        _s_free_timers = timer->next;
        timer->next    = NULL;
        timer->timer   = NULL;
    }
    return timer;
}


static void
sys_timer_free( SysTimer  timer )
{
    if (timer->timer) {
        qemu_del_timer( timer->timer );
        qemu_free_timer( timer->timer );
        timer->timer = NULL;
    }
    timer->next    = _s_free_timers;
    _s_free_timers = timer;
}


SysTimer   sys_timer_create( void )
{
    SysTimer  timer = sys_timer_alloc();
    return timer;
}

void
sys_timer_set( SysTimer  timer, SysTime  when, SysCallback   _callback, void*  opaque )
{
    QEMUTimerCB*  callback = (QEMUTimerCB*)_callback;

    if (callback == NULL) {  /* unsetting the timer */
        if (timer->timer) {
            qemu_del_timer( timer->timer );
            qemu_free_timer( timer->timer );
            timer->timer = NULL;
        }
        timer->callback = callback;
        timer->opaque   = NULL;
        return;
    }

    if ( timer->timer ) {
         if ( timer->callback == callback && timer->opaque == opaque )
            goto ReuseTimer;

         /* need to replace the timer */
         qemu_free_timer( timer->timer );
    }

    timer->timer    = qemu_new_timer( rt_clock, callback, opaque );
    timer->callback = callback;
    timer->opaque   = opaque;

ReuseTimer:
    qemu_mod_timer( timer->timer, when );
}

void
sys_timer_unset( SysTimer  timer )
{
    if (timer->timer) {
        qemu_del_timer( timer->timer );
    }
}

void
sys_timer_destroy( SysTimer  timer )
{
    sys_timer_free( timer );
}


/** CHANNELS
 **/

typedef struct SysChannelRec_ {
    int                 fd;
    SysChannelCallback  callback;
    void*               opaque;
    SysChannel          next;
} SysChannelRec;

#define  MAX_CHANNELS  16

static SysChannelRec  _s_channels0[ MAX_CHANNELS ];
static SysChannel     _s_free_channels;

static void
sys_init_channels( void )
{
    int  nn;

    for ( nn = 0; nn < MAX_CHANNELS-1; nn++ ) {
        _s_channels0[nn].next = _s_channels0 + (nn+1);
    }
    _s_free_channels = _s_channels0;
}

static SysChannel
sys_channel_alloc( )
{
    SysChannel  channel = _s_free_channels;
    if (channel != NULL) {
        _s_free_channels  = channel->next;
        channel->next     = NULL;
        channel->fd       = -1;
        channel->callback = NULL;
        channel->opaque   = NULL;
    }
    return channel;
}

static void
sys_channel_free( SysChannel  channel )
{
    if (channel->fd >= 0) {
        socket_close( channel->fd );
        channel->fd = -1;
    }
    channel->next    = _s_free_channels;
    _s_free_channels = channel;
}


static void
sys_channel_read_handler( void*  _channel )
{
    SysChannel  channel = _channel;
    D( "%s: read event for channel %p:%d\n", __FUNCTION__,
       channel, channel->fd );
    channel->callback( channel->opaque, SYS_EVENT_READ );
}

static void
sys_channel_write_handler( void*  _channel )
{
    SysChannel  channel = _channel;
    D( "%s: write event for channel %p:%d\n", __FUNCTION__, channel, channel->fd );
    channel->callback( channel->opaque, SYS_EVENT_WRITE );
}

void
sys_channel_on( SysChannel          channel,
                int                 events,
                SysChannelCallback  event_callback,
                void*               event_opaque )
{
    IOHandler*  read_handler  = NULL;
    IOHandler*  write_handler = NULL;

    if (events & SYS_EVENT_READ) {
        read_handler = sys_channel_read_handler;
    }
    if (events & SYS_EVENT_WRITE) {
        write_handler = sys_channel_write_handler;
    }
    channel->callback = event_callback;
    channel->opaque   = event_opaque;
    qemu_set_fd_handler( channel->fd, read_handler, write_handler, channel );
}

int
sys_channel_read( SysChannel  channel, void*  buffer, int  size )
{
    int   len = size;
    char* buf = (char*) buffer;

    while (len > 0) {
        int  ret = recv(channel->fd, buf, len, 0);
        if (ret < 0) {
            if (socket_errno == EINTR)
                continue;
            if (socket_errno == EWOULDBLOCK)
                break;
            D( "%s: after reading %d bytes, recv() returned error %d: %s\n",
                __FUNCTION__, size - len, socket_errno, socket_errstr());
            return -1;
        } else if (ret == 0) {
            break;
        } else {
            buf += ret;
            len -= ret;
        }
    }
    return size - len;
}


int
sys_channel_write( SysChannel  channel, const void*  buffer, int  size )
{
    int         len = size;
    const char* buf = (const char*) buffer;

    while (len > 0) {
        int  ret = send(channel->fd, buf, len, 0);
        if (ret < 0) {
            if (socket_errno == EINTR)
                continue;
            if (socket_errno == EWOULDBLOCK)
                break;
            D( "%s: send() returned error %d: %s\n",
                __FUNCTION__, socket_errno, socket_errstr());
            return -1;
        } else if (ret == 0) {
            break;
        } else {
            buf += ret;
            len -= ret;
        }
    }
    return size - len;
}

void  sys_channel_close( SysChannel  channel )
{
    qemu_set_fd_handler( channel->fd, NULL, NULL, NULL );
    sys_channel_free( channel );
}

void  sys_main_init( void )
{
    sys_init_channels();
    sys_init_timers();
}


int   sys_main_loop( void )
{
    /* no looping, qemu has its own event loop */
    return 0;
}




SysChannel
sys_channel_create_tcp_server( int port )
{
    SysChannel  channel = sys_channel_alloc();

    channel->fd = socket_anyaddr_server( port, SOCK_STREAM );
    if (channel->fd < 0) {
        D( "%s: failed to created network socket on TCP:%d\n", 
            __FUNCTION__, port );
        sys_channel_free( channel );
        return NULL;
    }

    D( "%s: server channel %p:%d now listening on port %d\n",
       __FUNCTION__, channel, channel->fd, port );

    return channel;
}


SysChannel
sys_channel_create_tcp_handler( SysChannel  server_channel )
{
    SysChannel  channel = sys_channel_alloc();

    D( "%s: creating handler from server channel %p:%d\n", __FUNCTION__,
       server_channel, server_channel->fd );

    channel->fd = socket_accept_any( server_channel->fd );
    if (channel->fd < 0) {
        perror( "accept" );
        sys_channel_free( channel );
        return NULL;
    }

    /* disable Nagle algorithm */
    socket_set_lowlatency( channel->fd );

    D( "%s: handler %p:%d created from server %p:%d\n", __FUNCTION__,
        server_channel, server_channel->fd, channel, channel->fd );

     return channel;
}


SysChannel
sys_channel_create_tcp_client( const char*  hostname, int  port )
{
    SysChannel  channel = sys_channel_alloc();

    channel->fd = socket_network_client( hostname, port, SOCK_STREAM );
    if (channel->fd < 0) {
        sys_channel_free(channel);
        return NULL;
    };

    /* set to non-blocking and disable Nagle algorithm */
    socket_set_nonblock( channel->fd );
    socket_set_lowlatency( channel->fd );

    return channel;
}

