// Cdev collection access from tcl
// Johannes van Zeijts
//				March 97, first version

/*
	cdevcollection $devices
	cdevcollection $devices $attr
	
	$c set attrib $value

	$c get devices
	$c get attrib
	$c get
	$c get vector $v // store in blt::vector $v
	
	$c $message // send to cdev and return results

*/

#include <tcl.h>
#include "blt.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)

typedef cdevRequestObject *cdevRequestObjectPtr;

// class DeviceEntry {
// public:
// 	DeviceEntry(char*);
// 	~DeviceEntry();
// 	
// 	cdevRequestObject *request;
// 	cdevData result;
// 	int status;
// };


class TclCdevCollection {
public:
	TclCdevCollection(int, char**);
	~TclCdevCollection();		
		
	char *getAttrib();
	void setAttrib(char*);
	
	void getDevices(Tcl_Interp *);
	void getStatus(Tcl_Interp *);
	void getResult(Tcl_Interp *);
	void getCallback(Tcl_Interp *);
	int getLength() { return ndevices;}
	void getVector(double*);
	
	static void deleteCollection(ClientData);
	
	int send();
	int sendCallback(Tcl_Interp*, char*);
	int checkCallback();

	static void callbackFunction(int, void *, cdevRequestObject&, cdevData &);
	static void TimerHandler(ClientData);
	
private:
	cdevRequestObjectPtr *requests;
	cdevData *results;
	char **devices;
	int ndevices;
	char *attrib;
	int *status, *callbacks;
};

TclCdevCollection::TclCdevCollection(int n, char** s) : ndevices(0), devices(NULL), attrib(NULL) {
	if (n < 0) return;
	
	ndevices = n;
	devices = new char*[ndevices];
	for (int i = 0; i<ndevices; i++) {
		devices[i] = new char[strlen(s[i])+1];
		strcpy(devices[i],s[i]);
	}	
	requests = new cdevRequestObjectPtr[ndevices];
	results = new cdevData[ndevices];
	status = new int[ndevices];
	callbacks = new int[ndevices];
}

TclCdevCollection::~TclCdevCollection() {
	for (int i = 0; i<ndevices; i++) {
		delete devices[i];
	}	
	delete [] devices;	
	delete [] results;
	delete callbacks;
	delete status;
	delete attrib;
	delete requests;
}

class callbackInfo1 {
public:
	callbackInfo1(TclCdevCollection*, int);
	TclCdevCollection* ptr;
	int index;
};

class callbackInfo2 {
public:
	callbackInfo2(Tcl_Interp*, TclCdevCollection*, char*);
	TclCdevCollection* ptr;
	Tcl_Interp* interp;
	char *s;
};

callbackInfo1::callbackInfo1(TclCdevCollection* c, int i) : ptr(c), index(i) {
}

callbackInfo2::callbackInfo2(Tcl_Interp* intp, TclCdevCollection* c, char* v) : interp(intp), ptr(c) {
	s = new char[strlen(v)+1];
	strcpy(s,v);
}

void TclCdevCollection::callbackFunction(int stat, void *userarg, cdevRequestObject&, cdevData &result) {
	callbackInfo1 *info = (callbackInfo1 *) userarg;
	TclCdevCollection *c = info->ptr;
	c->status[info->index] = stat;
	c->results[info->index] = result;
	c->callbacks[info->index] = 1;
}

int TclCdevCollection::checkCallback() {
	int stat = 0;
	for (int i = 0; i < ndevices; i++) {
		if (callbacks[i] == 0) return 0;
	}
	return 1;	
}

void TclCdevCollection::TimerHandler(ClientData dummy) {
	cdevSystem::defaultSystem().poll();
	callbackInfo2 *info = (callbackInfo2 *) dummy;
	if (info->ptr->checkCallback()) {
		// set something
		Tcl_SetVar2(info->interp, "Control", info->s, "1", TCL_GLOBAL_ONLY);
		delete info;
	} else { /// try again later
		Tcl_CreateTimerHandler(50, TclCdevCollection::TimerHandler, dummy);
	}
}

void TclCdevCollection::setAttrib(char* a) {
	char message[256];
	delete attrib;
	attrib = new char[strlen(a)+1];
	strcpy(attrib, a);
	strcpy(message,"get ");
	strcat(message, attrib);
	for(int i = 0; i < ndevices; i++) {
		requests[i] = cdevRequestObject::attachPtr(devices[i], message);
	}
}

char* TclCdevCollection::getAttrib() {
	return attrib;
}

