/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define KERNEL
#include "ixemul.h"

#include <sys/time.h>

#include "select.h"

#define __time_req (u.u_time_req)
#define __tport    (u.u_sync_mp)

static void inline setcopy(int nfd, u_int *ifd, u_int *ofd)
{
  /* this procedure is here, because it's "normal" that if you only
   * want to select on fd 0,1,2 eg. you only pass a long to select,
   * not a whole fd_set, so we can't simply copy over results in the
   * full size of an fd_set.. */
  
  /* we have to copy that many longs... */
  nfd = (nfd+31) >> 5;
  while (nfd--) *ofd++ = *ifd++;
}

int
select(int nfd, fd_set *ifd, fd_set *ofd, fd_set *efd, struct timeval *timeout)
{
  struct file *f;
  int i, waitin, waitout, waitexc, dotout;
  int result, omask;
  u_int wait_sigs;
  struct timeval end_time;

  /* as long as I don't support anything similar to a network, I surely
   * won't get any exceptional conditions, so *efd is mapped into 
   * *ifd, if it's set. */
   
  /* first check, that all included descriptors are valid and support
   * the requested operation. If the user included a request to wait
   * for a descriptor to be ready to read, while the descriptor was only
   * opened for writing, the requested bit is immediately cleared
   */
  waitin = waitout = waitexc = 0;
  if (nfd > NOFILE) nfd = NOFILE;

  for (i = 0; i < nfd; i++)
    {
      if (ifd && FD_ISSET(i, ifd) && (f = u.u_ofile[i]))
	{
	  if (!f->f_read || !f->f_select)
	    FD_CLR(i, ifd);
	  else
	    ++waitin;
	}
      if (ofd && FD_ISSET(i, ofd) && (f = u.u_ofile[i]))
	{
	  if (!f->f_write || !f->f_select)
	    FD_CLR(i, ofd);
	  else
	    ++waitout;
	}
      if (efd && FD_ISSET(i, efd) && (f = u.u_ofile[i]))
	{
	  /* question: can an exceptional condition also occur on a 
	   * write-only fd?? */
	  if (!f->f_read || !f->f_select)
	    FD_CLR(i, efd);
	  else
	    ++waitout;
	}
    }

  if (0)
    {
      /* this belongs logically here, not at the end of the function */
badfd:
      errno = EBADF;
      return -1;
    }


  /* now, if we see, that we're not waiting for anything, AND if
   * timeout is NULL (not 0 seconds!), than select() is just something
   * like a very complex pause() call ;-)) */
  if (waitin + waitout + waitexc == 0 && !timeout)
    return syscall (SYS_pause);

  if (dotout=(timeout && timerisset(timeout)))
    {
      /* remember the time we have to leave (timeout) */
      syscall (SYS_gettimeofday, &end_time, 0);
      end_time.tv_usec += timeout->tv_usec;
      /* this conversion should be cheaper than a division and a modulo.. */
      while (end_time.tv_usec >= 1000000)
        {
	  end_time.tv_usec -= 1000000;
	  end_time.tv_sec++;
	}
      end_time.tv_sec += timeout->tv_sec;
    }

  /* have to make sure we can clean up the timer-request ! */
  omask = syscall (SYS_sigsetmask, ~0);

  for (;;)
    {
      fd_set readyin, readyout, readyexc;
      int tout, readydesc;

      FD_ZERO(&readyin);
      FD_ZERO(&readyout);
      FD_ZERO(&readyexc);

      tout = 
        readydesc = 0;

      /* have to always wait for the `traditional' ^C and the library internal
       * sleep_sig as well */
      wait_sigs = SIGBREAKF_CTRL_C | (1 << u.u_sleep_sig);

      /* note: we never post a timeout request of less than SELTIMEOUT, so
       * select() precision depends on SELTIMEOUT. However, considering that
       * this call emulates a Unix syscall, this comes quite near to Unix
       * precision, if not better ;-)) */
      if (dotout)
	{
	  __time_req->tr_time.tv_sec = 0;
	  __time_req->tr_time.tv_usec = SELTIMEOUT;
          __time_req->tr_node.io_Command = TR_ADDREQUEST;
          SendIO((struct IORequest *)__time_req);
	  wait_sigs |= 1 << __tport->mp_SigBit;
        }

      /* have all watched files get prepared for selecting */
      for (i = 0; i < nfd; i++)
	{
	  if (ifd && FD_ISSET (i, ifd) && (f = u.u_ofile[i]))
	    wait_sigs |= f->f_select (f, SELCMD_PREPARE, SELMODE_IN);
	  if (ofd && FD_ISSET (i, ofd) && (f = u.u_ofile[i]))
	    wait_sigs |= f->f_select (f, SELCMD_PREPARE, SELMODE_OUT);
	  if (efd && FD_ISSET (i, efd) && (f = u.u_ofile[i]))
	    wait_sigs |= f->f_select (f, SELCMD_PREPARE, SELMODE_EXC);
	}

      /* now wait for all legally possible signals, this includes BSD
       * signals (but want at least one signal set!) */
      while (! Wait (wait_sigs)) ;

      /* no matter what caused Wait() to return, wait for all requests to
       * complete (we CAN'T abort a DOS packet, sigh..) */
      if (dotout)
        {
	  /* IMPORTANT: unqueue the timer request BEFORE polling the fd's,
	   *            or __wait_packet() will treat the timer request
	   *            as a packet... */

	  if (! CheckIO ((struct IORequest *)__time_req))
            AbortIO ((struct IORequest *)__time_req);
          WaitIO ((struct IORequest *)__time_req);
        }

      /* collect information from the file descriptors */
      for (i = 0; i < nfd; i++)
	{
	  if (ifd && FD_ISSET (i, ifd) && (f = u.u_ofile[i])
	      && f->f_select (f, SELCMD_CHECK, SELMODE_IN))
	    {
	      FD_SET (i, &readyin);
	      ++ readydesc;
	    }
	  if (ofd && FD_ISSET (i, ofd) && (f = u.u_ofile[i])
	      && f->f_select (f, SELCMD_CHECK, SELMODE_OUT))
	    {
	      FD_SET (i, &readyout);
	      ++ readydesc;
	    }
	  if (ofd && FD_ISSET (i, efd) && (f = u.u_ofile[i])
              && f->f_select (f, SELCMD_CHECK, SELMODE_EXC))
	    {
	      FD_SET (i, &readyexc);
	      ++ readydesc;
	    }
	}

      /* we have a timeout condition, if readydesc == 0, dotout == 1 and 
       * end_time < current time */
      if (!readydesc && dotout)
        {
          struct timeval current_time;
          
          syscall (SYS_gettimeofday, &current_time, 0);
          tout = timercmp (&end_time, &current_time, <);
	}

      if (readydesc || tout)
	{
	  if (ifd) setcopy(nfd, (u_int *)&readyin,  (u_int *)ifd);
	  if (ofd) setcopy(nfd, (u_int *)&readyout, (u_int *)ofd);
	  if (efd) setcopy(nfd, (u_int *)&readyexc, (u_int *)efd);
	  result = readydesc; /* ok for tout, since then readydesc is already 0 */
	  break;
	}
    }

  syscall (SYS_sigsetmask, omask);

  return result;
}
