//---------------------------------------------------------------------------
// 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: cdevUtil.cc
//	A simple command line utility designed to provide a direct user
//	interface to cdevServices.  
//
// Author: Walt Akers
//
// Revision History:
//   cdevUtil.cc,v
// Revision 1.1  1998/02/13  17:11:59  akers
// Ongoing development
//
// Revision 1.1  1998/02/13  14:42:04  akers
// Ongoing development
//
// Revision 1.13  1997/07/16  21:25:17  akers
// Corrected patch that was installed in hpux-10.xx
//
// Revision 1.12  1997/03/25  22:24:44  akers
// Development in support of a new cdevDirectory
//
// Revision 1.11  1997/01/30  20:36:08  akers
// Ongoing development and improvement
//
// Revision 1.10  1996/06/26  15:54:27  akers
// Modifications to support multiple OS Versions
//
// Revision 1.9  1996/03/28  19:13:48  akers
// Minor changes in support of new version
//
// Revision 1.8  1995/11/27  14:23:11  akers
// Made cdevUtil VMS compliant
//
// Revision 1.7  1995/10/31  14:53:23  akers
// Minor Update
//
// Revision 1.6  1995/10/30  13:33:24  akers
// Added cdev specific version of strncpy
//
// Revision 1.5  1995/10/26  19:23:57  akers
// Added SIGSEGV Handler to cdevUtil
//
// Revision 1.4  1995/08/23  18:49:15  akers
// Added batch interface and multiple dimensional value display.
//
// Revision 1.3  1995/08/23  13:11:51  akers
// New source code release of cdevUtil
//
//--------------------------------------------------------------------------
#ifdef _WIN32
	#include <winsock2.h>
	#include <ctype.h>
	#include <stdio.h>
	#include <io.h>
	#include <stdlib.h>
#else
	#include <sys/time.h>
	#include <sys/ioctl.h>
	#include <unistd.h>
	#include <ctype.h>
	#include <signal.h>
#endif

#include <cdevSystem.h>
#include <cdevRequestObject.h>
#include <cdevDevice.h>
#include <cdevGroup.h>
#include <cdevCommon.h>
#include <cdevUtil.h>

#ifndef WIN32
	#define min(a, b)  ((a)<(b)?(a):(b))
	#define max(a, b)  ((a)>(b)?(a):(b))
#endif

char         * CDEV_PROMPT    = "> ";
int            debugging_mode = 0;
cdevSystem   & defSystem      = cdevSystem::defaultSystem();

char           globalLastDevice[255] = "\0";
char           globalLastAttrib[255] = "VAL";
char           globalParms     [4][255]; 
struct	{ 
	char tag[128]; 
	char value[255]; 
	}  globalTaggedData[100];

#ifndef __VMS
	// Under VMS this signal occurs in situations that are not abnormal,
	// and thus cannot be used as simply as this.
	// See the "DEC C Run Time Library Reference Manual" for details.
	void SIGSEGV_Handler(int) { exit(1); }
#endif	/* __VMS not defined */

// *****************************************************************************
// * defCallback:
// *	This is the default callback function used by the system.
// ***************************************************************************** 
void defCallback ( int status, void * userarg, cdevRequestObject & reqObj, cdevData & data )
{
cdevCallbackArg * arg = (cdevCallbackArg *)userarg;
char    temp[255];
char ** value = NULL;
size_t  count = 0;

sprintf(temp, "None"); 
if(data.getElems("value", &count)==CDEV_SUCCESS)
	{
	if(count>1)
		{
		value    = new char *[count+1];
		value[0] = NULL;		
		data.get("value", value);
		}
	else data.get("value", temp, 255);
	}
	
fprintf(stdout, "%s %d: Device: %s - Message: %s", 
	status==CDEV_SUCCESS?"SUCCESS":"FAILURE",status,
	reqObj.device().name(), 
	reqObj.message());

if(count>1)
	{
	fprintf(stdout, "\n%s", CDEV_PROMPT);
	for(int i=0; i<count; i++)
		{
		fprintf(stdout, "         Value:  %s\n%s", value[i], CDEV_PROMPT);
		delete value[i];
		}
	delete value;
	}
else 	fprintf(stdout, " - Value: %s\n%s", temp, CDEV_PROMPT);

if(status == CDEV_SUCCESS && debugging_mode) 
	{
	fprintf(stdout, "-------------------- DEBUG OUTPUT -------------------\n");
	data.asciiDump(stdout);
	fprintf(stdout, "------------------ END DEBUG OUTPUT -----------------\n");
	}

fflush (stdout);
if(arg!=NULL && arg->permanent()==0) delete arg;
} 


