/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : TaskServer.cc                                                 */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <sys/kernel.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include "TaskServer.h"
#include "Task.h"
#include "TaskInterface.h"
#include "Syslog.h"

TaskServer *TaskServer::_theTaskServer = 0;

TaskServer::TaskServer(const char *name)
  : SharedObjectServer(name)
{
  Boolean debug = False;
  char errorBuf[100];

  if (_theTaskServer != 0) {
    // Oops, we've already created a Task! Only 1 allowed per process
    sprintf(errorBuf, "Task::Task() - instance (\"%s\") already exists!",
	    _theTaskServer->name());

    throw Exception(errorBuf);
  }

  // Keep track of the most recently created TaskServer for cleanup 
  // on signal/exit
  _theTaskServer = this;

  signal(SIGCHLD, TaskServer::signalHandler);

  // Client subscription request callback
  addCallback(SubscribeCode, (CallbackPtr )TaskServer::addSubscriber);

  // Event notification callback
  addCallback(NotifyCode, (CallbackPtr )TaskServer::handleEvent);


  dprintf("TaskServer::TaskServer() - this=0x%x", this);

  signal(SIGINT, TaskServer::signalHandler);
  signal(SIGTERM, TaskServer::signalHandler);
  signal(SIGQUIT, TaskServer::signalHandler);
  signal(SIGHUP, SIG_IGN);
  atexit(TaskServer::cleanup);
}


TaskServer::~TaskServer()
{
  Boolean debug = False;
  dprintf("TaskServer::~TaskServer()");

  _theTaskServer = 0;
}



Boolean TaskServer::addSubscriber(Request *request, int nRequestBytes,
				  Reply **reply, int *nReplyBytes)
{
  Boolean debug = False;

  // Look at subscription
  Subscription *subscription = (Subscription *)request;

  dprintf("TaskServer::addSubscriber() - subscriber=%d\n", 
	 subscription->subscriberPid);

  // Register the subscriber
  if (registerSubscriber(subscription->eventCode, 
			 subscription->subscriberPid)) {
    _subscriptionReply.confirmed = True;
  }
  else
    _subscriptionReply.confirmed = False;


  *reply = &_subscriptionReply;
  *nReplyBytes = sizeof(SubscriptionReply);

  return True;
}


Boolean TaskServer::registerSubscriber(EventCode eventCode, pid_t subscriber)
{
  Boolean debug = False;
  Boolean found = False;

  // Add subscriber to list for specified event
  FwdEventTableEntry *entry;
  for (int i = 0; i < _fwdEventTable.size(); i++) {

    _fwdEventTable.get(i, &entry);

    if (entry->eventCode == eventCode) {
      // Table contains entry for eventCode; add to subscriber list
      dprintf("TaskServer::registerSubscriber() - already have subscribers to "
	      "event %d; add this one\n", eventCode);

      entry->subscriber.add(&subscriber);
      found = True;
      break;
    }
  }

  if (!found) {
    // Event is not yet in subscriber table
    dprintf("TaskServer::registerSubscriber() - "
	    "add event/subscriber to table\n");
    entry = new FwdEventTableEntry();
    entry->eventCode = eventCode;
    // Add to subscriber list
    entry->subscriber.add(&subscriber);
    _fwdEventTable.add(&entry);
  }

  return True;
}


/*
Subscribe to an event on a TaskInterface (i.e., this TaskServer can
also act as a subscriber/client)
*/
void TaskServer::subscribe(TaskInterface *client, 
			   EventCode eventCode,
			   EventCallback callback)
{
  client->subscribe(eventCode);
    
  EventTableEntry *entry = new EventTableEntry(client, eventCode, callback);
  _eventTable.add(&entry);
}


