// This may look like C code, but it is really -*- C++ -*-
// 
// <file> 
// 
// Name:        dispatcher.C
// 
// Purpose:     
// 
// Created:     
// 
// Modified:    2 Nov 95   Joerg Faschingbauer
// 
// Description: 
// 
// $Id: dispatcher.C,v 1.9 1997/02/19 16:34:55 jfasch Exp $
//
// $Log: dispatcher.C,v $
// Revision 1.9  1997/02/19 16:34:55  jfasch
// debug output
//
// Revision 1.8  1997/02/12 23:12:05  jfasch
// debug output
//
// Revision 1.7  1997/02/11 07:32:51  jfasch
// some debug output
//
// Revision 1.6  1997/01/24 14:19:24  jfasch
// link time assertion
//
// Revision 1.5  1996/06/24 14:17:45  jfasch
// deactivated waitForChild() for WIN32
//
// Revision 1.4  1996/05/03 11:42:01  jfasch
// removed some compiler warnings
//
// 
// </file> 
#include "dispatcher.h"

#include "childhandler.h"
#include "chlookup.h"
#include "fdmask.h"
#include "iohandler.h"
#include "timer.h"

#include <hyperg/utils/hgunistd.h>
#include <hyperg/utils/verbose.h>

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/wait.h>


// --------------------------------------------------------------------
const char* Dispatcher :: version1 = "Dispatcher: $Id: dispatcher.C,v 1.9 1997/02/19 16:34:55 jfasch Exp $" ;
Dispatcher* Dispatcher::_instance;

Dispatcher::Dispatcher() {
   _nfds = 0;

   _rmask = new FdMask;
   _wmask = new FdMask;
   _emask = new FdMask;
   _rmaskready = new FdMask;
   _wmaskready = new FdMask;
   _emaskready = new FdMask;

   saved_ = false ;
   rsaved_=new FdMask();
   wsaved_=new FdMask();
   esaved_=new FdMask();
   rrsaved_=new FdMask();
   wrsaved_=new FdMask();
   ersaved_=new FdMask();

   if ((_openMax = sysconf(_SC_OPEN_MAX)) == -1) { /* get current descriptor limit */
      perror("Dispatcher::Dispatcher():sysconf");
      abort();
   }
   if (_openMax > FD_SETSIZE)
      _openMax = FD_SETSIZE;
	 
   _rtable = new IOHandler*[_openMax];
   _wtable = new IOHandler*[_openMax];
   _etable = new IOHandler*[_openMax];
   _queue = new TimerQueue;
   for (int i = 0; i < _openMax; i++) {
      _rtable[i] = nil;
      _wtable[i] = nil;
      _etable[i] = nil;
   }

   dropout_ = false ;
   child_signal_ = false ;
   child_handlers_ = new ChildLookup ;
}

Dispatcher::~Dispatcher() {
   delete _rmask;
   delete _wmask;
   delete _emask;
   delete _rmaskready;
   delete _wmaskready;
   delete _emaskready;

   delete rsaved_;
   delete wsaved_;
   delete esaved_;
   delete rrsaved_;
   delete wrsaved_;
   delete ersaved_;

   delete _rtable;
   delete _wtable;
   delete _etable;
   delete _queue;

   delete child_handlers_ ;
}

Dispatcher& Dispatcher::instance() {
   if (_instance == nil) {
      _instance = new Dispatcher;
   }
   return *_instance;
}

void Dispatcher::instance (Dispatcher* d) { 
   DEBUGNL ("Dispatcher::instance(Dispatcher*)") ;
   _instance = d; 
}

IOHandler* Dispatcher::handler(int fd, DispatcherMask mask) const {
   IOHandler* cur = nil;
   if (mask == ReadMask) {
      cur = _rtable[fd];
   } else if (mask == WriteMask) {
      cur = _wtable[fd];
   } else if (mask == ExceptMask) {
      cur = _etable[fd];
   } else {
      abort();
   }
   return cur;
}