// *****************************************************************************
// * monitorCallback :
// *	This is the monitoring callback.  When the user issues a monitorOn
// *	command, this is the callback function that will be used to process
// *	the results.
// *****************************************************************************
void monitorCallback ( int status, void * , cdevRequestObject & reqObj, cdevData & data )
{
char * attrib     = strstr(reqObj.message(), "monitorOn");
char   value[255];
*value = 0;

if(attrib) attrib+=10;
data.get("value", value, 255);

if(status==CDEV_SUCCESS)
	{
	fprintf(stdout, 
		"MonitorOn Device: %s - Attrib: %s ... value changed to %s\n%s",
		reqObj.device().name(), attrib, value, CDEV_PROMPT);
	fflush (stdout);
	}
}


// *****************************************************************************
// * decomposeMessage :
// * 	This function breaks a cdev message string down into its components
// * 	to allow it to be transmitted through the system.
// *****************************************************************************
void decomposeMessage ( char * message )
{
int     idx    = 0;
int     tagIdx = 0;
memset(&globalParms,      0, sizeof(globalParms));
memset(&globalTaggedData, 0, sizeof(globalTaggedData));

char * tagPtr;
if((tagPtr = strchr(message, '='))!=NULL && tagPtr>message)	
	{
	tagPtr--;
	while(tagPtr>message && isspace(*tagPtr))  tagPtr--;
	while(tagPtr>message && !isspace(*tagPtr)) tagPtr--;
	if(tagPtr > message)
		{
		*tagPtr = 0;
		tagPtr++;
		}
	else tagPtr = NULL;
	}
else tagPtr = NULL;	

char * ptr = message;
while (ptr!=NULL && *ptr && idx<4)
	{
	while(*ptr && isspace(*ptr)) ptr++;
	char * eptr = ptr;

	if(idx<3) while(*eptr && !isspace(*eptr)) eptr++;
	else      while(*eptr && isprint(*eptr)) eptr++;
	
	cdevStrncpy(globalParms[idx], ptr, min(255, (eptr-ptr)+1));
	ptr = eptr;
	idx++;
	}

// *****************************************************************************
// * Always place an entry for the value tag in the first element of the
// * globalTaggedData structure.  This will give you room to later advance
// * parameters into the tagged data list without having to search to the
// * end.
// *****************************************************************************
if(*globalParms[3]!='\0')
	{
	strcpy(globalTaggedData[0].tag, "value");
	cdevStrncpy(globalTaggedData[0].value, globalParms[3], 255);
	}
else strcpy(globalTaggedData[0].tag, "value");
tagIdx++;
	
while(tagPtr && *tagPtr && tagIdx<99)
	{
	char * tagName      = NULL;
	char * endTagName   = NULL;
	char * tagValue     = NULL;
	char * endTagValue  = NULL;
	char * nextTag      = NULL;
	
	tagName = tagPtr;
	while(*tagName && isspace(*tagName)) tagName++;
	endTagName  = strchr(tagName, '=');
	if(endTagName && *endTagName)
		{
		tagValue = endTagName+1;
		while(isspace(*(endTagName-1))) endTagName--;
		}
	while(tagValue && *tagValue && isspace(*tagValue)) tagValue++;
	if((nextTag = strchr(tagValue, '='))!=NULL)
		{
		nextTag--;
		while(isspace(*nextTag)   && nextTag>tagValue) nextTag--;
		while(!isspace(*nextTag) && nextTag>tagValue) nextTag--;
		if(isspace(*nextTag))
			{
			endTagValue = nextTag;
			nextTag++;
			while(isspace(*(endTagValue-1))) endTagValue--;
			}
		else 
			{
			nextTag = NULL;
			endTagValue = tagValue;
			}	
		}
	else 
		{
		endTagValue = tagValue;
		while(endTagValue && *endTagValue) endTagValue++;
		}
	if(tagName && tagName<endTagName && tagValue && tagValue<endTagValue)
		{
		cdevStrncpy(globalTaggedData[tagIdx].tag,   tagName, min(128, (endTagName-tagName)+1));
		cdevStrncpy(globalTaggedData[tagIdx].value, tagValue, min(255, (endTagValue-tagValue)+1));
		tagIdx++;
		}		
	tagPtr = nextTag;
	}
}


