#include "cdevServerTools.h"


// ****************************************************************************
// cdevSimpleTimer::cdevSimpleTimer :
// 	Constructor.
// ****************************************************************************
cdevSimpleTimer::cdevSimpleTimer ( cdevReactor & Reactor, double Rate)
	{
	setTimeoutRate(Rate);
	if(Reactor.registerTimer (this) == -1)
		{
		outputError ( CDEV_SEVERITY_SEVERE, "cdevSimpleTimer",
			     "Unable to register timer with cdevReactor");
		}
	}
	
// ****************************************************************************
// cdevSimpleTimer::~cdevSimpleTimer :
// 	Destructor.
// ****************************************************************************
cdevSimpleTimer::~cdevSimpleTimer ( void )
	{
	if(reactor) reactor->extractHandler(this);
	handleClose();
	}
	
// ****************************************************************************
// cdevSimpleTimer::handle_timeout :
// 	Called when the timer expires.
// ****************************************************************************
int cdevSimpleTimer::handleTimeout(void)
	{
	return execute();
	}
	

// *****************************************************************************
// * cdevNameServerManager::cdevNameServerManager :
// *	This method will read the CDEV_NAME_SERVER environment variable to 
// *	obtain a list of one or more cdev name servers that should be notified
// *	that this server is operational.  The class will create a
// *	cdevNameServerHandler event handler to manage the connection to each of 
// *	the specified name servers.  If a connection is lost to one of the 
// *	name servers, the cdevNameServerManager will attempt periodically to 
// *	reconnect to the specific name server.
// *****************************************************************************
cdevNameServerManager::cdevNameServerManager ( cdevReactor  & Reactor,
						char        * DomainName, 
						char        * ServerName,
						unsigned short ServerPort )
	: domainName(strdup(DomainName)),
	  serverName(strdup(ServerName)),
	  serverPort(ServerPort),
	  nameServerList(NULL),
	  nameServerHandlers(NULL),
	  nameServerCnt(0)
	{
	// *********************************************************************
	// * Prior to establishing any connections, the class must first install
	// * all of the necessary data into the serverInfo and updateInfo
	// * objects.
	// *********************************************************************
	char * userName = getenv("USER");
	char   hostname[255];
	struct hostent * hostPtr;
	struct timeval tv;

	gettimeofday (&tv);
	gethostname(hostname, 255);
	hostPtr = gethostbyname(hostname);
	
	serverInfo.insert ("name",   serverName);
	serverInfo.insert ("domain", domainName);
	serverInfo.insert ("host",   (char *)hostPtr->h_name);
	serverInfo.insert ("owner",  userName?userName:(char *)"UNKNOWN");
	serverInfo.insert ("time",  (long)tv.tv_sec);
	serverInfo.insert ("port",  (long)serverPort);
	serverInfo.insert ("pid",    getpid ());
	updateInfo.insert ("name",   serverName);
	updateInfo.insert ("domain", domainName);

	// *********************************************************************
	// * First obtain a list of name servers from the CDEV_NAME_SERVER
	// * environment variable.  If this list is empty, then report an
	// * error once and then never call the handler again.
	// *********************************************************************
	char * nsList = getenv("CDEV_NAME_SERVER");
	int    idx;
	int    length;
	
	if(nsList!=NULL)
		{
		char * nsPtr  = nsList;
		char * endPtr = nsList;
		
		// *************************************************************
		// * Determine the number of name servers that have been 
		// * defined in the CDEV_NAME_SERVER environment variable.
		// *************************************************************
		while(*nsPtr==':' && *nsPtr!=0) nsPtr++;
		if(*nsPtr!=0)
			{
			nameServerCnt = 1;
			while(nsPtr!=NULL && *nsPtr!=0)
				{
				if((nsPtr = strchr(nsPtr, ':'))!=NULL)
					{
					while(*(++nsPtr)==':' && *nsPtr!=0);
					if(*nsPtr!=0) nameServerCnt++;
					}
				}
			}
		
		// *************************************************************
		// * Copy each of the defined cdev name server host names into 
		// * the nameServerList.
		// *************************************************************
		if(nameServerCnt>0)
			{
			nameServerList     = new char *[nameServerCnt];
			nameServerHandlers = new cdevNameServerHandler *[nameServerCnt];
			
			for(nsPtr = nsList, idx=0; idx<nameServerCnt && *nsPtr!=0; idx++)
				{
				while(*nsPtr==':' && *nsPtr!=0) nsPtr++;
				for(endPtr=nsPtr; *endPtr && *endPtr!=':'; endPtr++);
				length = endPtr-nsPtr;
				
				nameServerList[idx] = new char[length+1];
				strncpy(nameServerList[idx], nsPtr, length);
				nameServerList[idx][length] = 0;
				nsPtr = endPtr;
				}
			}
		}

	// *********************************************************************
	// * If no cdev name servers have been defined in the cdev name server
	// * list, print a warning message.
	// ********************************************************************
	if(nameServerCnt<=0)
		{
		outputError(CDEV_SEVERITY_WARN, "CDEV Name Server Manager",
		            "\n%s\n%s\n%s",
		            "   => No name servers have been specified in the CDEV_NAME_SERVER",
		            "   => environment variable.",
		            "   => This server WILL NOT be registered with any name servers...");
		}
	// *********************************************************************
	// * Otherwise, set all of the cdevNameServerHandler pointers to NULL
	// * so that they will be connected when the execute timeout method 
	// * is called.
	// *********************************************************************
	else	{
		for(idx=0; idx<nameServerCnt; idx++)
			{
			nameServerHandlers[idx] = NULL;
			}
		}	
	
	// *********************************************************************
	// * Register this class with the cdevReactor so that it will be called
	// * every thirty seconds.  If any of the cdevNameServerHandlers have 
	// * become disconnected, it will attempt to reestablish the connection
	// * at that time.
	// *********************************************************************
	setTimeoutRate(15.0);
	Reactor.registerTimer(this);
	handleTimeout();
	}


