//-----------------------------------------------------------------------------
// Copyright (c) 1994,1995 Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
//-----------------------------------------------------------------------------
//
// Description:
//      Implementation of nsService Class
//
// Author:  Jie Chen
//
// Revision History:
//   rnsService.cc,v
// Revision 1.8  1998/03/19  18:31:43  chen
// add monitorEntry capability
//
// Revision 1.7  1998/02/23  16:29:35  chen
// change back to use alarm to handle reconnecton to the name server
//
// Revision 1.6  1998/02/19  18:39:52  akers
// Bug Fixes
//
// Revision 1.5  1998/02/18  14:42:52  chen
// add registerFd to the service
//
// Revision 1.4  1998/02/10  18:06:44  chen
// use system timer instead of alarm
//
// Revision 1.3  1998/02/06  15:11:26  chen
// add connection management for name server
//
// Revision 1.2  1998/01/30  18:31:37  akers
// Ongoing development
//
// Revision 1.1  1998/01/22  17:07:52  akers
// Addition of new NameServer
//
//
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

#ifndef _WIN32
	#include <unistd.h>
#endif

#include "rnsService.h"
#include "rnsRequestObject.h"
#include <rnsMonitorObj.h>

#define  _RNS_CLIENT_RECONN_TIMEOUT 5

char * rnsService::tags_[] = {
  "table",
  "key",
  "keyExp",
  "keyType",
  "name",
  "domain",
  "host",
  "owner",
  "time",
  "port",
  "pid",
  "ping",
  "client",
  "queryMsg"
};

int rnsService::numtags_ = 14;

static rnsService* _rnsNameService_ = 0;

#ifdef _WIN32
void CALLBACK _rnsAlarmHandler_ ( HWND hwnd, UINT umsg, UINT idEvent, DWORD dwTime )
	{
	if (_rnsNameService_->reconnect () == CDEV_SUCCESS)
		{
	    	if (_rnsNameService_->monitorsRestarted ())
	    		{
			KillTimer(NULL, idEvent);
			}
		}
	}
#else
static struct sigaction _oldaction_;

static void _rnsAlarmHandler_ (int /* signo */)
{
  int done = 0;

  if (_rnsNameService_->reconnect () == CDEV_SUCCESS) {
    if (_rnsNameService_->monitorsRestarted ()) {
      sigaction (SIGALRM, &_oldaction_, 0);
      done = 1;
    }
  }
  
  if (!done)
    alarm (_RNS_CLIENT_RECONN_TIMEOUT);
}
#endif 

rnsService::rnsService (char* name, cdevSystem& system)
:cdevService (name, system), client_ (), fds_ (0), numFds_ (0),
 monobjs_ ()
{
#ifdef _TRACE_OBJECTS
  printf ("              Create rnsService Class Object\n");
#endif
  // register all tags
  for (int i = 0; i < rnsService::numtags_; i++) 
    cdevData::addTag (rnsService::tags_[i]);
  
  // remember this service
  _rnsNameService_ = this;
}

rnsService::~rnsService (void)
{
#ifdef _TRACE_OBJECTS
  printf ("             Delete rnsService Class Object\n");
#endif
  if (client_.connected ())
    client_.disconnect ();
  
  if (numFds_ > 0)
    delete []fds_;

  // delete all callbacks
  cdevSlistIterator ite (monobjs_);
  rnsMonitorObj*   mobj = 0;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *) ite ();
    delete mobj;
  }
}

int
rnsService::getFd (int* &fd, int& numFd)
{
  fd = fds_;
  numFd = numFds_;
  return CDEV_SUCCESS;
}

int
rnsService::flush (void)
{
  return CDEV_SUCCESS;
}


int
rnsService::poll (void)
{
  return client_.pendIO (0.001);
}

int
rnsService::pend (int /* fd */)
{
  return client_.pendIO ();
}

int
rnsService::pend (double seconds, int)
{
  return client_.pendIO (seconds);
}

int
rnsService::getRequestObject (char* deviceName, char* msg,
			      cdevRequestObject* &req)
{
  if (!client_.connected ()) {
    char* nsHost = getenv ("CDEV_NAME_SERVER");
    if (!nsHost) {
       reportError (CDEV_SEVERITY_SEVERE, serviceName_, 0,
		   "Cannot find environment variable CDEV_NAME_SERVER !!!");
      req = 0;
      return CDEV_ERROR;
    }
    if (client_.connect (nsHost, RSVC_SERVER_PORT, 3.0) != RSVC_SUCCESS) {
      reportError (CDEV_SEVERITY_SEVERE, serviceName_, 0,
		   "Cannot connect to name server at port %d on %s",
		   RSVC_SERVER_PORT, nsHost);
      req = 0;
      return CDEV_ERROR;
    }
    
    if (numFds_ > 0)
      delete []fds_;

    fds_ = new int[1];
    numFds_ = 1;
    fds_[0] = client_.getFd ();

    // register fd to the system
    registerFd (client_.getFd (), 1);

    // register a disconnection callback
    if (client_.disconnectCallback (&(rnsService::discCallback),
				    (void *)this) != RSVC_SUCCESS) 
      reportError (CDEV_SEVERITY_WARN, serviceName_, 0,
		   "Cannot register disconnect callback");
  }
  req = new rnsRequestObject (deviceName, msg, this, system_);
  return CDEV_SUCCESS;
}

