#ifndef _CDEV_DIRECTORY_TABLE_H_
#define _CDEV_DIRECTORY_TABLE_H_

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <cdevSystem.h>
#include <cdevErrCode.h>
#include <cdevError.h>
#include <StringHash.h>

#ifdef __VMS
typedef unsigned long u_long;
typedef unsigned short u_short;
#endif


class cdevElementDefinition;
class cdevClassDefinition;
class cdevServiceDefinition;
class cdevAliasDefinition;
class cdevDeviceDefinition;
class cdevCollectionDefinition;

int CDEV_CLASS_SPEC cdevReportError(int severity, char *name, void *, char *formatString, ...);

// *****************************************************************************
// * class cdevDirectoryTable :
// *	This is the master class that is used to collect and maintain 
// *	information associated with the CDEV Device Definition Language file.
// *
// *	This class maintains a number of tables and lists for the following 
// *	purposes...
// * 
// *	collectionClass   - This is the single instance of the cdevClassDefinition
// *                        for the collection class.
// *
// *	serviceHead       - This is a list of all of the services that have been
// *    serviceTail         read from the DDL file in the order in which they 
// *                        were loaded.
// *
// *	classHead         - This is a list of all of the classes that have been 
// *    classTail           read from the DDL file in the order in which they 
// *                        were loaded.
// *
// *	aliasHead         - This is a list of aliases that are defined within
// *	aliasTail           the DDL file.
// *
// *	collectionHead    - This is a list of collection devices that are 
// *	collectionTail      defined within the DDL file.
// *
// *	serviceHash       - This is a hash table that allows for fast lookup of 
// *                        each of the services that were loaded into the 
// *                        service list.
// *
// *	classHash         - This is a hash table that allows fast lookup by name
// *                        of any of the classes that were loaded into the 
// *                        class list.
// *
// *	classInstanceHash - This is a hash table that contains only the names
// *                        and addresses of classes that have been used to 
// *                        instanciate a device. 
// *
// *	deviceHash        - This is a hash table that allows the caller to 
// *                        quickly locate any device that has been defined in
// *                        the DDL file.
// *
// *	collectionHash    - This is a hash table that allows the caller to 
// *                        quickly locate a collection device definition.
// *****************************************************************************
class CDEV_CLASS_SPEC cdevDirectoryTable
{
public:
	static long BinaryMagic_1_4;
	static long BinaryMagic_1_5;
	static long BinaryMagic;

	typedef enum {VERB=0, ATTRIBUTE, MESSAGE} ElementType;	

private:
	cdevClassDefinition      * collectionClass;

	cdevServiceDefinition    * serviceHead;
	cdevServiceDefinition    * serviceTail;
	cdevClassDefinition      * classHead;
	cdevClassDefinition      * classTail;
	cdevAliasDefinition      * aliasHead;
	cdevAliasDefinition      * aliasTail;
	cdevCollectionDefinition * collectionHead;
	cdevCollectionDefinition * collectionTail;
	StringHash                 serviceHash;
	StringHash                 classHash;
	StringHash                 classInstanceHash;
	StringHash                 deviceHash;
	StringHash                 collectionHash;
	
public:
	inline  cdevDirectoryTable   ( void );		
	inline ~cdevDirectoryTable   ( void );

	inline int  addService       ( cdevServiceDefinition  *def );
	inline int  addService       ( char                  *name, 
				       char                 **tags=NULL, 
				       int                    nTags=0);

	inline int  addClass         ( cdevClassDefinition   *def );
	inline int  addClass         ( char                  *name, 
				       cdevClassDefinition   *parent     = NULL,
				       cdevElementDefinition *verbs      = NULL, 
				       cdevElementDefinition *attributes = NULL, 
				       cdevElementDefinition *messages   = NULL);

	inline int  addClassInstance ( cdevClassDefinition  * def );
	inline int  addDevice        ( cdevDeviceDefinition * def );
	inline int  addAlias         ( cdevAliasDefinition  * def );
	inline int  addAlias         ( char * name, cdevDeviceDefinition & def );
	inline int  addCollection    ( cdevCollectionDefinition * def );
	inline int  addCollection    ( char * name, char ** devices, int nDevices );
	
