// Created by Cyrus Patel (cyp@fb14.uni-mainz.de) 
// Copyright distributed.net 1997-1999 - All Rights Reserved
// For use in distributed.net projects only.
// Any other distribution or use of this source violates copyright.
//
// The polling process is essentially an event handler that gets called 
// when "idle events" get queued, ie the main process sleeps. Procedures
// registered with the polling process then get called synchronously 
// (subject to priority and round-robin order).
//
// The advantages, from the client's perspective, are many-fold: 
// For one, the line between "multi-threaded" and "single-threaded" clients 
// vanishes (silly terminology from the client's perspective anyway; a
// threaded client is not faster that a non-threaded one on a single
// processor machine), which cuts down on the need to maintain separate
// source or binaries. Secondly, its more efficient on platforms that do
// not support threading: Multiple 'small problems' running back-to-back 
// have less overhead that 'big problems' (still <3 seconds) since the
// time-till-stop can be controlled precisely. Thirdly, it is more efficient 
// on platforms that do support threading, but are non-preemptive. That is, 
// the number of context switches can be kept at a minimum while still 
// maintaining exact control over when when the client _should_ yield.
// Fourthly, it is useful even in preemptive environments since routine 
// work can be delegated (with a run-at time stamp if necessary) without
// having to spin off another thread and without having oodles of 
// platform-specific code mucking about in real client code. Fifth: it
// does away with the 'timeslice factor' crutch.
//
// $Log: pollsys.cpp,v $
// Revision 1.7  1999/01/29 18:46:43  jlawson
// fixed formatting.
//
// Revision 1.6  1999/01/01 02:45:16  cramer
// Part 1 of 1999 Copyright updates...
//
// Revision 1.5  1998/11/06 04:26:26  cyp
// Fixed a bug that would show up if the number of procedures registered was
// an uneven number >= 11.
//
// Revision 1.4  1998/11/02 04:48:42  cyp
// Minor fix to suppress looping if there are no events in the queue.
// Previously it would suppress looping only if there was no queue (at all).
//
// Revision 1.3  1998/09/28 22:01:29  remi
// Cleared a gcc 2.7.2.2 warning about 'register' parameters in
// RegPolledProcedure.
//
// Revision 1.2  1998/09/28 21:07:24  remi
// Cleared 3 "might be used uninitialised" warnings.
//
// Revision 1.1  1998/09/28 02:52:24  cyp
// Created.
//
// 
//
#if (!defined(lint) && defined(__showids__))
const char *pollsys_cpp(void) {
return "@(#)$Id: pollsys.cpp,v 1.7 1999/01/29 18:46:43 jlawson Exp $"; }
#endif

//-------------------------------------------------------------------------

#include "baseincs.h"  /* NULL, malloc */
#include "clitime.h"   /* CliTimer() */
#include "pollsys.h"   /* ourselves to keep prototypes in sync */
#define __SLEEP_FOR_POLLING__
#include "sleepdef.h"

#ifdef USLEEP_IS_SLEEP
#include "network.h"
#undef usleep
void usleep( unsigned int usecs )
{
  struct timeval tv;
  tv.sec = 0; tv.usec = usecs;
  select(0,NULL,NULL,NULL,&tv);
}
#endif


/* -------------------------------------------------------------------- */

#define MAX_POLL_RUNLEVEL 32 /* ie priority, 0 is lowest/slowest */

struct polldata
{
  struct polldata *next;
  unsigned int priority; /* 0 is lowest/slowest */
  int chainhead;
  int inuse;
  int fd;
  void (*proc)(void *);
  void *arg;
  struct timeval execat; 
};

static struct
{
  struct polldata *runlist;
  unsigned int regcount;
  struct polldata *nextrun[MAX_POLL_RUNLEVEL+1];
} pollsysdata = { NULL, 0, {NULL}};

// UnregPolledProcedure() unregisters a procedure previously registered with
// RegPolledProcedure(). Procedures are auto unregistered when executed.