int
rnsService::reconnect (void)
{
  char* nsHost = getenv ("CDEV_NAME_SERVER");
  if (!client_.connected ()) {
    if (client_.connect (nsHost, RSVC_SERVER_PORT, 3.0) == RSVC_SUCCESS) {
      reportError (CDEV_SEVERITY_INFO, serviceName_, 0,
		   "Reconnected to name server at port %d on %s",
		   RSVC_SERVER_PORT, nsHost);

      fds_ = new int[1];
      numFds_ = 1;
      fds_[0] = client_.getFd ();

      // register this fd to the sysem
      registerFd (client_.getFd(), 1);

      // register a disconnection callback
      if (client_.disconnectCallback (&(rnsService::discCallback),
				      (void *)this) != RSVC_SUCCESS) 
	reportError (CDEV_SEVERITY_WARN, serviceName_, 0,
		     "Cannot register disconnect callback");

      return CDEV_SUCCESS;
    }
    else
      return CDEV_TIMEOUT;
  }
  return CDEV_SUCCESS;
}
    
int
rnsService::getNameServer (cdevDevice* &ns)
{
  ns = 0;
  return CDEV_SUCCESS;
}

cdevService *
newRnsService (char* name, cdevSystem* system)
{
  return new rnsService (name, *system);
}


int
rnsService::addMonitorObject (rnsMonitorObj* obj)
{
  cdevSlistIterator ite (monobjs_);
  rnsMonitorObj*   mobj = 0;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *) ite ();
    if (*(mobj->userCallback_) == *(obj->userCallback_))
      return CDEV_ERROR;
  }
  monobjs_.add ((void *)obj);
  return CDEV_SUCCESS;
}

int
rnsService::removeMonitorObject (rnsMonitorObj* obj)
{
  cdevSlistIterator ite (monobjs_);
  rnsMonitorObj*   mobj = 0;
  int               found = 0;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *) ite ();
    if (mobj == obj) {
      found = 1;
      break;
    }
  }
  if (found) {
    monobjs_.remove ((void *)obj);
    return CDEV_SUCCESS;  
  }
  return CDEV_ERROR;
}

rnsMonitorObj*
rnsService::hasCallback (cdevCallback& cbk)
{
  cdevSlistIterator ite (monobjs_);
  rnsMonitorObj*   mobj = 0;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *) ite ();
    if (*(mobj->userCallback_) == cbk) 
      return mobj;
  }
  return 0;
}

int
rnsService::monitorsRestarted (void)
{
  cdevSlistIterator ite (monobjs_);
  rnsMonitorObj*    mobj = 0;
  rnsRequestObject* req = 0;
  int               notdone = 0;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *)ite ();
    if (!mobj->started_) {
      notdone = 1;
      req = (rnsRequestObject *)mobj->reqObj_;
      if (req->action_ == rnsRequestObject::MONITORON) 
	req->sendMonitor (mobj);
      else if (req->action_ == rnsRequestObject::MONITORENTRY)
	req->sendEntryMonitor (mobj);
    }
  }
  return !notdone;
}
      

void
rnsService::discCallback (int /* status */, void* arg, rsvcData* /* data */)
{
  rnsService* nsvc = (rnsService *) arg;

  if (nsvc->numFds_ > 0) {
    // remove this fd from the system
    for (int i = 0; i < nsvc->numFds_; i++)
      nsvc->registerFd (nsvc->fds_[i], 0);

    delete [](nsvc->fds_);
    nsvc->numFds_ = 0;
  }

  cdevSlistIterator ite (nsvc->monobjs_);
  rnsMonitorObj*   mobj = 0;
  cdevCallback*     cbk = 0;
  cdevRequestObject *req = 0;
  cdevData          empty;

  for (ite.init (); !ite; ++ite) {
    mobj = (rnsMonitorObj *) ite ();
    
    cbk = mobj->userCallback_;
    req = mobj->reqObj_;
    cbk->fireCallback (CDEV_DISCONNECTED, cbk->userarg (),
		       *req, empty, 0);
    mobj->started_ = 0;
  }

#ifdef _WIN32
  SetTimer (NULL, 0, 1000*_RNS_CLIENT_RECONN_TIMEOUT, _rnsAlarmHandler_);
#else
  // register a alarm handler to try to connect to the name server
  struct sigaction act;

  act.sa_handler = _rnsAlarmHandler_;
  sigemptyset (&act.sa_mask);
  act.sa_flags = 0;

  if (sigaction (SIGALRM, &act, &_oldaction_) < 0) 
    cdevSystem::defaultSystem().reportError(
		CDEV_SEVERITY_ERROR, "RNS Service", NULL,
		"Cannot register alarm signal handler");
  else
    alarm (_RNS_CLIENT_RECONN_TIMEOUT);
#endif
}