	inline void asciiDump           ( FILE * fp = stdout );
	inline void asciiDumpRedirector ( FILE * fp = stdout );	

	inline cdevServiceDefinition * findService ( char * name ); 
	inline cdevClassDefinition   * findClass   ( char * name ); 

	// *********************************************************************
	// * Member access methods.
	// *********************************************************************
	inline cdevServiceDefinition * getServices       ( void );
	inline cdevClassDefinition   * getClasses        ( void );
	inline cdevAliasDefinition   * getAliases        ( void );
	inline StringHash            & getServiceHash    ( void );
	inline StringHash            & getClassHash      ( void );
	inline StringHash            & getInstanceHash   ( void );
	inline StringHash            & getDeviceHash     ( void );
	inline StringHash            & getCollectionHash (void );

	// *********************************************************************
	// * External data load/dump methods.
	// *********************************************************************
	int  load             ( char * inputFile );
	int  asciiLoad        ( char * inputFile );
	int  asciiBufferLoad  ( char * buffer );
	void binaryDump       ( char * outputFile );
	int  binaryLoad       ( char * inputFile );
	int  loadText         ( char * includePath, char * filename, char * &buf, int &buflen, int &cursor);
	void readServices     ( char * buf );
	void readClasses      ( char * buf );
	void readElements     ( cdevClassDefinition  &def, ElementType type, char *start, char *end );
	void readDevices      ( char * buf );
	void readAliases      ( char * buf );
	void readCollections  ( char * buf );
	void compressComments ( char * filename, char * buf );
	void compressSpaces   ( char * filename, char * buf );
};

#include <cdevDeviceDefinition.h>
#include <cdevAliasDefinition.h>
#include <cdevServiceDefinition.h>
#include <cdevElementDefinition.h>
#include <cdevRedirectorDefinition.h>
#include <cdevClassDefinition.h>
#include <cdevCollectionDefinition.h>

// *****************************************************************************
// * cdevDirectoryTable::cdevDirectoryTable :
// *	Constructor for the cdevDirectoryTable class. 
// *****************************************************************************
inline cdevDirectoryTable::cdevDirectoryTable ( void ) 
	: serviceHead(NULL), serviceTail(NULL), classHead(NULL), classTail(NULL),
	  aliasHead(NULL), aliasTail(NULL), collectionHead(NULL), 
	  collectionTail(NULL), serviceHash(0), classHash(0), 
	  deviceHash(0, 9973), classInstanceHash(0), collectionHash(0)
	{
	// *********************************************************************
	// * Note, by creating a collection class to hold all collection devices
	// * I am preventing the developer from creating his own collection 
	// * class.
	// *********************************************************************
	collectionClass = new cdevClassDefinition(*this, strdup("collection"));
	addClass(collectionClass);
	}
		

// *****************************************************************************
// * cdevDirectoryTable::~cdevDirectoryTable :
// *	This is the destructor for the cdevDirectoryTable class.
// *****************************************************************************
inline cdevDirectoryTable::~cdevDirectoryTable ( void )
	{
	while(serviceHead!=NULL)
		{
		cdevServiceDefinition *ptr = serviceHead;
		serviceHead = ptr->getNext();
		serviceHash.remove(ptr->getName());
		delete ptr;
		}
	while(classHead!=NULL)
		{
		cdevClassDefinition *ptr = classHead;
		classHead = ptr->getNext();
		classHash.remove(ptr->getName());
		classInstanceHash.remove(ptr->getName());
		delete ptr;
		}
	while(aliasHead!=NULL)
		{
		cdevAliasDefinition *ptr = aliasHead;
		aliasHead = ptr->getNext();
		deviceHash.remove(ptr->getName());
		delete ptr;
		}
	while(collectionHead!=NULL)
		{
		cdevCollectionDefinition *ptr = collectionHead;
		collectionHead = ptr->getNext();
		collectionHash.remove(ptr->getName());
		delete ptr;
		}
	}


