// a CDEV access interface for tcl
// Johannes van Zeijts, March 95, first version
//                      May   95, second version, using Walt Akers callback example 
//                      Dec   95, added arbitrary dimensional array output and context
//                      Jan   96, added handling of strings with spaces
//			Aug   96, deleted callbackinfo when we are not monitoring
//			Aug   96, converted to tcl7.5
// 		Nov 1, 96, moved to cdev 1.4
//			March, 97, overhaul, allowed multiple devices
//		Feb, 98 Make it work with select event handling
// For generic services this works only in cdev/1.6.2
// $id$

/* Output formatting
	Attention given to 'Strings with spaces' which have special meaning in tcl

#devices = 1

#tags = 0 (only value tag)

	dim = 0		1
					String
					String with spaces

	dim = 1		1 1 1 
					St1 St2 St3 
					{St w sp1} {St w sp2} {St w sp3}

	dim = 2		{11 12 13} {21 22 23}
					{st1 st2 st3} {...}
					{{st w sp1} {st w sp2} {st w sp3}} { ...}




#tags > 0 (value tag + other tags)
			Each tag in its own list index
			Result is always list of length (#tags + 1)
		
	
	dim = 0
					1 
					St1 
					{St w sp1} 
					
		
	dim =  1 
					{1 1 1}
					{St1 St2 St3}
					{{St w sp1} {St w sp2} {St w sp3}}
					
					
	dim = 2 
					{{1 1 1} {1 1 1}}
					{{{st w sp1} {st w sp2} {st w sp3}} {{st w sp1} {st w sp2} {st w sp3}}}


---------------------------------------------------------------------------------------
#devices > 1 ,

#tags = 0
	dim = 0 			1 1 1
						{str w sp} {str w sp}
			
	dim = 1			{1 1 1} {1 1 1}
						{{str w sp} ..} { } 
			

#tags > 0
		dim = 0		{0 1 2} {0 1 2}
			
		dim = 1 		{{1 1 1} {1 1 1}} 				
				
*/

#include <stream.h>
#include <tcl.h>

#include <ctype.h>
#include <string.h>
#include <iostream.h>
#include <math.h>
//#include <dl.h>

// cdev includes
#include <cdevSystem.h>
#include <cdevRequestObject.h>
#include <cdevDevice.h>
#include <cdevGroup.h>
#include <cdevData.h>

static int VALUE_TAG_ID =- 1;
#define VALUE_TAG ((VALUE_TAG_ID<0 && cdevData::tagC2I("value", &VALUE_TAG_ID)!= CDEV_SUCCESS)?-1:VALUE_TAG_ID)
static int STATUS_TAG_ID =- 1;
#define STATUS_TAG ((STATUS_TAG_ID<0 && cdevData::tagC2I("status", &STATUS_TAG_ID)!= CDEV_SUCCESS)?-1:STATUS_TAG_ID)
static int SEVERITY_TAG_ID =- 1;
#define SEVERITY_TAG ((SEVERITY_TAG_ID<0 && cdevData::tagC2I("severity", &SEVERITY_TAG_ID)!= CDEV_SUCCESS)?-1:SEVERITY_TAG_ID)

static int cdevdebug = 0;
static char* cdevFormat;
static char* defaultFormat = "%.4g";

class tclCdevData {
	public:
	tclCdevData();
	void parse(Tcl_Interp*, char* *, int);
	~tclCdevData();
	
	cdevData out, context;
	int hascontext;
	
	int tagn, *taglist;
	char *callbacktag;
	int filtersame;	
	int state, async;
	int unroll;
private:
	int getTag(char*);
};

tclCdevData::~tclCdevData() {
	delete taglist;
}

tclCdevData::tclCdevData():hascontext(0), tagn(0), taglist(NULL), async(0), state(TCL_ERROR) {
	filtersame = 1;
	unroll = 0;
};

int tclCdevData::getTag(char * s) {
	int TT=0;
	if (cdevData::tagC2I(s, &TT) == CDEV_SUCCESS) {
		return TT;
	}
	for (int i = 30; TT==0 && i<65534; i++) {
		cdevData::insertTag(i,s); /// bug in cdev: this does not return status
		if (cdevData::tagC2I(s, &TT) == CDEV_SUCCESS) break;
	}
	return TT;
}