/*
This method handles "events" generated by another process. The
event might be generated from an "auxillary" task, and should be 
forwarded to subscribers, or could be an event from a TaskInterface
that this TaskServer has subscribed to.
*/
Boolean TaskServer::handleEvent(Request *request, int nRequestBytes,
				Reply **reply, int *nReplyBytes)
{
  Boolean debug = False;

  Event *event = (Event *)request;

  dprintf("TaskServer::handleEvent() - "
	  "clientPid=%d, reqCode=%d, eventCode=%d, pid=%d, forward=%d\n",
	  clientPid(), event->code, event->eventCode, event->serverPid, 
	  event->forward);

  if (event->forward) {

    // Forward event to subscribers
    forwardEvent(event);
  }
  else {
    // Event's destination is here (i.e. server subscribed to it, 
    // through a contained TaskInterface)
    if (qnx_proxy_detach(clientPid()) == -1) {
      Syslog::write("TaskServer::handleEvent() - %s\n", strerror(errno));
    }

    invokeEventCallback(event);
  }
  *reply = 0;
  *nReplyBytes = 0;

  // Don't reply to proxy
  return False;
}


void TaskServer::triggerEvent(EventCode eventCode)
{
  // Create event and forward to subscribers
  Event *event = new Event();
  event->eventCode = eventCode;

  forwardEvent(event);

  delete event;
}


/*
Forward an Event to subscribers
*/
void TaskServer::forwardEvent(Event *event)
{
  Boolean debug = False;

  // Tag event with server pid
  event->serverPid = getpid();

  // Sending to subscribers, which is final destination
  event->forward = False;

  Boolean found = False;

  // Find list of subscribers for this event
  for (int i = 0; i < _fwdEventTable.size(); i++) {

    FwdEventTableEntry *entry;

    _fwdEventTable.get(i, &entry);

    if (entry->eventCode == event->eventCode) {

      found = True;

      // Notify all clients who subscribed to this event
      for (int j = 0; j < entry->subscriber.size(); j++) {
	pid_t processId;
	entry->subscriber.get(j, &processId);
	pid_t proxy = qnx_proxy_attach(processId, event, sizeof(Event), -1);

	if (proxy == -1) {
	  Syslog::write("TaskServer::forwardEvent() - %s\n", strerror(errno));
	}
	else {
	  dprintf("TaskServer::forwardEvent() - "
		  "trigger proxy %d, for pid %d\n", 
		  proxy, processId);

	  Trigger(proxy);
	}
      }

      return;
    }
  }

  if (!found) {
	  dprintf("TaskServer::forwardEvent(), %s - "
		  "no subscribers found for event code %d", 
		  name(), event->eventCode);
  }
}


/*
Event is one that this TaskServer subscribed to (through a contained
TaskInterface). Invoke the corresponding callback method.
*/
void TaskServer::invokeEventCallback(Event *event)
{
  Boolean debug = False;

  // Determine callback which corresponds to this notification
  Boolean found = False;
  for (int i = 0; i < _eventTable.size(); i++) {
    EventTableEntry *entry;
    _eventTable.get(i, &entry);

    dprintf("TaskServer::invokeEventCallback() - "
	    "entry eventCode=%d, serverPid=%d\n",
	    entry->_eventCode, entry->_client->serverPid());

    if (event->eventCode == entry->_eventCode &&
	event->serverPid == entry->_client->serverPid()) {

      // Invoke callback
      dprintf("TaskServer::invokeEventCallback() - found callback\n");
      callMemberFunction(this, entry->_callback)(entry->_client, 
						 event->eventCode);
      found = True;
    }
  }
  if (!found) {
    Syslog::write("TaskServer::invokeEventCallback(), %s - unknown event, "
		  "code=%d, pid=%d\n",
		  name(), event->eventCode, event->serverPid);
  }
}



void TaskServer::run()
{
  spawnAuxTasks();

  SharedObjectServer::run();
}


void TaskServer::signalHandler(int sigNo)
{
  Boolean debug = True;

  if (!_theTaskServer) {
    // Doesn't exist
    dprintf("TaskServer::signalHandler() - _theTaskServer doesn't exist");
    return;
  }

  dprintf("TaskServer::signalHandler() for task %s - exit()", 
	  _theTaskServer->name());

  // Invoke exit(), which will invoke cleanup()
  exit(1);
}


void TaskServer::cleanup()
{
  Boolean debug = False;

  char serverName[100];

  strcpy(serverName, _theTaskServer->name());

  dprintf("TaskServer::cleanup() - delete _theTaskServer (%s)", serverName);

  delete _theTaskServer;

  dprintf("TaskServer::cleanup() - done with %s", serverName);
  return;
}


size_t TaskServer::maxRequestBytes() 
{
  return sizeof(TaskServer::Event);
}
