/*
 *  This file is part of the XForms library package.
 *
 *  XForms is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1, or
 *  (at your option) any later version.
 *
 *  XForms 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with XForms.  If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * \file asyn_io.c
 *.
 *  This file is part of the XForms library package.
 *  Copyright (c) 1996-2002  T.C. Zhao
 *  All rights reserved.
 *
 *  Handle input other than the X event queue. Mostly maintanance
 *  here. Actual input/output handling is triggered in the main loop
 *  via fli_watch_io.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "include/forms.h"
#include "flinternal.h"
#include <sys/types.h>

#ifndef FL_WIN32
#include <sys/time.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifdef __sgi
#include <bstring.h>
#endif

#ifdef FL_WIN32
#include <X11/Xpoll.h>
#endif


static fd_set st_rfds,
              st_wfds,
              st_efds;


static void fl_add_to_freelist( FLI_IO_REC * io );
static void fl_clear_freelist( void );

/***************************************
 * collect all the fd_sets so we don't do it inside the
 * critical select inner loop
 ***************************************/

static void
collect_fd( void )
{
    FLI_IO_REC *p;
    int nf = 0;

    /* initialize the sets */

    FD_ZERO( &st_rfds );
    FD_ZERO( &st_wfds );
    FD_ZERO( &st_efds );

    /* loop through all requested IOs */

    for ( p = fli_context->io_rec; p; p = p->next )
    {
        if ( p->source < 0 )
        {
            M_err( "collect_fd", "source < 0\n" );
            continue;
        }

        if ( p->mask & FL_READ )
            FD_SET( p->source, &st_rfds );
        if ( p->mask & FL_WRITE )
            FD_SET( p->source, &st_wfds );
        if ( p->mask & FL_EXCEPT )
            FD_SET( p->source, &st_efds );
        if ( nf < p->source + 1 )
            nf = p->source + 1;
    }

    fli_context->num_io = nf;
}


/***************************************
 * Register a callback function for file descriptor fd
 ***************************************/

void
fl_add_io_callback( int              fd,
                    unsigned int     mask,
                    FL_IO_CALLBACK   callback,
                    void           * data )
{
    FLI_IO_REC *io_rec;

    /* create new record and make it the start of the list */

    io_rec = fl_malloc( sizeof *io_rec );
    io_rec->next     = fli_context->io_rec;
    io_rec->callback = callback;
    io_rec->data     = data;
    io_rec->source   = fd;
    io_rec->mask     = mask;

    fli_context->io_rec = io_rec;

    collect_fd( );
}


/***************************************
 ***************************************/

void
fl_remove_io_callback( int            fd,
                       unsigned       int mask,
                       FL_IO_CALLBACK cb )
{
    FLI_IO_REC *io,
               *last;

    for ( last = io = fli_context->io_rec;
          io && ! ( io->source == fd && io->callback == cb && io->mask & mask );
          last = io, io = io->next )
        /* empty */ ;

    if ( ! io )
    {
        M_err( "fl_remove_io_callback", "Non-existent handler for %d", fd );
        return;
    }

    io->mask &= ~mask;

    /* Special case: if after removal fd does not do anything
       anymore, i.e. mask == 0, remove it from global record */

    if ( io->mask == 0 )
    {
        if ( io == fli_context->io_rec )
            fli_context->io_rec = io->next;
        else
            last->next = io->next;

        fl_add_to_freelist( io );
    }

    collect_fd( );
}


/***************************************
 * Watch for activities using select or poll. Timeout in milli-second
 ***************************************/

void
fli_watch_io( FLI_IO_REC * io_rec,
              long        msec )
{
    fd_set rfds,
           wfds,
           efds;
    struct timeval timeout;
    FLI_IO_REC *p;
    int nf;

    fl_clear_freelist( );

    if ( ! io_rec )
    {
        if ( msec > 0 )
            fl_msleep( msec );
        return;
    }

    timeout.tv_usec = 1000 * ( msec % 1000 );
    timeout.tv_sec  = msec / 1000;

    /* initialize the sets */

    rfds = st_rfds;
    wfds = st_wfds;
    efds = st_efds;

    /* now watch it. HP defines rfds to be ints. Althought compiler will
       bark, it is harmless. */

    nf = select( fli_context->num_io, &rfds, &wfds, &efds, &timeout );

    if ( nf < 0 )     /* something is wrong. */
    {
        if ( errno == EINTR )
            M_warn( "fli_watch_io", "select interrupted by signal" );

        /* select() on some platforms returns -1 with errno == 0 */

        else if ( errno != 0 )
            M_err( "fli_watch_io", fli_get_syserror_msg( ) );
    }

    /* time expired */

    if ( nf <= 0 )
        return;

    /* handle it */

    for ( p = io_rec; p; p = p->next )
    {
        if ( ! p->callback || p->source < 0 || p->mask == 0 )
            continue;

        if ( p->mask & FL_READ && FD_ISSET( p->source, &rfds ) )
            p->callback( p->source, p->data );

        if ( p->mask & FL_WRITE && FD_ISSET( p->source, &wfds ) )
            p->callback( p->source, p->data );

        if ( p->mask & FL_EXCEPT && FD_ISSET( p->source, &efds ) )
            p->callback( p->source, p->data );
    }

    fl_clear_freelist( );
}


/***************************************
 ***************************************/

int
fli_is_watched_io( int fd )
{
    FLI_IO_REC *p;

    for ( p = fli_context->io_rec; p; p = p->next )
        if ( p->source == fd && p->mask )
            return 1;

    return 0;
}


/***************************************
 ***************************************/

typedef struct free_list_
{
    struct free_list_ * next;
    FLI_IO_REC         * io;
} Free_List_T;

static Free_List_T *fl = NULL;


static void
fl_add_to_freelist( FLI_IO_REC * io )
{
    Free_List_T *cur;

    cur = malloc( sizeof *cur );
    cur->next = fl;
    cur->io   = io;

    fl = cur;
}


/***************************************
 ***************************************/

static void
fl_clear_freelist( void )
{
    Free_List_T *cur;

    while ( fl )
    {
        fl_free( fl->io );
        cur = fl;
        fl = fl->next;
        fl_free( cur );
    }
}


/*
 * Local variables:
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