int useLastDevice ( void )
{
int retval = 0;

if(*globalLastDevice && strcmp(globalParms[0], globalLastDevice))
	{
	if(*globalParms[2] || *globalParms[3])
		{
		strcpy(globalTaggedData[0].value, globalParms[2]);
		if(*globalParms[3]) 
			{
			strcat(globalTaggedData[0].value,  " ");
			strcat(globalTaggedData[0].value, globalParms[3]);
			}
		}
	strcpy(globalParms[2], globalParms[1]);
	strcpy(globalParms[1], globalParms[0]);
	strcpy(globalParms[0], globalLastDevice);
	}
else retval = -1;

return retval;
}


// *****************************************************************************
// * submitMessage :
// *	This function will attempt to submit a request to a cdev device.
// *****************************************************************************
int submitMessage ( char * messageIn )
{
char            * device;
char              messageOut[512];
int               done = 0, pass = 0;
int               i;
int               result = -1;

if(debugging_mode) 
	fprintf(stdout, "-------------------- DEBUG OUTPUT -------------------\n");

decomposeMessage(messageIn);
if(!strcmp(globalParms[0], "set")        ||
   !strcmp(globalParms[0], "get")        ||
   !strcmp(globalParms[0], "monitorOn")  ||
   !strcmp(globalParms[0], "monitorOff") ||
   *globalParms[1]=='\0')
   	{
	done = useLastDevice();  	
   	}

// *****************************************************************************
// * Use VAL as the default attribute name.
// *****************************************************************************
if(*globalParms[2]=='\0') strcpy(globalParms[2], globalLastAttrib);

while(!done && 
      result!=0 && 
      *globalParms[0]!='\0' &&	// Device
      *globalParms[1]!='\0' &&	// Verb
      *globalParms[2]!='\0')	// Attribute
	{
	cdevCallbackArg * arg;
	device = globalParms[0];
	sprintf(messageOut, "%s %s", globalParms[1], globalParms[2]);
	
	if(!strcmp(globalParms[1], "monitorOn") )
	  arg = new cdevCallbackArg(monitorCallback, 1);
	else if (!strcmp(globalParms[1],"monitorOff") )
	  arg = new cdevCallbackArg(NULL,0);
	else	
	  arg = new cdevCallbackArg(defCallback, 0);

	for(i=0; globalTaggedData[i].tag[0]!=0; i++)
		{
		double dValue   = atof(globalTaggedData[i].value);
		int numericFlag = (globalTaggedData[i].value[0]=='0' || dValue!=0.0)?1:0;
		numericFlag = 0;

		if(numericFlag) arg->data().insert(globalTaggedData[i].tag, dValue);
		else arg->data().insert(globalTaggedData[i].tag, globalTaggedData[i].value);
		}

	cdevDevice        * dev    = defSystem.getDevice(device);
	cdevRequestObject * reqObj = dev->getRequestObject(messageOut);

	if(debugging_mode)
		{
		fprintf(stdout, "Attempting to transmit using the following parameters\n");
		fprintf(stdout, "Device: %s %d\n", globalParms[0],strlen(globalParms[0]));
		fprintf(stdout, "Verb:   %s %d\n", globalParms[1],strlen(globalParms[1]));
		fprintf(stdout, "Attrib: %s %d\n", globalParms[2],strlen(globalParms[2]));

		for(i=0; globalTaggedData[i].tag[0]!=0; i++)
			{
			if(globalTaggedData[i].value[0]!=0)
				{
				fprintf(stdout, "Tag: %s = %s %d\n", 
					globalTaggedData[i].tag, 
					globalTaggedData[i].value, strlen(globalTaggedData[i].value));
				}
			}
		fprintf(stdout, "\n");
		}

	if(reqObj && 
	   reqObj != defSystem.errorRequestObject())
		{
		  if (debugging_mode) {
		    printf("data to send:\n");
		    arg->data().asciiDump();
		  }

		  result = reqObj->sendCallback(arg->data(), arg->callback());
		  if (debugging_mode) printf("result %d\n",result);
		}
	else result = -1;
	
	if(result!=0) 
		{
		delete arg;
		done = useLastDevice();
		}
	else 
		{
		strcpy(globalLastDevice, globalParms[0]);
		strcpy(globalLastAttrib, globalParms[2]);
		if(debugging_mode) fprintf(stdout, "Transmission was successful.\n");
		}
	}

if(result != CDEV_SUCCESS) fprintf(stdout, "Could not transmit message as specified. %d\n",result);

if(debugging_mode) 
	fprintf(stdout, "------------------ END DEBUG OUTPUT -----------------\n");

return result;
}