void Dispatcher::link(int fd, DispatcherMask mask, IOHandler* handler) {
   DEBUGNL ("Dispatcher::link(): fd: "<<fd<<", mask: "<<mask<<", handler: "<<handler) ;
   attach(fd, mask, handler) ;

   if (saved_) {
      switch (mask) {
        case Dispatcher::ReadMask: 
           rsaved_->setBit (fd) ;
           _rmask->clrBit (fd) ;
           break ;
        case Dispatcher::WriteMask: 
           wsaved_->setBit (fd) ;
           _wmask->clrBit (fd) ;
           break ;
        case Dispatcher::ExceptMask: 
           esaved_->setBit (fd) ;
           _emask->clrBit (fd) ;
           break ;
      }
   }
}

void Dispatcher :: unlink (int fd) {
   DEBUGNL ("Dispatcher::unlink(): fd: "<<fd) ;
   detach (fd) ;

   if (saved_) {
      rsaved_->clrBit (fd) ;
      wsaved_->clrBit (fd) ;
      esaved_->clrBit (fd) ;
   }
}

void Dispatcher :: unlink (int fd, DispatcherMask m) {
   DEBUGNL ("Dispatcher::unlink(): fd: "<<fd<<", mask: "<<m) ;
   detach(fd, m);

   if (saved_) {
      switch (m) {
        case ReadMask: 
           rsaved_->clrBit (fd) ;
           break ;
        case WriteMask: 
           wsaved_->clrBit (fd) ; 
           break ;
        case ExceptMask: 
           esaved_->clrBit (fd) ;
           break ;
      }
   }
}

void Dispatcher :: attach (int fd, DispatcherMask mask, IOHandler* handler) {
   if (mask == ReadMask) {
      _rmask->setBit(fd);
      _rtable[fd] = handler;
   } else if (mask == WriteMask) {
      _wmask->setBit(fd);
      _wtable[fd] = handler;
   } else if (mask == ExceptMask) {
      _emask->setBit(fd);
      _etable[fd] = handler;
   } else {
      abort();
   }
   if (_nfds < fd+1) {
      _nfds = fd+1;
   }
}

void Dispatcher::detach(int fd) {
   _rmask->clrBit(fd);
   _rtable[fd] = nil;
   _wmask->clrBit(fd);
   _wtable[fd] = nil;
   _emask->clrBit(fd);
   _etable[fd] = nil;
   if (_nfds == fd+1) {
      while (_nfds > 0 && _rtable[_nfds-1] == nil &&
             _wtable[_nfds-1] == nil && _etable[_nfds-1] == nil
         ) {
         _nfds--;
      }
   }
}

void Dispatcher :: detach(int fd, DispatcherMask mask) {
   if (mask == ReadMask) {
      _rmask->clrBit (fd) ;
      _rtable[fd] = nil ;
   }
   else if (mask == WriteMask) {
      _wmask->clrBit (fd) ;
      _wtable[fd] = nil ;
   }
   else if (mask == ExceptMask) {
      _emask->clrBit (fd) ;
      _etable[fd] = nil ;
   }
   
   if (_nfds == fd+1) {
      while (_nfds > 0 && 
             _rtable[_nfds-1] == nil &&
             _wtable[_nfds-1] == nil && 
             _etable[_nfds-1] == nil) {
         _nfds-- ;
      }
   }
}

void Dispatcher::startTimer(long sec, long usec, IOHandler* handler) {
   DEBUGNL ("Dispatcher::startTimer(): handler: "<<handler) ;
   timeval deltaTime;
   deltaTime.tv_sec = sec;
   deltaTime.tv_usec = usec;
   _queue->insert(TimerQueue::currentTime() + deltaTime, handler);
}

void Dispatcher::stopTimer(IOHandler* handler) {
   DEBUGNL ("Dispatcher::stopTimer(): handler: "<<handler) ;
   _queue->remove(handler);
}

boolean Dispatcher::setReady(int fd, DispatcherMask mask) {
   DEBUGNL ("Dispatcher::setReady(): fd: "<<fd<<", mask: "<<mask) ;
   if (handler(fd, mask) == nil) {
      return false;
   }
   if (mask == ReadMask) {
      _rmaskready->setBit(fd);
   } else if (mask == WriteMask) {
      _wmaskready->setBit(fd);
   } else if (mask == ExceptMask) {
      _emaskready->setBit(fd);
   } else {
      return false;
   }
   return true;
}

void Dispatcher::dispatch() {
   dispatch(nil);
}

