#ifndef _DWC_EVENT_C
#define _DWC_EVENT_C

/*--------------------------------------------------------------------------
 *  Copyright (c) 2001-2004 Igor Daniloff. 
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at this directory.
 *------------------------------------------------------------------------*/
 
#ifndef lint
static const char rcsid[] = "$Id: dwc_event.c,v 1.33 2005/09/21 09:20:43 asv Exp $";
#endif

#if defined(__WIN32_DW)
#include <windows.h>
#endif /* WIN32 */
#if defined(__UNIX_DW)
#if defined(EMX_DW)
#include <sys/types.h>
#include <io.h>
#endif /* EMX_DW */
#include <fcntl.h>
#endif /* __UNIX_DW */
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "dwc_macro.h"
#include "dwc_event.h"
#include "dwc_timo.h"
#if defined(__UNIX_DW)
#include "dwc_fd.h"
#include "dwc_lib.h"
#include "dwc_err.h"
#endif /* UNIX */
#if defined(__WIN32_DW)
#include <windows.h>
#include "dw_winlib.h"
#endif /* WIN32 */


/* i.e. BSD systems do not have ETIME */
#ifndef ETIME
#ifndef ETIMEDOUT
#define ETIME       EIO
#else
#define ETIME       ETIMEDOUT
#endif
#endif

struct DWC_int_event_t {
    DWC_string_t      name;
#if defined(__UNIX_DW)
    int               pipefds[2];
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    HANDLE            handle;
#endif /* __WIN32_DW */
};