// Parses command line, fills up the outgoing cdevData and the context (if any)
void tclCdevData::parse(Tcl_Interp *interp, char **argv, int argc) {
	int i, c1, c2, tag;
	cdevData *ptr;
	char **s1 = NULL;
	char **s2 = NULL;

	// Point to 'out' cdevData until we hit the context (if any)
	ptr = &out;
	for(i = 3; i < argc; i++) {
		// skip 'cdev' '$device' and '$message'
		if(Tcl_SplitList(interp, argv[i], &c1, &s1) != TCL_OK) {
			return;
		}
		if(c1 == 2) {
			// {tag value} pair, check if the value is a scalar
			if(Tcl_SplitList(interp, s1[1], &c2, &s2) != TCL_OK) {
				free((char *) s1);
				return;
			}
			tag = getTag(s1[0]);
			if(c2 <= 1) {
				// we have a scalar
				if (c2 == 0) { // handle empty string
					ptr->insert(tag,"");
				} else {
					ptr->insert(tag, s1[1]);
					// insert scalar
				}
			}
			else {
				ptr->insert(tag, s2, c2);
				// insert vector of scalars, does not handle matrices
			}
			free((char *) s2);
		}
		else if (c1 == 1) {
			// can be one of "-context" , "-tags", "-filtersame" or a callbacktag
			if(strcmp(argv[i], "-context") == 0) {
				// all remaining 'tag value' pairs go in the context
				ptr = &context;
				hascontext = 1;
			}
			else if(strcmp(argv[i], "-tags") == 0) {
				// get the list of tags to print out from the result data
				i++;
				// points to next entry in argv
				if(i == argc) {
					interp->result = "expected tag names";
					free((char *) s1);
					return;
				}
				if(Tcl_SplitList(interp, argv[i], &tagn, &s2) != TCL_OK) {
					interp->result = "tag problem";
					free((char *) s1);
					return;
				}
				else {
					taglist = new int[tagn];
					for(int j = 0; j < tagn; j++) {	
						taglist[j] =  getTag(s2[j]);
					}
				}
				free((char *) s2);
			} else if (strcmp(argv[i], "-filtersame") == 0) {
				i++;
				if (i == argc) {
					interp->result = "expected boolean";
					free((char*) s1);
					return;
				}
				if (Tcl_GetBoolean(interp, argv[i], &filtersame) != TCL_OK) {
					free((char*) s1);
					return;
					}
			} else if (strcmp(argv[i], "-unroll") == 0) {
				i++;
				if (i == argc) {
					interp->result = "expected number";
					free((char*) s1);
					return;
				}
				if (Tcl_GetInt(interp, argv[i], &unroll) != TCL_OK) {
					free((char*) s1);
					return;
					}
			} else {
				// single entry, a callbacktag, better be at end of input
				if(i != argc-1) {
					interp->result = "expecting callbacktag at end";
					free((char *) s1);
					return;
				}
				// Here we have a callback tag, hence we want to send asynchronously
				callbacktag = argv[i];
				async = 1;
			}
		}
		else {
			// c1 > 2
			// tag / string pair (i.e. "value this is a string with spaces embedded"
			char **av = s1;
			av++;
			char *concat = Tcl_Concat(c1-1, av);
			tag = getTag(s1[0]);
			ptr->insert(tag, concat);
			if(cdevdebug == 1) {
				fprintf(stderr, "inserted: %s, with value: %s\n", s1[0], concat);
			}
			free(concat);
		}
		free((char *) s1);
	}
	state = TCL_OK;
}

class callbackInfo {
	public:
	callbackInfo(Tcl_Interp*, char *);
	callbackInfo(Tcl_Interp*, char*, char *);
	~callbackInfo();
	Tcl_Interp *interp;
	char *s;
	char *value; // keep pointer to previous string to compare new result too.
	int status, filtersame;	
	void addtags(int, int *);
	
	int *tags, ntags;
	char **devicetags;
	int unroll, unrolllength;
	char **unrolldata;
};

callbackInfo::callbackInfo(Tcl_Interp *e, char *arg) {
	interp = e;
	s = new char[strlen(arg)+1];
	strcpy(s, arg);
	ntags = 0;
	tags = NULL;
	value = NULL;
	devicetags = NULL;
	status = -999;
	filtersame = 1;
	unroll = 0;
	unrolllength = 0;
	unrolldata = NULL;
}

callbackInfo::callbackInfo(Tcl_Interp *e, char *arg1, char *arg2) {
	interp = e;
	s = new char[strlen(arg1)+strlen(arg2)+1];
	strcpy(s, arg1);
	strcat(s, arg2);
	ntags = 0;
	tags = NULL;
	value = NULL;
	status = -999;
	devicetags = NULL;
	filtersame = 1;
	unroll = 0;
	unrolllength = 0;
	unrolldata = NULL;
}

callbackInfo::~callbackInfo() {
	delete tags;
	for (int i = 0; i < ntags; i++) {
		delete devicetags[i];
	}
	delete unrolldata;
	delete devicetags;
	delete s;
}

void callbackInfo::addtags(int n, int *v) {
	char *tagstring;
	ntags = n;
	tags = new int[n];
	devicetags = new char*[ntags];
	for(int i = 0; i < ntags; i++) {
		tags[i] = v[i];
		cdevData::tagI2C(tags[i],tagstring);
		devicetags[i] = new char[strlen(s) + 2 + strlen(tagstring)];
		strcpy(devicetags[i], s);
		strcat(devicetags[i], ".");
		strcat(devicetags[i], tagstring);
	}
}

static char cs[512];
cdevBounds *Bounds;
int Dim;

static int K = 0;

void HandleLevel(char **in, int level, Tcl_DString &ds) {
	int i;
	for(i = 0; i < Bounds[level].length; i++) {
		if(level >= Dim-1) {
			Tcl_DStringAppendElement(&ds, in[K]);
			K++;
		}
		else {
			Tcl_DStringStartSublist(&ds);
			HandleLevel(in, level+1, ds);
			Tcl_DStringEndSublist(&ds);
		}
	}
}

// Format a cdevData tag result into a tcl result
// mode == #ntags 

