//------------------------------------------------------------------------------
// Copyright (c) 1991,1992 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: 
//	cdevTagTable class manages tags within the cdev system.
//
// Author:  Walt Akers
//
//
//    
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cdevErrCode.h>

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

#include "cdevTagTable.h"

char* cdevTagTable::defaultTags[] =
{
  "value",
  "status",
  "severity",
  "time",
  "units",
  "displayHigh",
  "displayLow",
  "alarmHigh",
  "alarmLow",
  "warningHigh",
  "warningLow",
  "controlHigh",
  "controlLow",
  "bitMask",
  "file",
  "class",
  "device",
  "message",
  "verb",
  "attribute",
  "collection",
  "resultCode", // Used to return the completion status of a server operation
  "clientID",	// Used in context to identify a client by number
  "precision",
  NULL
};
unsigned int cdevTagTable::numberDefTags = 24;
// Note: if you want to add new tags, make sure to change the above number to 
// reflect the change of number of default tags

// default tag table location and tag table environment variable
char* cdevTagTable::defaultTagTableLocation = "/usr/local/lib/cdevTagTable";
char* cdevTagTable::tagTableEnv = "CDEVTAGTABLE";

const int INT_TAG_TABLE_SIZE = 997;
const int STR_TAG_TABLE_SIZE = 997;

cdevTagTable::cdevTagTable (void)
:itagList_ (INT_TAG_TABLE_SIZE), 
 stagList_ (STR_TAG_TABLE_SIZE, cdevStrHashFunc),
 callbacks_(NULL),
 highestTag(1000)
{
#ifdef _TRACE_OBJECT
  printf ("Create cdevTagTable Class Object\n");
#endif
  initialize ();
}

void cdevTagTable::initialize ( void )
{
  // first check a tag table file pointed by an environment variable CDEVTAGTABLE
  int           tsuccess = 1, i = 0;
  char**        tags;
  int*          tagvalues;
  unsigned int  numTags = 0;

  char* tagtable = ::getenv (cdevTagTable::tagTableEnv);
  if (tagtable) 
    tsuccess = cdevTagTable::parseTagTable (tagtable, tags, tagvalues, numTags);
  else
    tsuccess = cdevTagTable::parseTagTable (cdevTagTable::defaultTagTableLocation, 
					    tags, tagvalues, numTags);

  if (tsuccess) {
    for (i = 0; i < numTags; i++) 
      insertTag(tagvalues[i], tags [i]); 

#ifdef _CDEV_DEBUG
    for(i = 0; i < numTags; i++){
      char * s;
      tagI2C(tagvalues[i], s);
      printf("Tag %i = %s\n", tagvalues[i], s);
    }
#endif

    delete []tagvalues;
    for (i = 0; i < numTags; i++)
      delete []tags[i];
    delete []tags;
  }
  else {
    for(i = 1; i <= cdevTagTable::numberDefTags; i++) 
      insertTag(i, cdevTagTable::defaultTags [i-1]); 

#ifdef _CDEV_DEBUG
    for(i = 1; i <= cdevTagTable::numberDefTags; i++){
      char * s;
      tagI2C(i, s);
      printf("Tag %i = %s\n", i, s);
    }
#endif
  }
  
}

void 
cdevTagTable::freeMemory (void)
{
  cdevStrHashIterator sit1(stagList_);
  cdevTagEle *tmpele = 0;
  for (sit1.init(); !sit1; ++sit1){
    if ( (tmpele = (cdevTagEle *)sit1 ()) != 0){
    delete []tmpele->tagName;
    delete tmpele;}
  }
  // do not need to delete pointers inside itagList_
}

int 
cdevTagTable::tagC2I(char *ctag, int *tag)
{ 
  cdevSlist& slist = stagList_.bucketRef (ctag);
  cdevSlistIterator ssit (slist);
  cdevTagEle *tmpele = 0;
  for (ssit.init(); !ssit; ++ssit){ 
    tmpele = (cdevTagEle *)ssit ();
    if ( ::strcmp (tmpele->tagName, ctag) == 0 ){
      *tag = tmpele->tagId; return CDEV_SUCCESS;
    }
  }
  return CDEV_NOTFOUND; 
}