// *****************************************************************************
// * printHelp:
// *	Displays a help message to the user.
// *****************************************************************************
void printHelp ( void )
{
fprintf(stdout, "\n");
fprintf(stdout, "------------------------------------------------------------------------------\n");
fprintf(stdout, "cdevUtil: A command line interface to cdev...\n");
fprintf(stdout, "\n");
fprintf(stdout, "INTERNAL COMMANDS:\n");
fprintf(stdout, "	debug on  - turns on debugging mode\n");
fprintf(stdout, "	debug off - turns off debugging mode\n");
fprintf(stdout, "	help      - prints this message\n");
fprintf(stdout, "	quit      - exit the cdev utility\n");
fprintf(stdout, "\n");
fprintf(stdout, "CDEV COMMANDS:\n");
fprintf(stdout, "	cdev Commands have the following syntax...\n");
fprintf(stdout, "\n");
fprintf(stdout, "		device verb attr tag1=value1 tag2=value2\n");
fprintf(stdout, "\n");	
fprintf(stdout, "	where	device = the name of the cdev device\n");
fprintf(stdout, "		verb   = the operation to perform\n");
fprintf(stdout, "		attr   = the attribute of the cdev device to operate on\n");
fprintf(stdout, "		tags   = tags may be either general cdev tags or tags that\n");
fprintf(stdout, "		         are specified by the service.\n");
fprintf(stdout, "\n");
fprintf(stdout, "	The following command sets the VAL attribute of device TEST1 to 100.\n");
fprintf(stdout, "\n");
fprintf(stdout, "		TEST1 set VAL value=100\n");
fprintf(stdout, "------------------------------------------------------------------------------\n");
}