void HandleTag(int tag, cdevData &result, Tcl_DString &ds, int mode, int ndevices) {
	int i, len;
	size_t dim;
	char **sp;
	char *tagname = NULL;

	if(cdevdebug == 1) {
		if (cdevData::tagI2C(tag, tagname) != CDEV_SUCCESS) {
//			cout << "HandleTag " << tag << " Could not convert" << endl;
			printf("HandleTag: %d  counld not convert\n",tag);
		} else {
// 			cout << "HandleTag " << tag << " " << tagname << endl;
			printf("HandleTag: %d  %s\n",tag,tagname);
 		}
 		tagname = NULL;
	}
	
	if(result.getDim(tag, &dim) != CDEV_SUCCESS) {
		if(cdevdebug == 1) {
			printf("HandleTag: result.getDim failed\n");
		}
		cs[0] = (char)NULL;
		if (ndevices == 1 && mode == 0) {
			Tcl_DStringAppend(&ds, cs, -1); // append an empty string
		} else {
			Tcl_DStringAppendElement(&ds, cs); // append an empty list
		}
		return;
	}

	// Always retrieve the result as a char*, cdevData does the conversion for me!
	// do special conversion for: DOUBLE and TIMESTAMP

	if(cdevdebug == 1) {
		printf("HandleTag: result.getDim ok : dim=%d\n",dim);
	}
	if(dim == 0) {
		// put this in if we want format conversions.
 		int i = result.getType(tag);
		if(cdevdebug == 1) {
			printf("Type is %d \n",i);
		}
 		if ((i == CDEV_FLOAT) || (i == CDEV_DOUBLE)) {
			if(cdevdebug == 1) {
				printf("HandleTag: is CDEV_DOUB\n");
			}
 			double dres;
 			result.get(tag, &dres);
 			sprintf(cs, cdevFormat, dres);
 		} else if (i == CDEV_TIMESTAMP) {
 			unsigned long timeres;
 			result.get(tag, &timeres);
 			sprintf(cs, "%lu", timeres);
		} else if(i== CDEV_BYTE) {
			printf("CAN'T HANDLE CDEV_BYTE/n"); 
			cs[0] = (char)NULL;
			result.get(tag, cs, 255);
 		} else {
			cs[0] = (char)NULL;
			result.get(tag, cs, 255);
		}

		if (mode == 0 && ndevices == 1) {
			Tcl_DStringAppend(&ds,cs,-1);
		} else {
			Tcl_DStringAppendElement(&ds,cs);
		}
		return;
	} else if (dim==1) {
		size_t  elems;

       		if( (result.getElems(tag,&elems)) != 0){
               		printf("error geting number of elements in value\n");
                	exit(0);
   		}
 		int i = result.getType(tag);
		if(cdevdebug == 1) {
			printf("Type is %d \n",i);
		}
 		if (i ==  CDEV_INT32) {
			int *ival;
			if(cdevdebug == 1) {
				printf("HandleTag: is CDEV_INT32\n");
			}
        		ival = (int *)malloc(elems*(size_t)sizeof(int));
        		result.get(tag,ival);
			if(cdevdebug == 1) {
				printf("cdev format = %s\n",cdevFormat);
			}
        		for(i=0;i<elems;i++){
				if(cdevdebug == 1) {
                			printf("value of element %d is %d\n",i,ival[i]);
				}
/* 				sprintf(cs, cdevFormat, ival[i]);*/
 				sprintf(cs, "%d ", ival[i]);
				Tcl_DStringAppendElement(&ds,cs);
        		}
        		free(ival);
		}   //end of if CDEV_INT32
 		if (i ==  CDEV_DOUBLE) {
			double *dval;
			if(cdevdebug == 1) {
				printf("HandleTag: is CDEV_DOUB\n");
			}
        		dval = (double *)malloc(elems*(size_t)sizeof(double));
        		result.get(tag,dval);
        		for(i=0;i<elems;i++){
				if(cdevdebug == 1) {
                			printf("value of element %d is %f\n",i,dval[i]);
				}
 				sprintf(cs, cdevFormat, dval[i]);
				Tcl_DStringAppendElement(&ds,cs);
        		}
        		free(dval);
		}   //end of if CDEV_DOUBLE

 		else if (i ==  CDEV_FLOAT) {
			float *fval;
			if(cdevdebug == 1) {
				printf("HandleTag: is CDEV_FLOAT\n");
			}
        		fval = (float *)malloc(elems*(size_t)sizeof(float));
        		result.get(tag,fval);
        		for(i=0;i<elems;i++){
				if(cdevdebug == 1) {
                			printf("value of element %d is %f\n",i,fval[i]);
				}
 				sprintf(cs, cdevFormat, fval[i]);
				Tcl_DStringAppendElement(&ds,cs);
        		}
        		free(fval);
		}   //end of if CDEV_FLOAT
		else if(i== CDEV_BYTE) {
			printf("CAN'T HANDLE CDEV_BYTE/n"); 
		}

		return;
	} else {
		// dim > 1
		Dim = (int) dim;
		K = 0;
		Bounds = new cdevBounds[Dim];
		result.getBounds(tag, Bounds, Dim);
		len = 1;
		for(i = 0; i < Dim; i++) {
			if(cdevdebug == 1) {
				printf("HandleT:Bounds[%d].offset = %d\n",i,Bounds[i].offset);
				printf("HandleT:Bounds[%d].length = %d\n",i,Bounds[i].length);
			}
			len *= Bounds[i].length;
		}
		if(cdevdebug == 1) {
			printf("HandleTag: len = %d\n",len);
 			int i = result.getType(tag);
 			if ((i == CDEV_FLOAT) || (i == CDEV_DOUBLE)) {
				printf("HandleTag: is CDEV_DOUB\n");
			}
		}

		sp = new char*[len];
		result.get(tag, sp);
		
		if (mode>0 || (mode==0&&ndevices>1)) {
			if(cdevdebug == 1) {
				printf("HandleTag:  will Tcl_DStringStart..\n");
			}
			Tcl_DStringStartSublist(&ds);
		}
		if(cdevdebug == 1) {
			printf("HandleTag:  will call HandleLevel\n");
		}
		HandleLevel(sp, 0, ds);
		if (mode>0 || (mode==0&&ndevices>1)) {
			if(cdevdebug == 1) {
				printf("HandleTag:  will Tcl_DStringEnd...\n");
			}
			Tcl_DStringEndSublist(&ds);
		}
		// append the result to a Tcl_DString
	}

	if(cdevdebug == 1) {
		printf("HandleTag: will delete bounds \n");
	}
	delete Bounds;
	while(len > 0) {
		len--;
		delete sp[len];
	}
	if(cdevdebug == 1) {
		printf("HandleTag: will delete sp \n");
	}
	delete [] sp;
	if(cdevdebug == 1) {
		printf("HandleTag: finished\n");
	}
}