void TclCdevCollection::getDevices(Tcl_Interp *interp) {
	int i;
	for (i = 0; i < ndevices; i++) {
		Tcl_AppendElement(interp, devices[i]);
	}
}

void TclCdevCollection::getStatus(Tcl_Interp *interp) {
	int i;
	char res[256];
	for (i = 0; i < ndevices; i++) {
		sprintf(res, "%d", status[i]);
		Tcl_AppendElement(interp, res);
	}
}

void TclCdevCollection::getCallback(Tcl_Interp *interp) {
	int i;
	char res[256];
	for (i = 0; i < ndevices; i++) {
		sprintf(res, "%d", callbacks[i]);
		Tcl_AppendElement(interp, res);
	}
}

void TclCdevCollection::getResult(Tcl_Interp *interp) {
	int i;
	char res[300];
	for (i = 0; i < ndevices; i++) {
		results[i].get(VALUE_TAG, res, 255);
		Tcl_AppendElement(interp, res);
	}
}

void TclCdevCollection::getVector(double *res) {
	for (int i = 0; i < ndevices; i++) {
		results[i].get(VALUE_TAG, &res[i]);
	}
}

int TclCdevCollection::send() {
	int stat = CDEV_SUCCESS;
	int i;
	for (i = 0; i < ndevices; i++) {
		results[i].remove();
		status[i] = requests[i]->send(NULL, results[i]);
		if ((status[i] != CDEV_SUCCESS) && (stat == CDEV_SUCCESS)) {
			stat = CDEV_ERROR;
		}
	}
	return stat;
}

int TclCdevCollection::sendCallback(Tcl_Interp* interp, char* v) {
	int stat = CDEV_SUCCESS;
	for (int i = 0; i < ndevices; i++) {
		results[i].remove();
		callbacks[i] = 0;
		callbackInfo1* info1 = new callbackInfo1(this, i);
		cdevCallback* cb = new cdevCallback(TclCdevCollection::callbackFunction, (void *) info1);
		status[i] = requests[i]->sendCallback(NULL, *cb);
		if ((status[i] != CDEV_SUCCESS) && (stat == CDEV_SUCCESS)) {
			stat = CDEV_ERROR;
		}
	}
	callbackInfo2 *info2 = new callbackInfo2(interp, this, v);
	Tcl_SetVar2(interp, "Control", v, "0", TCL_GLOBAL_ONLY);
	Tcl_CreateTimerHandler(50, TclCdevCollection::TimerHandler, (ClientData) info2);
	return stat;
}

void TclCdevCollection::deleteCollection(ClientData clientData) {
	TclCdevCollection *collectionPtr;
	collectionPtr = (TclCdevCollection *) clientData;
	delete collectionPtr;
}