/* -------------------------------------------------------------------- */
DWC_event_t dwc_event_create( DWC_string_t name )
{
    int         error = 0;
    DWC_event_t event = NULL;

    event = DWC_Malloc( DWC_event_t, sizeof(struct DWC_int_event_t) );
    if( !event )
    {
        error = ENOMEM;
        goto error_exit;
    }
    if( name )
    {
        event->name = DWC_Strdup( name );
    }
    else
    {
        event->name = DWC_Strdup( "anonymous" );
    }
    if( !event->name )
    {
        error = ENOMEM;
        goto error_exit;
    }
#if defined(__UNIX_DW)
    if( pipe( event->pipefds ) < 0 )
    {
        error = errno;
        goto error_exit;
    }
    if( !set_nonblocking_fd( event->pipefds[0] )
        ||
        !set_nonblocking_fd( event->pipefds[1] ) )
    {
        error = errno;
        close( event->pipefds[0] );
        close( event->pipefds[1] );
        goto error_exit;
    }
    printLib( DW_LL_DEBUG, "dwlib: event: '%s'[%d->%d] created", event->name, event->pipefds[1], event->pipefds[0] );
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    event->handle = CreateEvent( NULL, TRUE, FALSE, event->name );
    if( !event->handle )
    {
        error = errno;
        goto error_exit;
    }
    printLib( DW_LL_DEBUG, "dwlib: event: '%s'[%d] created", event->name, event->handle );
#endif /* __WIN32_DW */
    return event;
    
  error_exit:    
    if( event )
    {
        if( event->name )
        {
            printLib( DW_LL_ERROR, "dwlib: event: creating '%s' failed - %s", event->name, stdlib_error( error ) );
            DWC_Free( event->name );
        }
        else if( name )
        {
            printLib( DW_LL_ERROR, "dwlib: event: creating '%s' failed - %s", name, stdlib_error( error ) );
        }
        else
        {
            printLib( DW_LL_ERROR, "dwlib: event: creating failed - %s", stdlib_error( error ) );
        }
        DWC_Free( event );
    }
    else
    {
        if( name )
        {
            printLib( DW_LL_ERROR, "dwlib: event: creating '%s' failed - %s", name, stdlib_error( error ) );
        }
        else
        {
            printLib( DW_LL_ERROR, "dwlib: event: creating failed - %s", stdlib_error( error ) );
        }
    }        
    return NULL;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t dwc_event_wait( DWC_event_t event, DWC_timeout_t timeout )
{
#if defined(__UNIX_DW)
    fd_set              rd_set;
    int                 rd;
    char                inbuf;
    DWC_systimeout_t    sst;
#endif /* __UNIX_DW */
    int                 error = 0;
    DWC_systimeout_t    stimo, *pstimo;
    DWC_error_code_t    retval;

    if( !event || !VALID_TIMEOUT(timeout) )
    {
        printLib( DW_LL_ERROR, "dwlib: event::wait bad parameters were passed" );
        return DWC_BADPARAMS;
    }
#if defined(__UNIX_DW)
 restart:   
#endif /* __UNIX_DW */
    if( timeout == EXPIRED_TIMEOUT )
    {
        printLib( DW_LL_ERROR, "dwlib: event::wait expired timeout for `%s' were passed", event->name );
        return DWC_TIMED;
    }    
    pstimo = timeout_to_system( timeout, &stimo );
#if defined(__UNIX_DW)
    printLib( DW_LL_DEBUG, "dwlib: event: wait for '%s'[%d] with timeout %d started ...", event->name, event->pipefds[0], TIMEOUT_TO_SEC(timeout) );
    DWC_FD_CHECK( event->pipefds[0], "dwlib: event", return DWC_COMMONERROR );
    FD_ZERO( &rd_set );
    FD_SET( event->pipefds[0], &rd_set );
    sst = start_timeout( timeout );
    rd = select( event->pipefds[0]+1, &rd_set, NULL, NULL, pstimo );
    if( (rd == 1) && FD_ISSET( event->pipefds[0], &rd_set ) )
    {
        if( read( event->pipefds[0], &inbuf, 1 ) == 1 )
        {
            error = 0;
            retval = DWC_NOERROR;
        }
        else
        {
            error = errno;
            retval = DWC_COMMONERROR;
        }   
        
    }
    else if( (rd == 0) && pstimo )
    {
        error = ETIME;
        retval = DWC_TIMED;
    }
    else if( WAS_INTERRUPTED )
    { 
        timeout = stop_timeout( timeout, sst );
        goto restart;
    }
    else
    { 
        error = errno;
        retval = DWC_COMMONERROR;
    }
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    error = WaitForSingleObject( event->handle, stimo );
    if( error == WAIT_OBJECT_0 )
    {
        error = 0;
        retval = DWC_NOERROR;
    }
    else if( error == WAIT_TIMEOUT )
    {
        error = ETIME;
        retval = DWC_TIMED;
    }
    else
    {
        error = errno;
        retval = DWC_COMMONERROR;
    }
#endif /* __WIN32_DW */
    if( retval < 0 )
    {
        if( retval != DWC_TIMED )
            printLib( DW_LL_ERROR, "dwlib: event: waiting '%s' failed - %s", event->name, stdlib_error( error ) );
        else
            printLib( DW_LL_DEBUG, "dwlib: event: waiting '%s' timed out", event->name );
    }
    else
    {
#if defined(__UNIX_DW)
        printLib( DW_LL_DEBUG, "dwlib: event: '%s'[%d->!%d] signaled", event->name, event->pipefds[1], event->pipefds[0] );
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
        printLib( DW_LL_DEBUG, "dwlib: event: '%s' signaled", event->name );
#endif /* __WIN32_DW */
    }
    return retval;
}

/* -------------------------------------------------------------------- */
int dwc_event_multiple_wait( DWC_event_t* events, DWC_length_t num, DWC_timeout_t timeout, DWC_bool_t wait_all )
{
#if defined(__UNIX_DW)
    fd_set              rd_set, done_set;
    int                 rd,maxfd, done_num;
    char                inbuf;
    int                 error = 0;
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    DWORD               error = 0;
    HANDLE             *hnd_array;
#endif /* __WIN32_DW */
    unsigned int        i;
    int                 retval = DWC_COMMONERROR;
    DWC_systimeout_t    wa_start, stimo, *pstimo;

    if( !events || (num <= 0) || !VALID_TIMEOUT(timeout) )
    {
        printLib( DW_LL_ERROR, "dwlib: event::multiple_wait bad parameters were passed" );
        return DWC_BADPARAMS;
    }
#if defined(__UNIX_DW)
    FD_ZERO( &done_set );
    done_num = 0;
  wait_all_loop:
#endif /* UNIX */
    if( timeout == EXPIRED_TIMEOUT )
    {
        printLib( DW_LL_ERROR, "dwlib: event::multiple_wait timed out" );
        return DWC_TIMED;
    }    
    wa_start = start_timeout( timeout );
    pstimo = timeout_to_system( timeout, &stimo );
#if defined(__UNIX_DW)
    FD_ZERO( &rd_set );
    for( i = 0, maxfd = 0; i < num; ++i )
    {
        if( !events[i] )
        {
            printLib( DW_LL_ERROR, "dwlib: event::multiple_wait failed - %d-th event invalid", i );
            return DWC_BADPARAMS;
        }
        DWC_FD_CHECK( events[i]->pipefds[0], "dwlib: event", return DWC_COMMONERROR );
        if( !FD_ISSET( events[i]->pipefds[0], &done_set ) )
        {
            FD_SET( events[i]->pipefds[0], &rd_set );
            if( events[i]->pipefds[0] > maxfd )
                maxfd = events[i]->pipefds[0];
        }
    }
    error = 0;
    rd = select( maxfd+1, &rd_set, NULL, NULL, pstimo );
    if( rd > 0 )
    {
        for( i = 0; i < num; ++i )
        {
            if( FD_ISSET( events[i]->pipefds[0], &rd_set ) )
            {
                if( (read( events[i]->pipefds[0], &inbuf, 1 ) == 1) )
                {
                    retval = i;
                    if( wait_all )
                    {
                        printLib( DW_LL_DEBUG, "dwlib: event::multiple_wait '%s' was signaled - wait others", events[i]->name );                        
                        FD_SET( events[i]->pipefds[0], &done_set );
                        ++done_num;
                    }
                }
                else
                {
                    error = errno;
                    retval = DWC_COMMONERROR;
                    break;
                }
                if( !wait_all )
                {
                    break;
                }
            }
        }
        if( wait_all && !error )
        {
            if( done_num < num )
            {
                timeout = stop_timeout( timeout, wa_start );
                goto wait_all_loop;
            }
            printLib( DW_LL_DEBUG, "dwlib: event::multiple_wait success" );                        
        }
    }
    else if( (rd == 0) && pstimo )
    {
        error = ETIME;
        retval = DWC_TIMED;
    }
    else if( WAS_INTERRUPTED )
    { 
        timeout = stop_timeout( timeout, wa_start );
        goto wait_all_loop;
    }
    else
    {
        error = errno;
        retval = DWC_COMMONERROR;
    }
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    hnd_array = DWC_Malloc( HANDLE*, num * sizeof(HANDLE) );
    if( !hnd_array )
    {
        printLib( DW_LL_ERROR, "dwlib: event::multiple_wait events failed - no memory");
        return DWC_NOMEMORY;
    }
    for( i = 0; i < num; ++i )
    {
        if( !events[i] )
        {
            printLib( DW_LL_ERROR, "dwlib: event::multiple_wait %d-th event invalid", i );
            goto error_exit;
        }
        hnd_array[i] = events[i]->handle;
    }
    error = WaitForMultipleObjects( num, hnd_array, wait_all, stimo );
    if( error == WAIT_TIMEOUT )
    {
        error = ETIME;
        retval = DWC_TIMED;
    }
    else if( (WAIT_OBJECT_0 <= error) && (error < WAIT_OBJECT_0 + num) )
    {
        error = 0;
        retval = error - WAIT_OBJECT_0;
    }
    else
    {
        error = errno;
        retval = DWC_COMMONERROR;
    }
  error_exit:
    DWC_Free( hnd_array );
#endif /* __WIN32_DW */
    if( retval < 0  )
    {
        if( retval != DWC_TIMED )
            printLib( DW_LL_ERROR, "dwlib: event::multiple_wait failed - %s", stdlib_error( error ) );
        else
            printLib( DW_LL_VERBOSE, "dwlib: event::multiple_wait timed out" );           
    }
    else
    {
        printLib( DW_LL_DEBUG, "dwlib: event::multiple_wait '%s' signaled", events[retval]->name );
    }
    return retval;
}

/* -------------------------------------------------------------------- */
DWC_bool_t dwc_event_set( DWC_event_t event )
{
    int         error;
    char        outbuf = 'E';

    if( !event )
    {
        printLib( DW_LL_ERROR, "dwlib: event::set null pointer passed " );
        return DWC_BADPARAMS;
    }

    error = 0;
#if defined(__UNIX_DW)
    if( write( event->pipefds[1], &outbuf, 1 ) != 1 )
    {
        printLib( DW_LL_VERBOSE, "dwlib: event: writing to pipe (%s:%d) failed - %s", event->name, event->pipefds[1], stdlib_error( errno ) );
        error = errno;
    }
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    if( !SetEvent( event->handle ) )
    {
        error = errno;
    }
#endif /* __WIN32_DW */
    if( error != 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: event: set '%s' failed - %s", event->name, stdlib_error( error ) );
        return FALSE;
    }
    else
    {
#if defined(__UNIX_DW)
        printLib( DW_LL_DEBUG, "dwlib: event: '%s'[!%d->%d] was set", event->name, event->pipefds[1], event->pipefds[0] );
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
        printLib( DW_LL_DEBUG, "dwlib: event: '%s' was set", event->name );
#endif /* __WIN32_DW */
    }
    return TRUE;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t dwc_event_destroy( DWC_event_t event )
{
    DWC_error_code_t    retcode;
    int                 error;

    if( !event )
    {
        printLib( DW_LL_VERBOSE, "dwlib: event::delete null pointer passed " );
        return DWC_BADPARAMS;
    }
    error = 0;
#if defined(__UNIX_DW)
    printLib( DW_LL_DEBUG, "dwlib: event: close '%s'[%d->%d] was started", event->name, event->pipefds[1], event->pipefds[0] );
    if( close( event->pipefds[0] ) != 0 )
    {
        error = errno;
    }
    if( close( event->pipefds[1] ) != 0 )
    {
        error = errno;
    }
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    if( !CloseHandle( event->handle ) )
    {
        error = errno;
    }
#endif /* __WIN32_DW */
    if( error != 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: event: destroying '%s' failed - %s", event->name, stdlib_error( error ) );
        retcode = DWC_COMMONERROR;
    }
    else
    {
        retcode = DWC_NOERROR;
    }
    printLib( DW_LL_DEBUG, "dwlib: event: '%s' deleted", event->name );
    DWC_Free( event->name );
    DWC_Free( event );
    return retcode;
}

#if defined(__UNIX_DW)
/* -------------------------------------------------------------------- */
DWC_fdsocket_t dwc_event_get_handle( DWC_event_t event )
{
    if( event )
        return event->pipefds[0];
    else
        return INVALID_SOCKET;
}
#endif /* UNIX */

#endif /* _DWC_EVENT_C */