int UnregPolledProcedure( int fd )
{
  struct polldata *thisp;
  int rc = -1;
  
  thisp = pollsysdata.runlist;
  while ( thisp )
  {
    if (thisp->fd == fd)
    {
      if (thisp->inuse)
      {
        thisp->inuse = 0;
        rc = 0;
      }
      pollsysdata.regcount--;
      break;
    }
    thisp = thisp->next;
  }
  return rc;
}  

// RegPolledProcedure() adds a procedure to be called from the polling loop.
// Procedures may *not* use sleep() or usleep() directly! (Its a stack issue, 
// not a reentrancy problem). Procedures are automatically unregistered 
// when called (they can re-register themselves). The 'interval' argument 
// specifies how much time must elapse before the proc is scheduled to run - 
// the default is {0,0}, ie schedule as soon as possible. Returns a non-zero 
// handle on success or -1 if error. Care should be taken to ensure that
// procedures registered with a high priority have an interval long enough
// to allow procedures with a low(er) priority to run.

int RegPolledProcedure( void (*proc)(void *), void *arg, 
                        struct timeval *interval, unsigned int priority ) 
{
  struct polldata *thatp, *thisp, *chaintail;
  unsigned int i, mcount;
  int fd = -1;

  if (proc)
  {
    thisp = pollsysdata.runlist;
    chaintail = NULL;
    while ( thisp )
    {
      fd = thisp->fd;
      if (!thisp->inuse)
        break;
      chaintail = thisp;
      thisp = thisp->next;
    }
    if ( !thisp )
    {
      if (!pollsysdata.runlist)
      {
        for (i = 0; i < (sizeof(pollsysdata.nextrun)/
            sizeof(pollsysdata.nextrun[0])); i++)
          pollsysdata.nextrun[i]=NULL;
      }
      mcount = 1024/(sizeof(struct polldata));
      thisp = (struct polldata *)(malloc( mcount * sizeof(struct polldata) ));
      if ( thisp )
      {
        thatp = thisp;
        if (fd == -1)
          fd = 0;
        for (i = 0; i < mcount; i++)
        {
          thatp->next = (i<(mcount-1))?(thatp+1):(NULL);
          thatp->chainhead = (i==0);
          thatp->proc = NULL;  
          thatp->inuse = 0;  
          thatp->fd = ++fd;
          thatp++;
        }
        if (pollsysdata.runlist == NULL)
          pollsysdata.runlist = thisp;
        else
          chaintail->next = thisp;
      }
    }
    if ( !thisp )
      fd = -1;
    else
    {
      pollsysdata.regcount++;
      fd = thisp->fd;
      thisp->inuse = 1;
      thisp->proc = proc;
      thisp->arg  = arg;
      thisp->priority = priority;
      if (priority >= MAX_POLL_RUNLEVEL)
        thisp->priority = MAX_POLL_RUNLEVEL-1;
      CliTimer( &thisp->execat );
      if (interval)
      {
        thisp->execat.tv_sec += interval->tv_sec;
        thisp->execat.tv_usec += interval->tv_usec;
        thisp->execat.tv_sec += thisp->execat.tv_usec/1000000;
        thisp->execat.tv_usec %= 1000000;
      }
    }
  }
  return (fd);
}  


static void __initchk(void *dummy) { dummy = dummy; }
int InitializePolling(void)
{ 
  int fd;
  if (pollsysdata.runlist != NULL)
    return 0;
  if ((fd = RegPolledProcedure( __initchk, NULL, NULL, 0 )) != -1)
  {
    UnregPolledProcedure( fd ); /* remove it from the queue */
    return 0;
  }
  return -1;
}


int DeinitializePolling(void)
{
  struct polldata *thatp, *thisp;

  thisp = pollsysdata.runlist;
  thatp = pollsysdata.runlist = NULL;

  while ( thisp )
  {
    if ( thisp->chainhead )
    {
      if ( thatp )
        free((void *)(thatp));
      thatp = thisp;
    }
    thisp = thisp->next;
    if (!thisp && thatp)
      free((void *)(thatp));
  }
  return 0;
}  

