/* C support for scsh select call.
** Copyright (c) 1995 by Olin Shivers.
*/

#include "sysdep.h"

#include <sys/types.h>
#if defined(HAVE_SYS_SELECT_H)
#  include <sys/select.h>
#endif
#include <sys/time.h>

#include <errno.h>
#include <stdio.h>

#include "cstuff.h"
#include "fdports.h"	/* Accessors for Scheme I/O port internals. */
#include "fdports1.h"	/* Import fdes2fstar(). */
#include "machine/stdio_dep.h"	/* Import stdio buf-peeking ops. */

/* Make sure our exports match up w/the implementation: */
#include "select1.h"

/* the traditional sleazy max non-function. */
#define max(a,b) (((a) > (b)) ? (a) : (b))

extern FILE *fdes2fstar(int fd);

static void or2_fdset(fd_set *x, fd_set *y, int max_elt);
static int copyback_fdvec(scheme_value portvec, fd_set *fdset);

/* RVEC, WVEC, and EVEC are Scheme vectors of integer file descriptors,
** I/O ports, and #f's. NSECS is an integer timeout value, or #f for
** infinite wait. Do the select() call, returning result fd_sets in the
** passed pointers. Return 0 for OK, otherwise error is in errno.
*/

int do_select(scheme_value rvec, scheme_value wvec,
	      scheme_value evec, scheme_value nsecs,
	      fd_set *rset_ans, fd_set *wset_ans, fd_set *eset_ans)
{
    struct timeval timeout, *tptr;
    fd_set rset_bufrdy, wset_bufrdy, eset_bufrdy;    /* Buffered port hits. */
    int rbuf_rdy=0, wbuf_rdy=0, bufrdy;	/* Set if we find buffered I/O hits. */
    int max_fd = -1;			/* Max fdes in the sets. */
    int nelts, i;
    int nfound;

    FD_ZERO(rset_ans);		FD_ZERO(wset_ans);	FD_ZERO(eset_ans);
    FD_ZERO(&rset_bufrdy);	FD_ZERO(&wset_bufrdy);	FD_ZERO(&eset_bufrdy);

    /* Scan the readvec elts. */
    nelts = VECTOR_LENGTH(rvec);
    for(i=nelts; --i >= 0; ) {
	scheme_value elt = VECTOR_REF(rvec,i);
	int fd;

	if( FIXNUMP(elt) ) {			    /* It's an integer fdes. */
	    fd = EXTRACT_FIXNUM(elt);
	    FD_SET(fd, rset_ans);
	    }

	else if( elt != SCHFALSE ) {		    /* It better be a port. */
	    FILE *f;
	    scheme_value data = *Port_PortData(elt);
	    fd = EXTRACT_FIXNUM(*PortData_Fd(data));
	    f = fdes2fstar(fd);
	    if( !f ) return -1;
	    else if( *PortData_Peek(data) != SCHFALSE /* Port has a peekchar */
		     || !ibuf_empty(f) ) { 	     /* Stdio buf has chars. */
		FD_SET(fd, &rset_bufrdy);
		rbuf_rdy = 1; /* Hit. */
		}
	    else FD_SET(fd, rset_ans);		  /* No buffered data. */
	    }

	max_fd = max(max_fd, fd);
	}

    /* Scan the writevec elts. */
    nelts = VECTOR_LENGTH(wvec);
    for(i=nelts; --i >= 0; ) {
	scheme_value elt = VECTOR_REF(wvec,i);
	int fd;

	if( FIXNUMP(elt) ) {			    /* It's an integer fdes. */
	    fd = EXTRACT_FIXNUM(elt);
	    FD_SET(fd, wset_ans);
	    }

	else if( elt != SCHFALSE ) {		    /* It better be a port. */
	    FILE *f;
	    fd = EXTRACT_FIXNUM(*PortFd(elt));
	    f = fdes2fstar(fd);
	    if( !f ) return -1;
	    else if( !obuf_full(f) ) {		  /* I/O buf has room. */
		FD_SET(fd, &wset_bufrdy);		
		wbuf_rdy = 1; /* Hit. */
		}
	    else FD_SET(fd, wset_ans);		  /* No room. */
	    }

	max_fd = max(max_fd, fd);
	}

    /* Scan the exception-vec elts. */
    nelts = VECTOR_LENGTH(evec);
    for(i=nelts; --i >= 0; ) {
	scheme_value elt = VECTOR_REF(evec,i);
	int fd;

	if( FIXNUMP(elt) ) {			/* It's an integer fdes. */
	    fd = EXTRACT_FIXNUM(elt);
	    FD_SET(fd, rset_ans);
	    }

	else if( elt != SCHFALSE ) {		    /* It better be a port. */
	    fd = EXTRACT_FIXNUM(*PortFd(elt));
	    FD_SET(fd, rset_ans);
	    }

	max_fd = max(max_fd, fd);
	}

    bufrdy = rbuf_rdy || wbuf_rdy;
    if( bufrdy ) {		/* Already have some hits on buffered ports, */
	timeout.tv_sec = 0;	/* so we only poll the others.               */
	timeout.tv_usec = 0;
	tptr = &timeout;
	}
    else if ( FIXNUMP(nsecs) ) {
	timeout.tv_sec = EXTRACT_FIXNUM(nsecs);	/* Wait n seconds. */
	timeout.tv_usec = 0;
	tptr = &timeout;
	}
    else tptr = NULL;				/* #f => Infinite wait. */

    /* select1() is defined in sysdep.h -- bogus compatibility macro. */
    nfound = select1(max_fd+1, rset_ans, wset_ans, eset_ans, tptr); /* Do it.*/

    /* EINTR is not an error return if we have hits on buffered ports
    ** to report.
    */
    if( nfound < 0 )
	if ( errno != EINTR || !bufrdy ) return -1;
	else {	/* EINTR, but we have hits on buffered ports to report.  */
	    FD_ZERO(rset_ans);		/* This should never happen --   */
	    FD_ZERO(wset_ans);		/* EINTR on a zero-sec select()  */
	    FD_ZERO(eset_ans);		/* -- but I'm paranoid.          */
	    }

    /* OR together the buffered-io ready sets and the fd ready sets. */
    if( rbuf_rdy ) or2_fdset(rset_ans, &rset_bufrdy, max_fd);
    if( wbuf_rdy ) or2_fdset(wset_ans, &wset_bufrdy, max_fd);

    return 0;
    }