// *****************************************************************************
// * cdevNameServerManager::~cdevNameServerManager :
// *	This is the destructor for the cdevNameServerManager class.  When called 
// *	it will remove and delete any cdevNameServerHandlers that have been 
// *	registered with the cdevReactor.  
// *****************************************************************************
cdevNameServerManager::~cdevNameServerManager ( void )
	{
	int idx;
	
	for(idx = 0; idx<nameServerCnt; idx++)
		{
		if(nameServerHandlers[idx]!=0) delete nameServerHandlers[idx];
		if(nameServerList[idx]!=NULL)  delete nameServerList[idx];
		}
	if(nameServerList!=NULL)     delete nameServerList;
	if(nameServerHandlers!=NULL) delete nameServerHandlers;
	if(domainName!=NULL)         delete domainName;
	if(serverName!=NULL)         delete serverName;
	}


// *****************************************************************************
// * cdevNameServerManager::handleTimeout :
// *	This method will be called every thirty seconds to make sure that all
// *	connections to the cdev name servers are established.  If a 
// *	cdevNameServerHandler has become disconnected, this method will attempt
// *	to reconnect to the specified handler.
// *****************************************************************************
int cdevNameServerManager::handleTimeout ( void )
	{
	int idx;
	for(idx=0; idx<nameServerCnt; idx++)
		{
		if(nameServerHandlers[idx]==NULL &&
		   nameServerList[idx]!=NULL)
		   	{
			int             error     = 0; 
			rsvcClient    * client    = new rsvcClient;
			rsvcUdpClient * udpClient = new rsvcUdpClient;		   	
		   	if(client->connect(nameServerList[idx], RSVC_SERVER_PORT, 2.0)!=RSVC_SUCCESS)
		   		{
		   		outputError ( CDEV_SEVERITY_ERROR, "CDEV Name Server Manager",
		   			      "Failed to connect to name server on host %s",
		   			      nameServerList[idx]);
		   		error = -1;
		   		}
		   	else if(udpClient->connect(nameServerList[idx], RSVC_SERVER_PORT+1024)!=RSVC_SUCCESS)
		   		{
		   		outputError ( CDEV_SEVERITY_ERROR, "CDEV Name Server Manager",
		   		              "Failed to open UDP port to name server on host %s",
		   		              nameServerList[idx]);
				error = -1;
		   		}
		   	else if(client->insertValue ("cdevServers", serverInfo, rsvcCallback, nameServerList[idx], 1)!=RSVC_SUCCESS)
		   		{
		   		outputError ( CDEV_SEVERITY_ERROR, "CDEV Name Server Manager",
		   			      "Error transmitting to name server on host %s",
		   			      nameServerList[idx]);
		  		error = -1;
		   		}

			if(!error) nameServerHandlers[idx] = new cdevNameServerHandler(*this, *reactor, idx, client, udpClient);
			else	{
				client->disconnect();
				udpClient->disconnect();
				delete client;
				delete udpClient;
				}
		   	}
		}
	return 0;
	}

// *****************************************************************************
// * cdevNameServerManager::unregisterHandler:
// *	This method is called whenever a cdevNameServerHandler needs to 
// *	unregister itself from the cdevNameServerManager.  The handlers index
// *	will be set to 0 and the connection will be reestablished the next time 
// *	the handleTimeout method is called.
// *****************************************************************************
void cdevNameServerManager::unregisterHandler ( size_t index )
	{
	if(index < nameServerCnt) nameServerHandlers[index] = NULL;
	}