boolean Dispatcher::dispatch(long& sec, long& usec) {
   timeval howlong;
   timeval prevTime;
   timeval elapsedTime;

   howlong.tv_sec = sec;
   howlong.tv_usec = usec;
   prevTime = TimerQueue::currentTime();

   boolean success = dispatch(&howlong);

   elapsedTime = TimerQueue::currentTime() - prevTime;
   if (howlong > elapsedTime) {
      howlong = howlong - elapsedTime;
   } else {
      howlong = TimerQueue::zeroTime(); /* Used all of timeout */
   }

   sec = howlong.tv_sec;
   usec = howlong.tv_usec;
   return success;
}

boolean Dispatcher::dispatch(timeval* howlong) {
   FdMask rmaskret;
   FdMask wmaskret;
   FdMask emaskret;
   int nfound;

   if (anyReady())
      nfound = fillInReady(rmaskret, wmaskret, emaskret);
   else
      nfound = waitFor(rmaskret, wmaskret, emaskret, howlong);

   if (child_signal_) {
      // a child signal occured. wait() for the child status(es) and
      // notify handlers (if there).
      child_signal_ = false ;
      waitForChild() ;
   }

   notify(nfound, rmaskret, wmaskret, emaskret);

   return (nfound != 0);
}

void Dispatcher :: registerChild (pid_t p, ChildHandler* h) {
   DEBUGNL ("Dispatcher::registerChild(): handler: "<<h<<" ...") ;
   DChildHandler ch (p, h) ;
   child_handlers_->insert (ch) ;
   DEBUGNL ("Dispatcher::registerChild(): done") ;
}

void Dispatcher :: unregisterChild (pid_t p) {
   DChildHandler ch (p) ;
   child_handlers_->remove (ch) ;
}

void Dispatcher :: childSignal() {
   child_signal_ = true ;
   // drop out of the select() loop (waitFor()).
   dropout_ = true ;
}

void Dispatcher :: waitForChild() {
#ifndef WIN32
   DEBUGNL ("Dispatcher::waitForChild()") ;
   pid_t pid ; 
   int status ;
   while ((pid = ::waitpid (-1, &status, WNOHANG)) > 0  ||  (pid < 0 && ::errno == EINTR)) {
      DEBUGNL ("Dispatcher::waitForChild(): pid: "<<pid) ;
      DChildHandler lookup (pid) ;
      int pos ;
      if (child_handlers_->position (lookup, pos)) {
         DChildHandler notify (child_handlers_->operator[](pos)) ;
         DEBUGNL ("Dispatcher::waitForChild(): handler: "<<notify.handler()) ;
         notify.handler()->childEvent (pid, status) ;
      }
   }
#endif
}

void Dispatcher::setActive(const FdMask& rmask, const FdMask& wmask,const FdMask& emask) {
   saved_=true;
   *rsaved_=*_rmask;
   *wsaved_=*_wmask;
   *esaved_=*_emask;
   *rrsaved_=*_rmaskready;
   *wrsaved_=*_wmaskready;
   *ersaved_=*_emaskready;
   *_rmask=rmask;
   *_wmask=wmask;
   *_emask=emask;
   _rmaskready->zero();
   _wmaskready->zero();
   _emaskready->zero();
}

void Dispatcher::resetActive() {
   saved_=false;
   *_rmask=*rsaved_;
   *_wmask=*wsaved_;
   *_emask=*esaved_;
   *_rmaskready=*rrsaved_;
   *_wmaskready=*wrsaved_;
   *_emaskready=*ersaved_;
}

boolean Dispatcher::anyReady() const {
   return
      _rmaskready->anySet() || _wmaskready->anySet() || _emaskready->anySet();
}

int Dispatcher::fillInReady(
   FdMask& rmaskret, FdMask& wmaskret, FdMask& emaskret
   ) {
      rmaskret = *_rmaskready;
      wmaskret = *_wmaskready;
      emaskret = *_emaskready;
      _rmaskready->zero();
      _wmaskready->zero();
      _emaskready->zero();
      return rmaskret.numSet() + wmaskret.numSet() + emaskret.numSet();
}