void __RunPollingLoop( unsigned int secs, unsigned int usecs )
{
  static unsigned int isrunning = 0;
  struct timeval now, until;
  struct polldata *thisp, *nextp = NULL;
  void *arg = NULL;
  unsigned int runprio;
  register void (*proc)(void *) = NULL;
  int reclock, loopend, dorun;

  if ((++isrunning) > 1)
  {
    fprintf(stderr, "call to sleep when no sleep allowed!");
  }
  else if (!pollsysdata.runlist || pollsysdata.regcount==0)
  {
    if ( secs )
      sleep( secs );
    if ( usecs || !secs )
      usleep( usecs );
  }
  else
  {
    CliTimer( &now );
    until.tv_usec = now.tv_usec;
    until.tv_sec = now.tv_sec;
    until.tv_usec += usecs;
    until.tv_sec += secs;
    until.tv_sec += until.tv_usec / 1000000;
    until.tv_usec %= 1000000;
  
    runprio = MAX_POLL_RUNLEVEL;
    
    do
    {
      thisp = NULL;
      reclock = 0;
//printf("o%d", runprio);

      do
      {
        dorun = 0;
//printf("i");
      
        //could lock MUTEX here 
        if ( !pollsysdata.runlist )
        {
          loopend = 1;
          reclock = 0;
        }
        else
        {
          //lock MUTEX here
          if ( !thisp )
          {
            if ( !pollsysdata.nextrun[runprio] )
              pollsysdata.nextrun[runprio] = pollsysdata.runlist;
            thisp = pollsysdata.nextrun[runprio];
          }
          if ((nextp = thisp->next) == NULL)
            nextp = pollsysdata.runlist;
          loopend = ( !nextp || 
                (pollsysdata.nextrun[runprio])->fd == nextp->fd );
          if (thisp->inuse)
          {
            if ((thisp->priority == runprio) &&
              (( now.tv_sec > thisp->execat.tv_sec ) ||
              (( thisp->execat.tv_sec == now.tv_sec ) && 
              ( now.tv_usec >= thisp->execat.tv_usec ))))
            {
              arg = thisp->arg;
              proc = thisp->proc;
              thisp->inuse = 0;
              pollsysdata.regcount--;
              dorun = 1;
              reclock = 1;
              pollsysdata.nextrun[runprio] = nextp;
              loopend = 1;
//printf("r%dp%d", thisp->fd, thisp->priority );
            }
          }
        }
        //could unlock MUTEX here
        
        if (dorun)
          (*proc)(arg);
        thisp = nextp;
      } while (!loopend);
      
      if (reclock) /* ran at that level */
        runprio = MAX_POLL_RUNLEVEL; /* start over */
      else if (runprio > 0)
        runprio--;
      else
      {
        runprio = MAX_POLL_RUNLEVEL;
        if (( now.tv_sec < until.tv_sec ) || (( now.tv_sec == until.tv_sec ) 
          && ( now.tv_usec < until.tv_usec )))
          usleep(100); 
        reclock = 1;
      }

      if (reclock)
        CliTimer( &now );
           
    } while (( now.tv_sec < until.tv_sec ) || 
              (( now.tv_sec == until.tv_sec ) && 
               ( now.tv_usec < until.tv_usec )));
  }
    
  --isrunning;
  return;           
}

// PolledSleep() and PolledUSleep() are automatic/default replacements for 
// sleep() and usleep() (see sleepdef.h) and allow the polling process to run.

void PolledSleep( unsigned int seconds )   
{ __RunPollingLoop( seconds, 0 ); }

void PolledUSleep( unsigned int useconds)  
{ __RunPollingLoop( 0, useconds ); }

// NonPolledSleep() and NonPolledUSleep() are "real" sleepers. This are 
// required for real threads (a la Go_mt()) that need to yield control to 
// other threads.

void NonPolledSleep( unsigned int seconds) 
{ sleep( seconds ); }

void NonPolledUSleep(unsigned int useconds)
{ usleep( useconds ); }