// *****************************************************************************
// * cdevNameServerManager::rsvcCallback :
// *	This is the method that is called when the register server method
// *	is executed.
// *****************************************************************************
void cdevNameServerManager::rsvcCallback (int status, void* arg, rsvcData* /* data */)
	{
	char * nameServer = (char *)arg;

	if (status != RSVC_SUCCESS)
		{
		#ifndef _WIN32
		fprintf(stderr, "CDEV Name Server Manager Error: %s %s\n",
			"Failed to register with name server on host",
			nameServer?nameServer:"UNKNOWN");
		#endif
		}
	}


// *****************************************************************************
// * cdevNameServerHandler::cdevNameServerHandler :
// *	This is the constructor for the cdevNameServerHandler class.  It is 
// *	called by the cdevNameServerManager when a new cdevNameServerHandler 
// *	must be created to communicate with a name server.  The connected
// *	rsvcClient and rsvcUdpClient objects are provided to the class.
// ******************************************************************************
cdevNameServerHandler::cdevNameServerHandler ( cdevNameServerManager & Manager,
                                               cdevReactor           & Reactor,
                                               int                     index,
                                               rsvcClient            * tcpClient,
                                               rsvcUdpClient         * udpClient )
	: nameServerTCP(tcpClient), nameServerUDP(udpClient),
	  nameServerIndex(index), nameServerManager(Manager)
	{
	setTimeoutRate(5.0);
	setMask(WRITE_MASK);
	Reactor.registerHandler(this, WRITE_MASK);
	Reactor.registerTimer  (this);
	}

// *****************************************************************************
// * cdevNameServerHandler::~cdevNameServerHandler :
// *	This is the destructor for the class. this method is responsible for
// *	disconnecting and closing the tcp and udp client connections and 
// *	sending notification to the cdevNameServerManager that it is 
// *	terminating.
// *****************************************************************************
cdevNameServerHandler::~cdevNameServerHandler ( void )
	{
	nameServerTCP->disconnect();
	nameServerUDP->disconnect();
	delete nameServerTCP;
	delete nameServerUDP;
	if(reactor) reactor->extractHandler(this);
	nameServerManager.unregisterHandler(nameServerIndex);
	}

// *****************************************************************************
// * cdevNameServerHandler::handleInput :
// *	This method is called whenever data is ready to be read from the 
// *	nameServerTCP rsvcClient connection.
// *****************************************************************************
int cdevNameServerHandler::handleInput ( void )
	{
	return (nameServerTCP->pendIO()==RSVC_IOERROR)?-1:0;
	}

// *****************************************************************************
// * cdevNameServerHandler::handleOutput :
// *	This method is called whenever data is ready to be written to the 
// *	nameServerTCP rsvcClient connection.
// *****************************************************************************
int cdevNameServerHandler::handleOutput ( void )
	{
	setMask(READ_MASK);
	return (nameServerTCP->pendIO()==RSVC_IOERROR)?-1:0;
	}

// *****************************************************************************
// * cdevNameServerHandler::handleTimeout :
// *	This method is called every five seconds to allow the object to emit
// *	a UDP packet to the name server to notify it that the server is alive.
// *****************************************************************************
int cdevNameServerHandler::handleTimeout ( void )
	{
	if(nameServerUDP->update(*nameServerManager.getUpdateInfo())!=RSVC_SUCCESS)
		{
		outputError(CDEV_SEVERITY_ERROR, "CDEV Name Server Handler",
			    "Failed to update name server on host %s",
			    nameServerManager.getHostName(nameServerIndex));
		}
	return 0;
	}

// *****************************************************************************
// * cdevNameServerHandler::getHandle :
// *	This method allows the caller to obtain the file handle that is being
// *	used by the nameServerTCP rsvcClient connection.  This handle is used 
// *	by the cdevReactor to detect when an event has occured.
// *****************************************************************************
int cdevNameServerHandler::getHandle ( void ) const
	{
	return nameServerTCP->getFd();
	}
	

#ifndef DONT_SUPPORT_CDEV_CALLS
	// *********************************************************************
	// * cdevSystemHandler::cdevSystemHandler :
	// *	This is the constructor for the cdevSystemHandler object.
	// *********************************************************************
	cdevSystemHandler::cdevSystemHandler (cdevReactor & Reactor, 
					      double pollRate, 
					      cdevSystem & System)
		: cdevSimpleTimer(Reactor, pollRate), system(System)
		{
		execute();
		}
#endif

#ifndef DONT_SUPPORT_CDEV_CALLS
	// *********************************************************************
	// * cdevSystemHandler::~cdevSystemHandler :
	// *	This is the destructor for the cdevSystemHandler object.
	// *********************************************************************
	cdevSystemHandler::~cdevSystemHandler (void)
		{
		}
#endif

#ifndef DONT_SUPPORT_CDEV_CALLS
	// *********************************************************************
	// * cdevSystemHandler::execute :
	// *	This is the method that is executed when the specified timer 
	// *	expires.
	// *********************************************************************
	int cdevSystemHandler::execute (void)
		{
		system.poll();
		return 0;
		}
#endif	