int
cdevTagTable::tagI2C(int tag, char * &ctag) 
{
  cdevSlist& ilist = itagList_.bucketRef (tag);
  cdevSlistIterator isit (ilist);
  cdevTagEle *tmpele = 0;
  for (isit.init(); !isit; ++isit){ 
    tmpele = (cdevTagEle *)isit ();
    if ( tmpele->tagId == tag ){
      ctag = tmpele->tagName; return CDEV_SUCCESS; 
    }
  }
  return CDEV_NOTFOUND; 
}

void 
cdevTagTable::insertTag(int tag, char *ctag) 
{
  cdevSlist& slist = stagList_.bucketRef (ctag);
  cdevSlist& ilist = itagList_.bucketRef (tag);
  cdevSlistIterator ssit (slist);
  cdevSlistIterator isit (ilist);
  cdevTagEle *tmpele = 0;
  int sfound = 0, ifound = 0;

  for (ssit.init(); !ssit; ++ssit){ 
    tmpele = (cdevTagEle *)ssit ();
    if ( ::strcmp (tmpele->tagName, ctag) == 0 ){
      sfound = 1; break; 
    }
  }
  
  for (isit.init(); !isit; ++isit){ 
    tmpele = (cdevTagEle *)isit ();
    if ( tmpele->tagId == tag ){
      ifound = 1; break; }
  }

  if ( (sfound == 0) && (ifound == 0) ) {
    cdevTagEle *tag_elem = new cdevTagEle;
    tag_elem->tagName = new char[::strlen(ctag) + 1];
    ::strcpy (tag_elem->tagName, ctag);
    tag_elem->tagId = tag;
    stagList_.add((char *)ctag, (void *)tag_elem); 
    itagList_.add(tag, (void *)tag_elem); 
    
    // *************************************************************************
    // * Notify each of the callback objects that a new tag has been added.
    // *************************************************************************
    if(callbacks_!=NULL)
    	{
    	cdevTagTableCallback * cb = callbacks_;
    	while(cb!=NULL)
    		{
    		cb->callback(tag, ctag);
    		cb = cb->next_;
    		}
  	}
  }
  else {
#ifdef _CDEV_DEBUG
    printf ("the tag named %s is already in table: Error++++\n", ctag);
#endif
  }
}

// *****************************************************************************
// * tagExists:
// *	This function can be called to determine if a tag string has already
// *	been added to the cdevTagTable.  The value returned is a boolean
// *	result that is TRUE (non-zero) if the tag exists or FALSE (0) if not.
// *****************************************************************************
int cdevTagTable::tagExists ( char * ctag )
	{
	int tag;
	return !tagC2I(ctag, &tag);
	}
	

// *****************************************************************************
// * tagExists:
// *	This function can be called to determine if a tag integer has already
// *	been added to the cdevTagTable.  The value returned is a boolean
// *	result that is TRUE (non-zero) if the tag exists or FALSE (0) if not.
// *****************************************************************************
int cdevTagTable::tagExists ( int tag )
	{
	char * ctag;
	return !tagI2C(tag, ctag);
	}
	
// *****************************************************************************
// * addTag :
// *	This method will add the tag string to the cdevTagTable if it 
// *	does not already exist.  Tag Table will start searching for the
// *	next available tag number above 1000.  The value returned by the
// *	function will be the integer that is assigned to the character string
// *	tag.
// *****************************************************************************
int cdevTagTable::addTag ( char * ctag )
	{
	int   tag;
	char *ptr;

	if(tagC2I(ctag, &tag)!=CDEV_SUCCESS)
		{
		tag  = highestTag;
		while(tagI2C(tag, ptr)==CDEV_SUCCESS) tag++;
		insertTag(tag, ctag); 
		highestTag = tag+1;
		}
	return tag;
	}


// *****************************************************************************
// * addTagCallback :
// *	This method will add a cdevTagTableCallback object to the list of 
// *	classes that should be notified each time a tag is added to the
// *	cdevTagTable.
// *****************************************************************************
void cdevTagTable::addTagCallback ( cdevTagTableCallback * cb )
	{
	if(cb!=NULL)
		{
		cb->next_ = callbacks_;
		callbacks_ = cb;
		}
	}