int Dispatcher::waitFor(
   FdMask& rmaskret, FdMask& wmaskret, FdMask& emaskret, timeval* howlong
   ) {
      int nfound = 0 ;

      if (! dropout_) {
         do {
            rmaskret = *_rmask;
            wmaskret = *_wmask;
            emaskret = *_emask;
            howlong = calculateTimeout(howlong);
            
            /* mpichler, 19950710 */
#if defined(HPUX) && defined(__GNUC__)
            nfound = select(_nfds, (int*) &rmaskret, (int*) &wmaskret, (int*) &emaskret, howlong);
#else
            nfound = select(_nfds, &rmaskret, &wmaskret, &emaskret, howlong);
#endif
            
#ifdef sgi
            for (int i = 0; i < _nfds; i++) {
               if (rmaskret.isSet(i) && !_rmask->isSet(i)) {
                  rmaskret.clrBit(i);
               }
               if (wmaskret.isSet(i) && !_wmask->isSet(i)) {
                  wmaskret.clrBit(i);
               }
               if (emaskret.isSet(i) && !_emask->isSet(i)) {
                  emaskret.clrBit(i);
               }
            }
#endif
            
            if (nfound < 0) {
               handleError();
            }
         } while (nfound < 0  &&  ! dropout_) ;
      }

      dropout_ = false ;

      return nfound;		/* Timed out or input available */
}

void Dispatcher::notify(
   int nfound, FdMask& rmaskret, FdMask& wmaskret, FdMask& emaskret
   ) {
      for (int i = 0; i < _nfds && nfound > 0; i++) {
         if (rmaskret.isSet(i)) {
            DEBUGNL ("Dispatcher::notify(): readmask, fd: "<<i) ;
            int status ;
            if (_rtable[i])
               status = _rtable[i]->inputReady(i) ;
            else {
               DEBUGNL ("Dispatcher::notify(): nil handler") ;
               status = 0 ;
            }
	    if (status < 0) {
               detach(i);
	    } else if (status > 0) {
               _rmaskready->setBit(i);
	    }
	    nfound--;
         }
         if (wmaskret.isSet(i)) {
            DEBUGNL ("Dispatcher::notify(): writemask, fd: "<<i) ;
            int status ;
            if (_wtable[i]) 
               status = _wtable[i]->outputReady(i) ;
            else {
               DEBUGNL ("Dispatcher::notify(): nil handler") ;
               status = 0 ;
            }
	    if (status < 0) {
               detach(i);
	    } else if (status > 0) {
               _wmaskready->setBit(i);
	    }
	    nfound--;
         }
         if (emaskret.isSet(i)) {
            DEBUGNL ("Dispatcher::notify(): exceptmask, fd: "<<i) ;
            int status ;
            if (_etable[i]) 
               status = _etable[i]->exceptionRaised(i) ;
            else {
               DEBUGNL ("Dispatcher::notify(): nil handler") ;
               status = 0 ;
            }
	    if (status < 0) {
               detach(i);
	    } else if (status > 0) {
               _emaskready->setBit(i);
	    }
	    nfound--;
         }
      }

      if (!_queue->isEmpty()) {
         _queue->expire(TimerQueue::currentTime());
      }
}

timeval* Dispatcher::calculateTimeout(timeval* howlong) const {
   static timeval timeout;

   if (!_queue->isEmpty()) {
      timeval curTime;

      curTime = TimerQueue::currentTime();
      if (_queue->earliestTime() > curTime) {
         timeout = _queue->earliestTime() - curTime;
         if (howlong == nil || *howlong > timeout) {
            howlong = &timeout;
         }
      } else {
         timeout = TimerQueue::zeroTime();
         howlong = &timeout;
      }
   }
   return howlong;
}

void Dispatcher::handleError() {
   if (errno == EINTR) {
      return;
   }

   if (errno == EBADF) {
      checkConnections();
      return;
   }

   perror("Dispatcher: select");
   exit(1);
}

void Dispatcher::checkConnections() {
   FdMask rmask;
   timeval poll = TimerQueue::zeroTime();

   for (int fd = 0; fd < _nfds; fd++) {
      if (_rtable[fd] != nil) {
         rmask.setBit(fd);
/* mpichler, 19950710 */
#if defined(HPUX) && defined(__GNUC__)
         if (select(fd+1, (int*) &rmask, nil, nil, &poll) < 0)
#else
	    if (select(fd+1, &rmask, nil, nil, &poll) < 0)
#endif
               detach(fd);
         rmask.clrBit(fd);
         }
      }
   }