void waitForStdin ( cdevSystem & system )
	{
	int fd[32];
	int numFD = 31, nfds;
	int stdinReady = 0;
	fd_set rfds;	// Ready file descriptors
	fd_set afds;	// Active file descriptors

	do 	{
		// *************************************************************
		// * Copy the file descriptor for stdin to the list of 
		// * descriptors
		// *************************************************************
		fd[0] = fileno(stdin);

		// *************************************************************
		// * Get the file descriptors in use by the cdevSystem object
		// *************************************************************
		system.getFd(&fd[1], numFD);
	
		// *************************************************************
		// * Add in the previously defined file descriptors
		// *************************************************************
		numFD += 1;

		// *************************************************************
		// * Zero the active file descriptors
		// *************************************************************
		FD_ZERO(&afds);
	
		// *************************************************************
		// * Setup the active file descriptors
		// *************************************************************
		for(int i=0; i<numFD; i++) FD_SET(fd[i], &afds);

		// *************************************************************
		// * Get the maximum number of file descriptors
		// *************************************************************
		nfds = FD_SETSIZE;
	
		// *************************************************************
		// * Copy the active descriptors to the ready descriptors
		// *************************************************************
		memcpy((void *)&rfds, (void *)&afds, sizeof(rfds));

		// *************************************************************
		// * Use select to detect activity on the file descriptors
		// *************************************************************
#if !defined(__hpux) || (defined(__hpux) && _OS_MAJOR_>9)
		if(select(nfds, &rfds, 0, 0, 0)<0)
#else
		if(select(nfds, (int *)&rfds, 0, 0, 0)<0)
#endif

			{
			system.reportError(
				CDEV_SEVERITY_WARN, 
				"select", NULL, 
				"select function returned -1"); 
			}
		// *************************************************************
		// * Iterate through the list of file descriptors 
		// *************************************************************
		else 
			{
			// *****************************************************
			// * If the stdin descriptor is ready, set the flag
			// *****************************************************
			if(FD_ISSET(fd[0], &rfds)) stdinReady = 1;
			
			// *****************************************************
			// * Always poll the cdev system object
			// *****************************************************
			system.poll();
			}
		} while(!stdinReady);
	}	


int main(int argc, char ** argv)
{
int done = 0;
char command[2048];

#if (!defined(__VMS) && !defined(_WIN32))
	// Under VMS this signal occurs in situations that are not abnormal,
	// and thus cannot be used as simply as this.
	// See the "DEC C Run Time Library Reference Manual" for details.
	signal(SIGSEGV, SIGSEGV_Handler);
#endif	/* __VMS not defined */

cdevSystem::defaultSystem().setThreshold(CDEV_SEVERITY_ERROR);

if(argc>1)
	{
	int i;
	char * oldCdevPrompt = CDEV_PROMPT;
	CDEV_PROMPT = "\0";
	fprintf(stdout, "Submitting batch cdev commands...\n");
	for(i=1; i<argc && !done; i++)
		{
		if(*argv[i] == 'q' && !strcmp(argv[i], "quit")) done = 1;
		else if(*argv[i]=='d' && !strcmp(argv[i], "debug on")) debugging_mode = 1;
		else if(*argv[i]=='d' && !strcmp(argv[i], "debug off")) debugging_mode = 0;
		else if(*argv[i]=='h' && !strcmp(argv[i], "help")) printHelp();
		else if(strlen(argv[i])>1) submitMessage(argv[i]);	
		cdevSystem::defaultSystem().poll();	
		}
	cdevSystem::defaultSystem().poll();
	CDEV_PROMPT = oldCdevPrompt;
	}

if(!done)
	{
	fprintf(stdout, CDEV_PROMPT);
	fflush (stdout);
	}
	
while( !done )
	{
	waitForStdin(cdevSystem::defaultSystem());

	fgets(command, 2048, stdin);
	if(*command == 'q' && !strcmp(command, "quit\n")) done = 1;
	else if(*command=='d' && !strcmp(command, "debug on\n")) debugging_mode = 1;
	else if(*command=='d' && !strcmp(command, "debug off\n")) debugging_mode = 0;
	else if(*command=='h' && !strcmp(command, "help\n")) printHelp();
	else if(strlen(command)>1) submitMessage(command);	
	if(!done) fprintf(stdout, CDEV_PROMPT);
	fflush (stdout);

	cdevSystem::defaultSystem().poll();
	}
return 0;
}