int Cdev_Handleresult(int *tags, int ntags, cdevData &result, Tcl_DString &ds, int ndevices) {
	if(cdevdebug == 1) {
//		cout << "Cdev_Handleresult" << endl; 
		printf("Cdev_Handleresult\n"); 
	}

	// assume we always return the "value" tag

	if (ndevices>1 && ntags>0) Tcl_DStringStartSublist(&ds);
	
	HandleTag(VALUE_TAG, result, ds, ntags, ndevices);
	if(cdevdebug == 1) {
		printf("CdevHandleresult: ntags = %d ndevices = %d\n",ntags,ndevices);
	}
	if(ntags > 0) {
		for(int i = 0; i < ntags; i++) {
			HandleTag(tags[i], result, ds, ntags, ndevices);
		}
	}
	if (ndevices>1 && ntags>0) Tcl_DStringEndSublist(&ds);
	return TCL_OK;
}

char* getCdevErrorCode(int);


//int cbcount = 0;
// executes inside poll() or pend(file) whenever 'file' is readable
void Cdev_CallbackFunction(int status, void *userarg, cdevRequestObject&, cdevData &result) {
	Tcl_DString ds;
	int i, j, t;

	if(cdevdebug == 1) {
//		cout << "Cdev_CallbackFunction" << endl; 
	}

	callbackInfo *info = (callbackInfo *) userarg;

	if(cdevdebug == 1) {
		printf("callback: status = %d, done = %d\n", status, cdevCallback::isTransactionDone());
		result.asciiDump();
//		cout << "dumped cdevData" << endl;			
	}

	if (info->status != status) {
		info->status = status;
		Tcl_SetVar2(info->interp, "CdevStatus", info->s, getCdevErrorCode(status), TCL_GLOBAL_ONLY);
	}

	if (cdevCallback::isTransactionDone() == 1) {
//		Tcl_SetVar2(info->interp, "CdevDone", info->s, getCdevErrorCode(status), TCL_GLOBAL_ONLY);
		delete info;
		return;
	}
	
//	if ((status == CDEV_SUCCESS || status == CDEV_WARNING) && (result.getType(VALUE_TAG) != CDEV_INVALID)) 
	if ((status == CDEV_SUCCESS || status == CDEV_WARNING)) {
	
		if (info->unroll == 0) { // Do the old stuff
			Tcl_DStringInit(&ds);
			i = Cdev_Handleresult(info->tags, info->ntags, result, ds, 1);
			if (info->filtersame) {
					if (info->value != NULL) {
	 					if (strcmp(info->value, Tcl_DStringValue(&ds)) != 0) { // only set if value is changed
	 					info->value = 	Tcl_SetVar2(info->interp, "Control", info->s, Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
	 				} 
				} else {
					info->value = Tcl_SetVar2(info->interp, "Control", info->s, Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
				}
			} else {
				info->value = Tcl_SetVar2(info->interp, "Control", info->s, Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
			}
			Tcl_DStringFree(&ds);
		} else if (info->unroll == 1) {
		// First to the "value" tag, next loop through all the requested tags

			Tcl_DStringInit(&ds);

			HandleTag(VALUE_TAG, result, ds, 0, 1);
			info->value = Tcl_SetVar2(info->interp, "Control", info->s, Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
			Tcl_DStringFree(&ds);
			
			for (i = 0; i < info->ntags; i++) {
				HandleTag(info->tags[i], result, ds, 0, 1);
				info->value = Tcl_SetVar2(info->interp, "Control", info->devicetags[i], Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
				Tcl_DStringFree(&ds);
			}
		} else { // unroll > 1
		// Loop through the cdevData since it is supposed to be vector valued.
 			Tcl_DStringInit(&ds);

//        	HandleTag(VALUE_TAG, result, ds, 0, 1);
//  			info->value = Tcl_SetVar2(info->interp, "Control", info->s, Tcl_DStringValue(&ds), TCL_GLOBAL_ONLY);
// 			Tcl_DStringFree(&ds);

       	size_t nElems;
			result.getElems(VALUE_TAG, &nElems);
 			char** outString;
			if (result.find(VALUE_TAG, (void*&) outString) != CDEV_SUCCESS) {
				if (cdevdebug) {
//					cout << "Could not find value tag" << endl;
				}
				return;
			}

			char *namePtr, name[512];

			if ((info->unrolldata == NULL)  || (nElems > info->unrolllength)) {
				delete info->unrolldata;
				info->unrolldata = new char*[nElems];
				info->unrolllength = nElems;
			}
			
			double *dp;
			unsigned long *lp;
			
			for (j = 0; j < info->ntags; j++) {
 				t = result.getType(info->tags[i]);
 				if ((t == CDEV_FLOAT) || (t == CDEV_DOUBLE)) {
  					result.find(info->tags[i], (void* &) dp);
					for (i = 0; i < nElems; i++) {
	 					sprintf(cs, cdevFormat, dp[i]);
						info->unrolldata[i] = new char[strlen(cs)+1];
						strcpy(info->unrolldata[i],cs);
					}
 				} else if (t == CDEV_TIMESTAMP) {
  					result.find(info->tags[i], (void* &)lp);
 					for (i = 0; i < nElems; i++) {
						sprintf(cs, "%lu", lp[i]);
						info->unrolldata[i] = new char[strlen(cs)+1];
						strcpy(info->unrolldata[i],cs);
					}
 				} else {
					result.get(info->tags[i], info->unrolldata);
				}
				
				for (i = 0; i < nElems; i++) {
				 	namePtr = name;
					strcpy(namePtr, outString[i]);
					strcat(namePtr, info->devicetags[i]);
					Tcl_SetVar2(info->interp, "Control", name, info->unrolldata[i], TCL_GLOBAL_ONLY);
					delete info->unrolldata[i];
				 }				 
			}
		}
	}
}

// Old way of polling, superseded by FileHandler calls, but since FD's are not working yet, still in use
void Cdev_UpdateProc(ClientData dummy) {
	if(cdevdebug == 1) {
//		cout << "Cdev_UpdateProc" << endl; 
	}
	cdevSystem::defaultSystem().poll();
	Tcl_CreateTimerHandler(200, Cdev_UpdateProc, dummy);
}


void Cdev_FileProc(ClientData clientData, int mask) {
	int fd = (int) clientData;
	if (cdevdebug) {
//		cout << "Cdev_FileProc:  polling: " << fd << " mask = " << mask << endl;
	}
	if (mask == TCL_EXCEPTION) {
		if (cdevdebug) {
//			cout << "Cdev_FileProc:  TCL_EXCEPTION" << endl;
		}
// SMH 		Tcl_File f = Tcl_GetFile(clientData, TCL_UNIX_FD);	
		int f = (int) clientData;		
		Tcl_DeleteFileHandler(f);
		return;
	}
	
	cdevSystem::defaultSystem().pend(0.0001, fd);
	if (cdevdebug) {
//		cout << "Cdev_FileProc:  did pend " << endl;
	}
//	cdevSystem::defaultSystem().poll();
}

int CdevDebugCmd(ClientData, Tcl_Interp*, int, char* *) {
	if(cdevdebug == 0) {
		cdevdebug = 1;
	}
	else {
		cdevdebug = 0;
	}
	return TCL_OK;
}

int CdevFormatCmd(ClientData, Tcl_Interp* interp, int argc, char** argv) {
	if (argc == 1) {
		sprintf(interp->result, "%s", cdevFormat);
		return TCL_OK;
	}
	
	delete cdevFormat;
	cdevFormat = new char[strlen(argv[1])+1];
	strcpy(cdevFormat, argv[1]);
	return TCL_OK;
}

int PutEnvCmd(ClientData, Tcl_Interp*, int argc, char** argv) {
	if (argc == 1) {
		return TCL_OK;
	}
	
	putenv(argv[1]);
	return TCL_OK;
}

static int Finished = 0;

void Cdev_RegisterFD(int fd, int condition, void *) {
	if (cdevdebug) {
//		cout << "Register FD " << fd << " " << condition << " Finished = " << Finished << endl;
	}
	
	if (Finished == 1) {
		return;
	}
	
// SMH	Tcl_File f = Tcl_GetFile((ClientData) fd, TCL_UNIX_FD);	
	int f = fd;

	if (condition == 1) {
		Tcl_CreateFileHandler(f, TCL_READABLE|TCL_EXCEPTION, Cdev_FileProc, (ClientData) fd);
	} else {
		Tcl_DeleteFileHandler(f);
	}
}

void Cdev_ExitProc(ClientData) {
	Finished = 1;
}

typedef cdevRequestObject *cdevRequestObjectPtr;

int CdevCmd(ClientData, Tcl_Interp *interp, int argc, char **argv) {
	// cdev device message {tag value} {..} cb
	// cdev device message {tag value} {..} -context (tag value} {..} cb
	
	char **devices = NULL;
	int i, n, ndevices;
	Tcl_DString ds;
	cdevData result;
	cdevCallback *cb = NULL;
	callbackInfo *info;
	cdevRequestObjectPtr request, *requests;
	tclCdevData data;
	int delinfo = 0;
	
//	printf("CdevCmd: \n");

	if(argc < 3) {
		Tcl_AppendResult(interp, "cdev: wrong # args", (char *) NULL);
		return TCL_ERROR;
	}
	
	ndevices = 0;
	if(Tcl_SplitList(interp, argv[1], &ndevices, &devices) != TCL_OK) {
		return TCL_ERROR;
	}
	
	if(ndevices == 1) {
//		printf("CdevCmd: ndevices=1 argv[1]= %s argv[2]=%s\n",argv[1],argv[2]);
	      if((request=cdevRequestObject::attachPtr(argv[1],argv[2]))==NULL){
			if(cdevdebug == 1) {
				printf("::attachPtr returned NULL\n");
			}
			return TCL_ERROR;
		}
		free((char*)devices);
	}
	else {
		requests = new cdevRequestObjectPtr[ndevices];
		for(n = 0; n < ndevices; n++) {
			requests[n] = cdevRequestObject::attachPtr(devices[n], argv[2]);
		}
	}
	
//	printf("have got request object\n");
	data.parse(interp, argv, argc);
	
	if(data.state != TCL_OK) {
		return TCL_ERROR;
	}
	
	if(data.async) {
		// we want to sent it async
//		printf("data.async is true\n");
		if(cdevdebug == 1) {
			printf("data.async is true\n");
//			cout << "data.async is true" << endl;
		}	
		if(ndevices == 1) {
			info = new callbackInfo(interp, data.callbacktag);
			info->filtersame = data.filtersame;
			info->unroll = data.unroll;
			if(data.tagn != 0) {
				info->addtags(data.tagn, data.taglist);
			}
			
			if(strncmp(argv[2], "monitorOff", 10) == 0) {
				delinfo = 1;
				cb = new cdevCallback(Cdev_CallbackFunction, NULL);
			}
			else {
				cb = new cdevCallback(Cdev_CallbackFunction, (void *) info);
			}
						
			if(data.hascontext) {
				if(cdevdebug == 1) {
//					cout << "Context data: " << endl;
					printf("Context data: \n");
					data.context.asciiDump(stdout);
				}
				if(request->setContext(data.context) != CDEV_SUCCESS) {
					interp->result = "setContext error";
					delete info;
					return TCL_ERROR;
				}
			}
			if(cdevdebug == 1) {
//				cout << argv[1] << " " << argv[2] << " OutBound data: " << endl;
				data.out.asciiDump(stdout);
			}
			
			if(request->sendCallback(data.out, *cb) != 0) {
				delete info;
				delete cb;
				return TCL_ERROR;
			}
			else {
				if(delinfo) delete info;
				delete cb;
				cdevSystem::defaultSystem().flush();	
				return TCL_OK;
			}
		}
		else {
			// ndevices > 1, send each one separately
			int j = 0;
			
			if(strncmp(argv[2], "monitorOn", 9) == 0) {
				j = 1;
			}
			else if(strncmp(argv[2], "monitorOff", 10) == 0) {
				j = 2;
			}
			
			for(n = 0; n < ndevices; n++) {
				info = new callbackInfo(interp, devices[n], data.callbacktag);
				if(data.tagn != 0) {
					info->addtags(data.tagn, data.taglist);
				}
				info->filtersame = data.filtersame;
				info->unroll = data.unroll;
				if(j == 1) {
					cb = new cdevCallback(Cdev_CallbackFunction, (void *) info);
				}
				else if(j == 2) {
					delinfo = 1;
					cb = new cdevCallback(Cdev_CallbackFunction, NULL);
				}
				else {
					cb = new cdevCallback(Cdev_CallbackFunction, (void *) info);
				}
								
				if(data.hascontext) {
					if(requests[n]->setContext(data.context) != 0) {
						interp->result = "setContext error";
						delete requests;
						free((char *) devices);
						delete info;
						return TCL_ERROR;
					}
				}
				if(requests[n]->sendCallback(data.out, *cb) != CDEV_SUCCESS) {
					delete requests;
					delete info;
					free((char *) devices);
					delete cb;
					return TCL_ERROR;
				}
				else {
					if(delinfo) delete info;
					delete cb;
				}
			}
			delete requests;
			free((char *) devices);
			cdevSystem::defaultSystem().flush();	
			return TCL_OK;
		}
	}

	// send it sync
	if(cdevdebug == 1) {
//		cout << "will send it sync " << endl;
		printf("will send it sync\n ");
	}
	Tcl_DStringInit(&ds);
	
	if(ndevices == 1) {
		if(data.hascontext) {
			if(cdevdebug == 1) {
//				cout << "Context data: " << endl;
				printf("ndevices=1, Context data:\n ");
				data.context.asciiDump(stdout);
			}
			if(request->setContext(data.context) != CDEV_SUCCESS) {
				interp->result = "setContext error";
				return TCL_ERROR;
			}
		}
		
		if(cdevdebug == 1) {
//			cout << argv[1] << " " << argv[2] << " OutBound data: " << endl;
			printf(" CdevCmd: sync: ndevices=1, OutBound data:\n ");
			data.out.asciiDump(stdout);
		}
		
		if(request->send(data.out, result) != CDEV_SUCCESS) {
			sprintf(interp->result, "Is an error in send: %s %s", argv[1], argv[2]);
			return TCL_ERROR;
		} 

		
		if(cdevdebug == 1) {
//			cout << "Result data: " << endl;
			printf("ndevices=1, send ok \n");
			result.asciiDump(stdout);
			printf("finished ascii-dump \n");
		}
		
// 		if(STATUS_TAG !=- 1) {
// 			if(result.get(STATUS_TAG, &i) == CDEV_SUCCESS) {
// 				if(i != CDEV_SUCCESS) {
// 					if(SEVERITY_TAG !=- 1) {
// 						Tcl_DStringFree(&ds);
// 						result.get(SEVERITY_TAG, cs, 255);
// 						Tcl_DStringAppend(&ds, cs, -1);
// 						Tcl_DStringResult(interp, &ds);
// 					}
// 					else {
// 						interp->result = "cdev status != 0";
// 					}
// 					return TCL_ERROR;
// 				}
// 			}
// 		}
		
		if(Cdev_Handleresult(data.taglist, data.tagn, result, ds, 1) != TCL_OK) {
			interp->result = "cannot handle cdev result";
			Tcl_DStringFree(&ds);
			return TCL_ERROR;
		}
		
		if(cdevdebug == 1) {
//			cout << "Cdev_Handleresult Done " << endl;
			printf("Cdev_Handleresult Done \n");
		}
		Tcl_DStringResult(interp, &ds);
		if(cdevdebug == 1) {
//			cout << "will call interp->freeProc " << endl;
		}
//		interp->freeProc = TCL_DYNAMIC;
	}
	else {
		// ndevices > 1
		if(cdevdebug == 1) {
			printf(" CdevCmd: sync: ndevices=%d:\n ",ndevices);
		}
		int addbraces = 0;
		for(n = 0; n < ndevices; n++) {
			if(cdevdebug == 1) {
				printf(" doing device %d:\n ",n);
			}
			if(data.hascontext) {
				if(requests[n]->setContext(data.context) != CDEV_SUCCESS) {
					interp->result = "setContext error";
					delete requests;
					free((char *) devices);
					return TCL_ERROR;
				}
			}
			if(requests[n]->send(data.out, result) != CDEV_SUCCESS) {
				sprintf(interp->result, "error in send: %s %s", devices[n], argv[2]);
				delete requests;
				free((char *) devices);
				return TCL_ERROR;
			}
			if(cdevdebug == 1) {
				printf(" send worked:\n");
				result.asciiDump(stdout);
			}
			if(STATUS_TAG !=- 1) {
				if(result.get(STATUS_TAG, &i) == CDEV_SUCCESS) {
					if(i != CDEV_SUCCESS) {
						if(SEVERITY_TAG !=- 1) {
							Tcl_DStringFree(&ds);
							result.get(SEVERITY_TAG, cs, 255);
							Tcl_DStringAppend(&ds, cs, -1);
							Tcl_DStringResult(interp, &ds);
						}
						else {
							interp->result = "cdev status != 0";
						}
						delete requests;
						free((char *) devices);
						return TCL_ERROR;
					}
				}
			}
			if(Cdev_Handleresult(data.taglist, data.tagn, result, ds, ndevices) != TCL_OK) {
				interp->result = "cannot handle cdev result";
				Tcl_DStringFree(&ds);
				return TCL_ERROR;
			}
		}
		Tcl_DStringResult(interp, &ds);
// 	changed SMH : 13MAR98 : do not know why - but this line dumps core 
//		interp->freeProc = TCL_DYNAMIC;
		delete requests;
		free((char *) devices);
	}

	if(cdevdebug == 1) {
		printf(" all fine - will return TCL_OK:\n");
	}
	return TCL_OK;
}

int CdevDirectoryCmd(ClientData, Tcl_Interp *interp, int argc, char **argv) {
	int ndevices = 0;
	cdevData result;
	Tcl_DString ds;
	cdevCallback *cb = NULL;
	callbackInfo *info;
	tclCdevData data;
	int delinfo = 0;
	
	if(cdevdebug == 1) {
		printf("CdevDirectoryCmd \n");
	}
	if(argc < 3) {
		Tcl_AppendResult(interp, "cdevDirectory: wrong # args", (char *) NULL);
		return TCL_ERROR;
	}
	
	if(argc < 3) {
		Tcl_AppendResult(interp, "cdevDirectory: wrong # args", (char *) NULL);
		return TCL_ERROR;
	}
	
	cdevDevice &device = cdevDevice::attachRef("cdevDirectory");
	
	data.parse(interp, argv-1, argc+1);
	
	if(data.state != TCL_OK) {
		return TCL_ERROR;
	}
	
	if(data.async) {
		// we want to sent it async
		
		info = new callbackInfo(interp, data.callbacktag);
		if(data.tagn != 0) {
			info->addtags(data.tagn, data.taglist);
		}
		
		if(strncmp(argv[2], "monitorOn", 9) == 0) {
			cb = new cdevCallback(Cdev_CallbackFunction, (void *) info);
		}
		else if(strncmp(argv[2], "monitorOff", 10) == 0) {
			delinfo = 1;
			cb = new cdevCallback(Cdev_CallbackFunction, NULL);
		}
		else {
			cb = new cdevCallback(Cdev_CallbackFunction, (void *) info);
		}
				
		if(cdevdebug == 1) {
//			cout << argv[1] << " " << argv[2] << " OutBound data: " << endl;
			data.out.asciiDump(stdout);
		}
		
		if(device.sendCallback(argv[1], data.out, *cb) != 0) {
			delete info;
			return TCL_ERROR;
		}
		else {
			if(delinfo) delete info;
			return TCL_OK;
		}
	}
	// send it sync
	Tcl_DStringInit(&ds);
	
	if(cdevdebug == 1) {
//		cout << argv[1] << " " << argv[2] << " OutBound data: " << endl;
		data.out.asciiDump(stdout);
	}
	
	if(device.send(argv[1], data.out, result) != CDEV_SUCCESS) {
		sprintf(interp->result, "error in send: %s", argv[1]);
		return TCL_ERROR;
	}
	
	if(cdevdebug == 1) {
//		cout << "Result data: " << endl;
		result.asciiDump(stdout);
	}
	
// 	if(STATUS_TAG !=- 1) {
// 		if(result.get(STATUS_TAG, &i) == CDEV_SUCCESS) {
// 			if(i != CDEV_SUCCESS) {
// 				if(SEVERITY_TAG !=- 1) {
// 					Tcl_DStringFree(&ds);
// 					result.get(SEVERITY_TAG, cs, 255);
// 					Tcl_DStringAppend(&ds, cs, -1);
// 					Tcl_DStringResult(interp, &ds);
// 				}
// 				else {
// 					interp->result = "cdev status != 0";
// 				}
// 				return TCL_ERROR;
// 			}
// 		}
// 	}
	
	if(Cdev_Handleresult(data.taglist, data.tagn, result, ds, 1) != TCL_OK) {
		interp->result = "cannot handle cdev result";
		Tcl_DStringFree(&ds);
		return TCL_ERROR;
	}
	
	Tcl_DStringResult(interp, &ds);
//	interp->freeProc = TCL_DYNAMIC;
	
	return TCL_OK;
}

char *getCdevErrorCode(int code) {
	switch (code) {
		case -2: return "CDEV_WARNING";
		case -1: return "CDEV_ERROR";
		case  0: return "CDEV_SUCCESS";
		case  1: return "CDEV_INVALIDOBJ";
		case  2: return "CDEV_INVALIDARG";
		case  3: return "CDEV_INVALIDSVC";
		case  4: return "CDEV_INVALIDOP";
		case  5: return "CDEV_NOTCONNECTED";
		case  6: return "CDEV_IOFAILED";
		case  7: return "CDEV_CONFLICT";
		case  8: return "CDEV_NOTFOUND";
		case  9: return "CDEV_TIMEOUT";
		case 10: return "CDEV_CONVERT";
		case 11: return "CDEV_OUTOFRANGE";
		case 12: return "CDEV_NOACCESS";
		case 13: return "CDEV_ACCESSCHANGED";
		case 60: return "CDEV_DISCONNECTED";
		case 61: return "CDEV_RECONNECTED";
		case 70: return "CDEV_DELETE_CALLBACK";
		default: return "UNKNOWN";
	}
}

int CdevErrorCodeCmd(ClientData, Tcl_Interp *interp, int argc, char **argv) {
	// cdevconvertcode code
		
	if(argc < 2) {
		Tcl_AppendResult(interp, "cdevconvertcode: wrong # args", (char *) NULL);
		return TCL_ERROR;
	}
	
	int code = atoi(argv[1]);
	interp->result = getCdevErrorCode(code);
// 	switch (code) {
// 		case -2: interp->result = "CDEV_WARNING"; break;
// 		case -1: interp->result = "CDEV_ERROR"; break;
// 		case  0: interp->result = "CDEV_SUCCESS"; break;
// 		case  1: interp->result = "CDEV_INVALIDOBJ"; break;
// 		case  2: interp->result = "CDEV_INVALIDARG"; break;
// 		case  3: interp->result = "CDEV_INVALIDSVC"; break;
// 		case  4: interp->result = "CDEV_INVALIDOP"; break;
// 		case  5: interp->result = "CDEV_NOTCONNECTED"; break;
// 		case  6: interp->result = "CDEV_IOFAILED"; break;
// 		case  7: interp->result = "CDEV_CONFLICT"; break;
// 		case  8: interp->result = "CDEV_NOTFOUND"; break;
// 		case  9: interp->result = "CDEV_TIMEOUT"; break;
// 		case 10: interp->result = "CDEV_CONVERT"; break;
// 		case 11: interp->result = "CDEV_OUTOFRANGE"; break;
// 		case 12: interp->result = "CDEV_NOACCESS"; break;
// 		case 13: interp->result = "CDEV_ACCESSCHANGED"; break;
// 		case 60: interp->result = "CDEV_DISCONNECTED"; break;
// 		case 61: interp->result = "CDEV_RECONNECTED"; break;
// 		case 70: interp->result = "CDEV_DELETE_CALLBACK"; break;
// 		default: interp->result = "UNKNOWN";
// 	}
	return TCL_OK;
}
	 
// #define CDEV_WARNING       -2  /* Failure of function is non-consequential */
// #define CDEV_ERROR         -1  /* Errors that are not in any categories    */
// #define CDEV_SUCCESS        0  /* cdev success                             */
// #define CDEV_INVALIDOBJ     1  /* invalid cdev objects                     */
// #define CDEV_INVALIDARG     2  /* invalid argument passed to cdev calls    */
// #define CDEV_INVALIDSVC     3  /* wrong service during dynamic loading     */
// #define CDEV_INVALIDOP      4  /* operation is unsupported (collection)    */
// #define CDEV_NOTCONNECTED   5  /* not connected to low network service     */
// #define CDEV_IOFAILED       6  /* low level network service IO failed      */
// #define CDEV_CONFLICT       7  /* conflicts of data types or tags          */
// #define CDEV_NOTFOUND       8  /* cdev cannot find user request (cdevData) */
// #define CDEV_TIMEOUT        9  /* time out                                 */
// #define CDEV_CONVERT       10  /* cdevData conversion error                */
// #define CDEV_OUTOFRANGE    11  /* value out of range for device attribute  */
// #define CDEV_NOACCESS      12  /* insufficient access to perform request   */
// #define CDEV_ACCESSCHANGED 13  /* change in access permission of device    */
// #define CDEV_DISCONNECTED      60 /* channel has been disconnected         */
// #define CDEV_RECONNECTED       61 /* channel has been reconnected          */
// #define CDEV_DELETE_CALLBACK   70 /* the callback object will be deleted   */
// 
// /* Request object state values                                             */
// #define CDEV_STATE_CONNECTED    0 /* request object is connected to device */
// #define CDEV_STATE_NOTCONNECTED 1 /* request object is not connected       */
// #define CDEV_STATE_INVALID      2 /* request object is invalid             */
// 
// /* Request object access values                                            */
// #define CDEV_ACCESS_NONE        0 /* no access to specified attribute      */
// #define CDEV_ACCESS_READONLY    1 /* read-only access to attribute         */
// #define CDEV_ACCESS_WRITE       2 /* read-write access to attribute        */
// 
// /* cdevError class severity codes                                          */
// #define CDEV_SEVERITY_INFO      0 /* informative message                   */
// #define CDEV_SEVERITY_WARN      1 /* warning message                       */
// #define CDEV_SEVERITY_ERROR     2 /* error message                         */
// #define CDEV_SEVERITY_SEVERE    3 /* severe or fatal error message         */

//  SMH changed for tcl8.0 
// extern"C"int Cdev_Init(Tcl_Interp *interp) {
extern"C"int Tclcdev_Init(Tcl_Interp *interp) {

//	printf("This is Tclcdev_Init \n");

	int i, numFD = 20;
	int fds[20];
	cdevSystem::defaultSystem().getFd(fds, numFD);
	for (i=0;i<numFD;i++) {
//		cout << " fd " << i << endl;
		Cdev_RegisterFD(fds[i],1,NULL);
	}
		
	cdevSystem::defaultSystem().addFdChangedCallback(Cdev_RegisterFD, NULL);
	
	if(Tcl_PkgProvide(interp, "Cdev", TCLCDEV_VERSION) != TCL_OK) {
		return TCL_ERROR;
	}
	if(Tcl_PkgProvide(interp, "CdevFormat", TCLCDEV_VERSION) != TCL_OK) {
		return TCL_ERROR;
	}
	
	cdevFormat = new char[strlen(defaultFormat)+1];
	strcpy(cdevFormat, defaultFormat);

	Tcl_CreateCommand(interp, "cdev", CdevCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "cdevDirectory", CdevDirectoryCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "cdevdebug", CdevDebugCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "cdevformat", CdevFormatCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "cdeverrorcode", CdevErrorCodeCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "putenv", PutEnvCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

	Tcl_CreateExitHandler(Cdev_ExitProc, NULL);	
//	Cdev_UpdateProc(NULL);
//	printf("end of Tclcdev_Init \n");
	return TCL_OK;
}