// *****************************************************************************
// * cdevDirectoryTable::addService :
// *	Adds a new service to the directory table. 
// *****************************************************************************
inline int cdevDirectoryTable::addService ( cdevServiceDefinition * def ) 
	{
	int result = CDEV_SUCCESS;
	
	if(def!=NULL && def->getName() && *def->getName())
		{
		if(serviceHash.find(def->getName())!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Service \"%s\" is already defined", def->getName());
			result = CDEV_ERROR;
			}
		else
			{
			serviceHash.insert(def->getName(), def);
			if(serviceTail==NULL)
				{
				serviceTail = def;
				serviceHead = def;
				}
			else 	{
				serviceTail->setNext(def);
				serviceTail = def;
				}
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}



// *****************************************************************************
// * cdevDirectoryTable::addService :
// *	Adds a new service to the directory table. 
// *****************************************************************************
inline int cdevDirectoryTable::addService ( char * name, char ** tags, int nTags ) 
	{
	int result = CDEV_SUCCESS;
	
	if(name!=NULL && *name)
		{
		if(serviceHash.find(name)!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Service \"%s\" is already defined", name);
			result = CDEV_ERROR;
			}
		else
			{
			cdevServiceDefinition *def = new cdevServiceDefinition
				(name, tags, nTags);

			serviceHash.insert(name, def);
			if(serviceTail==NULL)
				{
				serviceTail = def;
				serviceHead = def;
				}
			else 	{
				serviceTail->setNext(def);
				serviceTail = def;
				}
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}


// *****************************************************************************
// * cdevDirectoryTable::addClass :
// *	Adds a new class to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addClass ( cdevClassDefinition * def )
	{
	int result = CDEV_SUCCESS;
	if(def!=NULL && def->getName() && *def->getName())
		{
		if(classHash.find(def->getName())!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Class \"%s\" is already defined", def->getName());
			result = CDEV_ERROR;
			}
		else
			{
			classHash.insert(def->getName(), def);
			if(classTail==NULL)
				{
				classTail = def;
				classHead = def;
				}
			else 	{
				classTail->setNext(def);
				classTail = def;
				}
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}

// *****************************************************************************
// * cdevDirectoryTable::addClass :
// *	Adds a new class to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addClass
	( char                  *name,
	  cdevClassDefinition   *parent,
	  cdevElementDefinition *verbs,
	  cdevElementDefinition *attributes,
	  cdevElementDefinition *messages)
	{
	int result = CDEV_SUCCESS;
	
	if(name!=NULL && *name)
		{
		if(classHash.find(name)!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Class \"%s\" is already defined", name);
			result = CDEV_ERROR;
			}
		else
			{
			cdevClassDefinition *def = new cdevClassDefinition 
				(*this, name, parent, verbs, attributes, messages);

			classHash.insert(name, def);
			if(classTail==NULL)
				{
				classTail = def;
				classHead = def;
				}
			else 	{
				classTail->setNext(def);
				classTail = def;
				}
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}	


// *****************************************************************************
// * cdevDirectoryTable::addClassInstance :
// *	Adds a new class instance to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addClassInstance ( cdevClassDefinition * def )
	{
	int result = CDEV_SUCCESS;
	if(def!=NULL && def->getName() && *def->getName())
		{
		if(classHash.find(def->getName())==NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Class \"%s\" has not been defined", def->getName());
			result = CDEV_ERROR;
			}
		else if(classInstanceHash.find(def->getName())==NULL)
			{
			classInstanceHash.insert(def->getName(), def);
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}

// *****************************************************************************
// * cdevDirectoryTable::addDevice :
// *	Adds a new device to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addDevice ( cdevDeviceDefinition * def )
	{
	int result = CDEV_SUCCESS;
	if(def!=NULL && def->getName()!=NULL && *def->getName())
		{
		if(deviceHash.find(def->getName())!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Device \"%s\" has already been defined", def->getName());
			result = CDEV_ERROR;
			}
		else	deviceHash.insert(def->getName(), def);
		}
	else result = CDEV_ERROR;
	
	return result;
	}

// *****************************************************************************
// * cdevDirectoryTable::addAlias :
// *	Adds a new alias to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addAlias ( cdevAliasDefinition * def )
	{
	int result = CDEV_SUCCESS;
	if(def!=NULL && def->getName() && *def->getName())
		{
		if(deviceHash.find(def->getName())!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Alias \"%s\" has already been defined", def->getName());
			result = CDEV_ERROR;
			}
		else	{
			if(aliasTail==NULL)
				{
				aliasHead = def;
				aliasTail = def;
				}
			else	{
				aliasTail->setNext(def);
				aliasTail = def;
				}
			deviceHash.insert(def->getName(), &def->getDevice());
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}


// *****************************************************************************
// * cdevDirectoryTable::addAlias :
// *	Adds a new alias to the directory table. 
// *****************************************************************************
inline int  cdevDirectoryTable::addAlias 
	( char * name, cdevDeviceDefinition & device )
	{
	cdevAliasDefinition *def = new cdevAliasDefinition(name, device);
	int                  result = addAlias(def);
	if(result!=CDEV_SUCCESS) delete def;
	return result;
	}
	

// *****************************************************************************
// * cdevDirectoryTable::addCollection :
// *	Adds a new collection to the directory table. 
// *****************************************************************************
inline int cdevDirectoryTable::addCollection ( cdevCollectionDefinition * def )
	{
	int result = CDEV_SUCCESS;
	if(def!=NULL && def->getName()!=NULL && *def->getName())
		{
		if(deviceHash.find(def->getName())!=NULL ||
		   collectionHash.find(def->getName())!=NULL)
			{
			cdevReportError(CDEV_SEVERITY_ERROR, "CDEV Directory", NULL,
					"Collection device \"%s\" has already been defined", def->getName());
			result = CDEV_ERROR;
			}
		else	{
			if(collectionTail==NULL)
				{
				collectionTail = def;
				collectionHead = def;
				}
			else	{
				collectionTail->setNext(def);
				collectionTail = def;
				}
			collectionHash.insert(def->getName(), def);
			collectionClass->addDevice (strdup(def->getName()));
			}
		}
	else result = CDEV_ERROR;
	
	return result;
	}

// *****************************************************************************
// * cdevDirectoryTable::addCollection :
// *	Adds a new collection to the directory table. 
// *****************************************************************************
inline int cdevDirectoryTable::addCollection ( char * name, char ** devices, int nDevices )
	{
	int result;
	
	cdevCollectionDefinition * def = 
		new cdevCollectionDefinition(name, devices, nDevices);
	
	if((result = addCollection(def))!=CDEV_SUCCESS) delete def;

	return result;
	}
// *****************************************************************************
// * cdevDirectoryTable::findService :
// *	Allows the caller to locate a service by name. 
// *****************************************************************************
inline cdevServiceDefinition * cdevDirectoryTable::findService ( char * name ) 
	{
	return (cdevServiceDefinition *)serviceHash.find(name);
	}


// *****************************************************************************
// * cdevDirectoryTable::findClass :
// *	Allows the caller to locate a class by name. 
// *****************************************************************************
inline cdevClassDefinition * cdevDirectoryTable::findClass ( char * name ) 
	{
	return (cdevClassDefinition *)classHash.find(name);
	}

// *****************************************************************************
// * cdevDirectoryTable::asciiDump :
// *	This method will write the contents of the cdevDirectory object to 
// *	the user provided file pointer.
// *****************************************************************************
inline void cdevDirectoryTable::asciiDump ( FILE * fp ) 
	{
	cdevServiceDefinition *sPtr = serviceHead;
	if(sPtr) 
		{
		fprintf(fp, "\n// ******************************************\n");
		fprintf(fp, "// * Table of service definitions...        *\n");
		fprintf(fp, "// ******************************************\n");
		}
	while(sPtr!=NULL)
		{
		sPtr->asciiDump(fp);
		sPtr = sPtr->getNext();
		}

	cdevClassDefinition *cPtr = classHead;
	if(cPtr) 
		{
		fprintf(fp, "\n// ******************************************\n");
		fprintf(fp, "// * Table of class definitions...          *\n");
		fprintf(fp, "// ******************************************\n");
		}
	while(cPtr!=NULL)
		{
		if(cPtr!=collectionClass) cPtr->asciiDump(fp);
		cPtr = cPtr->getNext();
		}

	cPtr = classHead;
	if(cPtr) 
		{
		fprintf(fp, "\n// ******************************************\n");
		fprintf(fp, "// * Table of class instance definitions... *\n");
		fprintf(fp, "// ******************************************\n");
		}
	while(cPtr!=NULL)
		{
		if(cPtr!=collectionClass) cPtr->asciiDumpInstances(fp);
		cPtr = cPtr->getNext();
		}	

	cdevAliasDefinition *aPtr = aliasHead;
	if(aPtr) 
		{
		fprintf(fp, "\n\n// ******************************************\n");
		fprintf(fp, "// * Table of alias definitions...          *\n");
		fprintf(fp, "// ******************************************\n");
		}
	while(aPtr!=NULL)
		{
		aPtr->asciiDump(fp);
		aPtr = aPtr->getNext();
		}

	cdevCollectionDefinition * clPtr = collectionHead;
	if(clPtr)
		{
		fprintf(fp, "\n\n// ******************************************\n");
		fprintf(fp, "// * Table of collection definitions...     *\n");
		fprintf(fp, "// ******************************************\n");
		}
	while(clPtr!=NULL)
		{
		clPtr->asciiDump(fp);
		clPtr = clPtr->getNext();
		}
	}

// *****************************************************************************
// * cdevDirectoryTable::asciiDumpRedirector :
// *	This is a diagnostic method that allows the caller to dump the contents 
// * 	of the redirection table for each class that has been instanciated.
// *****************************************************************************
inline void cdevDirectoryTable::asciiDumpRedirector ( FILE * fp ) 
	{
	int cnt = 0;
	
	cdevClassDefinition *def;
	for(def = classHead; def!=NULL; def = def->getNext())
		{
		if(classInstanceHash.find(def->getName())!=NULL)
			{
			def->asciiDumpRedirector(fp);
			cnt++;
			}
		}
	if(!cnt) cdevReportError(CDEV_SEVERITY_WARN, "CDEV Directory", NULL,
				"No devices have been instanciated");
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getServices :
// *	Returns the list of services that are defined within the directory.
// *****************************************************************************
inline cdevServiceDefinition * cdevDirectoryTable::getServices ( void )
	{
	return serviceHead;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getClasses :
// *	Returns the list of classes that are defined within the directory.
// *****************************************************************************
inline cdevClassDefinition * cdevDirectoryTable::getClasses ( void )
	{
	return classHead;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getAliases :
// *	Returns the list of aliases that are defined within the directory.
// *****************************************************************************
inline cdevAliasDefinition * cdevDirectoryTable::getAliases ( void )
	{
	return aliasHead;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getServiceHash :
// *	Return a hash table of services that are defined within the directory.
// *****************************************************************************
inline StringHash & cdevDirectoryTable::getServiceHash ( void )
	{
	return serviceHash;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getClassHash :
// *	Return a hash table of classes that are defined within the directory.
// *****************************************************************************
inline StringHash & cdevDirectoryTable::getClassHash ( void )
	{
	return classHash;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getInstanceHash :
// *	Return a hash table of classes that have been instanciated within the
// *	directory.
// *****************************************************************************
inline StringHash & cdevDirectoryTable::getInstanceHash ( void )
	{
	return classInstanceHash;
	}

	
// *****************************************************************************
// * cdevDirectoryTable::getDeviceHash :
// *	Return a hash table of device names that have been instanciated in all
// *	classes within the directory.  This hash-table includes aliases that 
// *	have been instanciated.
// *****************************************************************************
inline StringHash & cdevDirectoryTable::getDeviceHash ( void )
	{
	return deviceHash;
	}


// *****************************************************************************
// * cdevDirectoryTable::getCollectionHash :
// *	Return a hash table of collection names that have been instanciated in 
// *	the DDL file.  Each collection definition contains the list of devices
// *	that it contains.
// *****************************************************************************
inline StringHash & cdevDirectoryTable::getCollectionHash ( void )
	{
	return collectionHash;
	}


#endif /* _CDEV_DIRECTORY_TABLE_H_ */