/* x = x or y */
static void or2_fdset(fd_set *x, fd_set *y, int max_elt)
{
    int i;
    for(i=max_elt+1; --i >= 0;)
	if( FD_ISSET(i,y) ) FD_SET(i,x);
    }



/* PORTVEC is a vector of integer file descriptors and Scheme ports.
** Scan over the vector, and copy any elt whose file descriptor is in FDSET
** to the front of the vector. Return the number of elts thus copied.
*/
static int copyback_fdvec(scheme_value portvec, fd_set *fdset)
{
    int vlen = VECTOR_LENGTH(portvec);
    int i, j=0;
    for( i = -1; ++i < vlen; ) {
	scheme_value elt = VECTOR_REF(portvec, i);
	int fd = EXTRACT_FIXNUM((FIXNUMP(elt)) ? elt : *PortFd(elt));
	if( FD_ISSET(fd,fdset) ) {
	    FD_CLR(fd,fdset);	/* In case luser put elt in multiple times. */
	    VECTOR_REF(portvec, j) = elt;
	    j++;
	    }
	}
    return j;
    }


/* Overwrite every inactive element in the vector with #f;
** Return count of active elements.
*/

static int clobber_inactives(scheme_value portvec, fd_set *fdset)
{
    int count = 0;
    int i = VECTOR_LENGTH(portvec);

    while( --i >= 0 ) {
	scheme_value elt = VECTOR_REF(portvec, i);
	if( elt != SCHFALSE ) {
	    int fd = EXTRACT_FIXNUM((FIXNUMP(elt)) ? elt : *PortFd(elt));
	    if( FD_ISSET(fd,fdset) ) {
		FD_CLR(fd,fdset); /* In case luser put elt in multiple times. */
		++count;
		}
	    else VECTOR_REF(portvec, i) = SCHFALSE; /* Clobber. */
	    }
	}
    return count;
    }


/* These two functions are the entry points to this file.
*********************************************************
*/

/* Copy active elts back to the front of their vector;
** Return error indicator & number of hits for each vector.
*/

scheme_value select_copyback(scheme_value rvec, scheme_value wvec,
			     scheme_value evec, scheme_value nsecs,
			     int *r_numrdy, int *w_numrdy, int *e_numrdy)
{
    fd_set rset, wset, eset;

    if( do_select(rvec, wvec, evec, nsecs, &rset, &wset, &eset) ) {
	*r_numrdy = *w_numrdy = *e_numrdy = 0;
	return ENTER_FIXNUM(errno);
	}
    
    *r_numrdy = copyback_fdvec(rvec, &rset);
    *w_numrdy = copyback_fdvec(wvec, &wset);
    *e_numrdy = copyback_fdvec(evec, &eset);
    return SCHFALSE;
    }


/* Overwrite non-active elements in the vectors with #f;
** return error indicator & number of hits for each vector.
*/

scheme_value select_filter(scheme_value rvec, scheme_value wvec,
			   scheme_value evec, scheme_value nsecs,
			   int *r_numrdy, int *w_numrdy, int *e_numrdy)
{
    fd_set rset, wset, eset;

    if( do_select(rvec, wvec, evec, nsecs, &rset, &wset, &eset) ) {
	*r_numrdy = *w_numrdy = *e_numrdy = 0;
	return ENTER_FIXNUM(errno);
	}
    
    *r_numrdy = clobber_inactives(rvec, &rset);
    *w_numrdy = clobber_inactives(wvec, &wset);
    *e_numrdy = clobber_inactives(evec, &eset);
    return SCHFALSE;
    }