int CollectionCmd(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]){
	TclCdevCollection *collectionPtr;
	collectionPtr = (TclCdevCollection *) clientData;

	if (strcmp(argv[1], "get") == 0) {
		if (argc == 2) { // send it out to cdev  // get
			if (collectionPtr->getAttrib() == NULL) {
				interp->result = "did not set attrib";
				return TCL_ERROR;
			}
			if (collectionPtr->send() == CDEV_SUCCESS) {
				collectionPtr->getResult(interp);
				return TCL_OK;
			}
			interp->result = "encountered cdev error";
			return TCL_ERROR;
		}
		if (strcmp(argv[2], "attrib") == 0) { // get attrib
			Tcl_SetResult(interp, collectionPtr->getAttrib(), TCL_STATIC);
			return TCL_OK;
		}
		if (strcmp(argv[2], "devices") == 0) { // get devices
			collectionPtr->getDevices(interp);
			return TCL_OK;
		}
		if (strcmp(argv[2], "status") == 0) { // get status
			collectionPtr->getStatus(interp);
			return TCL_OK;
		}
		if (strcmp(argv[2], "callback") == 0) { // get status
			collectionPtr->getCallback(interp);
			return TCL_OK;
		}
		if (strcmp(argv[2], "vector") == 0) { // get vector
			if (argc != 4) return TCL_ERROR;
			Blt_Vector vecInfo;
	
			if (!Blt_VectorExists(interp, argv[3])) {
				interp->result = "vector does not exist";
				return TCL_ERROR;
			}
			if (collectionPtr->send() != CDEV_SUCCESS) {
				interp->result = "encountered cdev error";
				return TCL_ERROR;
			}
			if (vecInfo.arraySize < collectionPtr->getLength()) { // resize if necc.
				if (Blt_ResizeVector(interp, argv[3], collectionPtr->getLength()) != TCL_OK) return TCL_ERROR;
			}
	
			if (Blt_GetVector(interp, argv[3], &vecInfo) != TCL_OK) return TCL_ERROR;

			vecInfo.numValues = collectionPtr->getLength();
			vecInfo.valueArr = (double *) malloc(sizeof(double) * collectionPtr->getLength());
			
			collectionPtr->getVector(vecInfo.valueArr);
			if (Blt_ResetVector(interp, argv[3], &vecInfo, TCL_DYNAMIC) != TCL_OK) {
				return TCL_ERROR;
			}
			return TCL_OK;
		}
	}
	
	if (strcmp(argv[1], "set") == 0) {
		if (argc != 4) return TCL_ERROR;
		if (strcmp(argv[2], "attrib") == 0) { // set attrib $value
			collectionPtr->setAttrib(argv[3]);
			return TCL_OK;
		}
	}	

	if (strcmp(argv[1], "sendcallback") == 0) {
		if (strcmp(argv[2], "vector") == 0) { // sendcallback vector $v
			if (argc != 4) return TCL_ERROR;
			Blt_Vector vecInfo;
	
			if (!Blt_VectorExists(interp, argv[3])) {
				interp->result = "vector does not exist";
				return TCL_ERROR;
			}
			if (collectionPtr->sendCallback(interp, argv[3]) != CDEV_SUCCESS) {
				interp->result = "encountered cdev error";
				return TCL_ERROR;
			}
			return TCL_OK;
		}
		return TCL_ERROR;
	}
		
	if (strcmp(argv[1], "checkcallback") == 0) {
		if (collectionPtr->checkCallback()) {
			interp->result = "1";
		} else {
			interp->result = "0";
		}
		return TCL_OK;
	}

	if (strcmp(argv[1], "getcallback") == 0) {
		if (strcmp(argv[2], "vector") == 0) { // getcallback vector $v
			if (argc != 4) return TCL_ERROR;
			Blt_Vector vecInfo;
	
			if (!Blt_VectorExists(interp, argv[3])) {
				interp->result = "vector does not exist";
				return TCL_ERROR;
			}

			if (!collectionPtr->checkCallback()) {
				interp->result = "callbacks not ready";
				return TCL_ERROR;
			}

			if (vecInfo.arraySize < collectionPtr->getLength()) { // resize if necc.
				if (Blt_ResizeVector(interp, argv[3], collectionPtr->getLength()) != TCL_OK) return TCL_ERROR;
			}
	
			if (Blt_GetVector(interp, argv[3], &vecInfo) != TCL_OK) return TCL_ERROR;

			vecInfo.numValues = collectionPtr->getLength();
			vecInfo.valueArr = (double *) malloc(sizeof(double) * collectionPtr->getLength());
			
			collectionPtr->getVector(vecInfo.valueArr);
			if (Blt_ResetVector(interp, argv[3], &vecInfo, TCL_DYNAMIC) != TCL_OK) {
				return TCL_ERROR;
			}
			return TCL_OK;
		}
	}
		
	interp->result = "unknown command";
	return TCL_ERROR;
}

int MakeCollectionCmd(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]){
	TclCdevCollection *collectionPtr;
	static int id = 0;
	int c1;
	char** s1;
	
	if (argc < 2 || argc >3) {
		interp->result = "wrong # args";
		return TCL_ERROR;
	}
	
	if(Tcl_SplitList(interp, argv[1], &c1, &s1) != TCL_OK) {
		return TCL_ERROR;
	}

	collectionPtr = new TclCdevCollection(c1,s1);
	free ((char*) s1);
	
	sprintf(interp->result, "col%d", id);
	id++;

	Tcl_CreateCommand(interp, interp->result, CollectionCmd, (ClientData) collectionPtr, TclCdevCollection::deleteCollection);
	
	if (argc == 3) {
		collectionPtr->setAttrib(argv[2]);
	}
	
	return TCL_OK;
}


extern "C" int Cdevcollection_Init(Tcl_Interp *interp) {
	
	Tcl_PkgRequire(interp, "Blt", "2.1", 0);
	Tcl_PkgRequire(interp, "Cdev", "1.4", 0);
	
	if(Tcl_PkgProvide(interp, "Cdevcollection", CDEV_VERSION) != TCL_OK) {
		return TCL_ERROR;
	}

	Tcl_CreateCommand(interp, "cdevcollection", MakeCollectionCmd, (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
	return TCL_OK;
}