// *****************************************************************************
// * delTagCallback :
// *	This method will remote a cdevTagTableCallback object that was 
// *	previously added to the list of callback classes.  
// *
// *	Note:	This method does not actually delete the cdevTagTableCallback
// *		object, it merely removes it from the list.  It is the 
// *		responsibility of the owner to delete the object when it is
// *		no longer needed.
// *****************************************************************************
void cdevTagTable::delTagCallback ( cdevTagTableCallback * cb )
	{
	cdevTagTableCallback *prev = NULL,  *ptr = callbacks_;
	
	while(ptr!=cb && ptr!=NULL)
		{
		prev = ptr;
		ptr  = ptr->next_;
		}
	
	if(ptr!=NULL)
		{
		if(prev!=NULL) prev->next_ = ptr->next_;
		else callbacks_            = ptr->next_;
		}
	}

// *****************************************************************************
// * readTagTable :
// *	This method allows the caller to obtain a list of tag names and 
// *	integers that are currently stored in this tag table.  
// *
// *	Note:	This method will allocate an array of integers and an array of 
// *		character string pointers.  The strings that are stored in the 
// *		string array will be the actual strings from within the
// *		tag table and should not be deleted...
// *
// *		The data allocated becomes the property of the caller and must 
// *		be deleted when it is no longer needed... the correct syntax to
// *		delete these items is...
// *
// *			delete tags;
// *			delete ctags;
// *
// *		This will delete the array, however, it will leave the 
// *		individual array elements intact.
// *****************************************************************************
int cdevTagTable::readTagTable ( int * &tags, char ** &ctags, int &ntags )
	{
	cdevTagEle *        element;
	cdevIntHashIterator iter(itagList_);
	int cnt = 0;
	
	ntags = 0;
	tags  = NULL;
	ctags = NULL;
	
	iter.init();
	while(iter.getData()!=NULL)
		{
		++cnt;
		++iter;
		}
	if(cnt)	{
		tags  = new int   [cnt];
		ctags = new char *[cnt];
		
		iter.init();
		while((element=(cdevTagEle *)iter.getData())!=NULL && ntags<cnt)
			{
			tags [ntags] = element->tagId;
			ctags[ntags] = element->tagName;
			++ntags;
			++iter;
			}
		}
	return !ntags;
	}

void
cdevTagTable::asciiDump (FILE* fp)
{
  char** tagnames;
  int*   tagvalues;
  int    numtags = 0;

  readTagTable (tagvalues, tagnames, numtags);

  for (int i = 0; i < numtags; i++)
    fprintf (fp, "%d         %s\n", tagvalues[i], tagnames[i]);

  delete []tagvalues;
  delete []tagnames;
}

int
cdevTagTable::parseTagTable (char*        file, 
			     char**       &tags, 
			     int*         &tagvalues, 
			     unsigned int &numTags)
{
  FILE *fd = ::fopen (file, "r");

  if (!fd) {
    return 0;
  }
  
  numTags = 0; 
  char buffer[128], tag[80];
  int  tagval = 0;

  // first check how many tags: not efficient, but how many tags we are talking about
  while (!feof (fd)) {
    ::fgets (buffer, sizeof (buffer), fd);
    if (::sscanf (buffer, "%d %s", &tagval, tag) == 2)
      numTags ++;
  }
  ::fseek (fd, 0L, SEEK_SET);  // reset to the begining of the file

  if (numTags == 0) {
    ::fclose (fd);
    return 0;
  }

  // allocate space for tag/tagvalue array
  tags = new char*[numTags];
  tagvalues = new int[numTags];
  
  unsigned int i = 0;
  while (!feof (fd)) {
    ::fgets (buffer, sizeof (buffer), fd);
    if (::sscanf (buffer, "%d %s", &tagval, tag) == 2) {
      tags[i] = new char[::strlen (tag) + 1];
      ::strcpy (tags[i], tag);
      tagvalues[i++] = tagval;
    }
  }
  assert (i == numTags);

  ::fclose (fd);
  return 1;
}
