/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : ExternalComms.cc                                              */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/kernel.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "ExternalComms.h"
#include "Syslog.h"

// The extern "C" function processReceivedMsg() accesses the 
// ExternalComms object through this pointer.
ExternalComms *__externalComms;


ExternalComms::ExternalComms()
  : Task(ExternalCommsName)
{
  _system = new SystemInterfaces();
  _socket = -1;
  __externalComms = this;

  _externalMessage = new ExternalMessage(MaxRequestBytes);

  _eventService = new EventService("eventService", this);
}


ExternalComms::~ExternalComms()
{
  if (_socket >= 0)
    close(_socket);

  delete _externalMessage;
  __externalComms = 0;
}


int ExternalComms::createSocket()
{
  Boolean debug = True;

  // Create socket
  if ((_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("ExternalComms::createSocket() - socket()");
    return -1;
  }

  struct sockaddr_in server;
  // Name the socket
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_port = htons(AltexSocketPort);
  if (bind(_socket, (struct sockaddr *)&server, sizeof(server))) {
    perror("ExternalComms::createSocket() - bind()");
    return -1;
  }

  // Start accepting connections
  listen(_socket, 5);

  return 0;
}


void ExternalComms::run()
{
  Boolean debug = True;

  createSocket();


  while (True) {

    fd_set ready;

    FD_ZERO(&ready);
    FD_SET(_socket, &ready);

    // Check for external clients trying to connect, or messages
    // from onboard processes. If onboard message is received,
    // it will be processed by processOnboardMsg(), but select() 
    // will not return until data is ready on the socket.
    if (select(_socket + 1, &ready, 0, 0, 0) < 0) {
      perror("ExternalComms::run() - select()");
      continue;
    }

    int offboardClient;

    if ((offboardClient = accept(_socket, 0, 0)) < 0) {
      perror("ExternalComms::run() - accept()");
      continue;
    }

    dprintf("ExternalComms::run() - accepted connection\n");

    int nBytes;
    if ((nBytes = _externalMessage->read(offboardClient)) < 0) {
      perror("ExternalComms::run() - read()");
      continue;
    }

    processExternalMsg(_externalMessage, offboardClient);

    //    dprintf("ExternalComms::run() - ending connection\n");
    // close(_clientSocket);
  }
}


void ExternalComms::processExternalMsg(ExternalMessage *message,
				       int client)
{
  char errorBuf[100];

  switch (message->messageCode()) {

  case ExternalMessage::ListTasks:
    listTasks(message, client);
    break;

  case ExternalMessage::InvokeTaskMethod:
    invokeTaskMethod(message, client);
    break;

  default:
    Syslog::write("ExternalComms::processOffboardRequest() - %d: "
		  "unknown code\n",
		  message->messageCode());
  }
}



void ExternalComms::listTasks(ExternalMessage *message, int client)
{
  // Fill reply buffer
  for (int i = 0; i < _system->tasks.size(); i++) {
  }

  // Send reply to requester
}



void ExternalComms::invokeTaskMethod(ExternalMessage *msg, int client)
{
  ExternalMethodMessage *methodMsg = (ExternalMethodMessage *)msg;
  
  if (methodMsg->taskIndex() >= _system->tasks.size() ||
      methodMsg->taskIndex() < 0) {
    // Invalid task specified
    throw Error("Invalid task number");
  }

  // Get pointer to appropriate TaskInterface
  TaskInterface *task;
  _system->tasks.get(methodMsg->taskIndex(), &task);


  // If client is subscribing to event, add to subscriber table
  if (methodMsg->requestCode() == SharedObject::SubscribeCode) {
    Subscription *subscription = new Subscription;
    subscription->clientSocket = client;
    subscription->task = task;
    subscription->eventCode = 
      ((SubscriptionMessage *)methodMsg)->eventCode();
    
    _subscribers.add(&subscription);

    _eventService->subscribe(task, 
			     subscription->eventCode, 
			     (EventService::Callback )
			     forwardNotification);

    // Send a reply to subscriber
  }
  else {
    // Send Request through TaskInterface
    task->sendExternalMsg(methodMsg);

    // Send Task's reply to requester
    if (msg->write(client, methodMsg->size()) == -1) {
      perror("ExternalComms::invokeTaskMethod() - write()");
    }
  }
}


void ExternalComms::processOnboardMsg(char *buf)
{
  Boolean debug = True;
  dprintf("Now in ExternalComms::processOnboardMsg()...\n");
}


void ExternalComms::forwardNotification(TaskInterface *eventSource, 
					EventCode eventCode)
{
  Subscription *subscription;

  for (int i = 0; i < _subscribers.size(); i++) {

    _subscribers.get(i, &subscription);

    if (subscription->task == eventSource && 
	subscription->eventCode == eventCode) {
      // Send notification to offboard subscriber
    }
  }
}



/*
processOnboardMsg() is called by the "C" function _select_receive(),
when an onboard QNX message is received.
*/
void processReceivedMsg(char *msg)
{
  Boolean debug = True;
  dprintf("Now in processReceivedMsg()...\n");
  __externalComms->processOnboardMsg(msg);
}



