// gorasche 120197
// this is a part of the new dispatcher for the HyperWave software suite
// it dispatches events for several types of objects (sockets, file descr. events mutexes etc.)
// it is currently implementing a subset i.e. waiting for process objects.

#include <hyperg/utils/arrays.h>
#include <hyperg/utils/new.h>
#include <hyperg/utils/verbose.h>
#include <hyperg/utils/assert.h>
#include <hyperg/utils/map_t.h>

#include "childhandler.h"
#include "nt-dispatcher.h"


typedef ChildHandler* pChildHandler;

NTDispatcher* NTDispatcher::instance_=nil;

Mapsdeclare (ChildLookup, HANDLE, pChildHandler) ;
Mapsimplement (ChildLookup, HANDLE, pChildHandler) ;

Arraysdeclare (HANDLEArray, HANDLE) ;
Arraysimplement (HANDLEArray, HANDLE) ;

NTDispatcher::NTDispatcher()
: children_(HGNEW (ChildLookup)),
  all_handles_(HGNEW (HANDLEArray))
{
  // create an unnamed automatic reset event, non inheritable etc.
  BreakEvent_=CreateEvent(0,1,0,0);

  // and insert it into the handle array
  all_handles_->insert(BreakEvent_);
}

NTDispatcher::~NTDispatcher()
{
  // close all process handles

  // free the event object
  CloseHandle(BreakEvent_);

  HGDELETE(children_);
  HGDELETE(all_handles_);
}

NTDispatcher& NTDispatcher::instance()
{
  // if there is no instance create one!
  if(instance_==nil)
  {
    instance_=new NTDispatcher;
  }
  return *instance_;
}

void NTDispatcher::instance(NTDispatcher* d)
{
  instance_=d;
}



void NTDispatcher::registerChild (HANDLE ch, ChildHandler* h)
{
  bool rv = children_->insert (ch, h) ;
  hgassert (rv,"NTDispatcher::registerChild(): already have that HANDLE: "<<ch) ;
  rv = all_handles_->insert (ch) ;
  hgassert (rv,"NTDispatcher::registerChild: Error inserting handle into array!") ;
}

void NTDispatcher::unregisterChild(HANDLE ch)
{
  bool rv = children_->remove (ch);
  all_handles_->remove(ch);
  hgassert(rv,"NTDispatcher::unregisterChild: Error removing handle from array!") ;
}

int NTDispatcher::dispatch()
{
  // we want to wait for eternal ages!
  long secs=0,usecs=0;
  return dispatch(secs,usecs);
}

int NTDispatcher::dispatch(long& sec, long& usec)
{
  DWORD dRet;

  // calculate the timeout value
  // the parameters are usecs and secs to stay backward compatible to
  // the original UNIX dispatcher
  DWORD dwTimeOut=INFINITE;
  if(sec>0||usec>0)
  {
    dwTimeOut=1000*sec;
    if(usec>0)
    {
      if(usec<1000)
        dwTimeOut++;
      else
        dwTimeOut+=usec/1000;
    }
  }
  DEBUGNL("NTDispatcher::dispatch with timeout"<< dwTimeOut);

  // wait for the termination of the child processes 
  // or an external signal telling us to stop (dropOut)
  dRet=WaitForMultipleObjects((DWORD)all_handles_->count(),all_handles_->data(),0,dwTimeOut);
  if(dRet==WAIT_FAILED)
  {
    int iErr=GetLastError();
    cerr << "NTDispatcher::dispatch: Error at waiting for processes" << endl;
    return -1;
  }

  // detect which object was signalled
  pChildHandler pch;
  int status=0;
  // this is necessary for not mixing up when
  // the array is manipulated while in callback
  int iMaxCount=all_handles_->count();
  for(int i=0;i<iMaxCount;i++)
  {
    if(dRet==WAIT_OBJECT_0+i)
    {
      // is this the dropout event?
      if(i==0)
        return 0;
      else
      {
        // the array starts at 1... entry 0 is reserved for fallout
        bool rv=children_->element(all_handles_->operator[](i),pch);
        if(rv)
        {
          // now call the callback!
          pch->childEvent((long)all_handles_->operator[](i),status) ;
        }
        else
        {
          cerr << "NTDispatcher::dispatch: error finding handler for process No. " << i << endl;
        }
      }
    }
    else if(dRet==WAIT_TIMEOUT)
    {
      // we got a timeout...
      return 2;
    }
  }

  return 1;
}

void NTDispatcher::dropOut()
{
  // signal the event, that terminates the wait
  SetEvent(BreakEvent_);
}
