/************************************************************************/
/* Copyright 2012 MBARI													*/
/************************************************************************/
/* Summary	: Functions to manipulate system variables for BEDS			*/
/* Filename : variable.c												*/
/* Author	: Robert Herlien (rah)										*/
/* Project	: Benthic Event Detection System (BEDS)						*/
/* Revision : 1.0														*/
/* Created	: 09/24/2012												*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/************************************************************************/
/* Modification History:												*/
/* 24sep2012 rah - created from Oasis4 parm.c							*/
/* $Log$
 */
/************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <beds.h>						/* BEDS controller definitions		*/
#include <variable.h>					/* Variable function definitions	*/
#include <utils.h>						/* BEDS Utility routines			*/
//#include <fileUtils.h>					/* BEDS file utilities definitions	*/
#include <getopt.h>
#include <syslog.h>						/* System logger					*/

#include <stdio.h>						/* Standard I/O						*/
#include <stdlib.h>						/* Standard C library, for malloc	*/
#include <string.h>						/* String library functions			*/
#include <ctype.h>						/* Standard ctype.h defs			*/
#include <stdint.h>						/* Standard integer types			*/


/********************************/
/*		Global Data				*/
/********************************/

Global Int32	platformID = 0;			/* Platform ID (0 = UNKNOWN)		*/
Global Int32	deploymentID = 0;		/* Deployment ID (0 = UNKNOWN)		*/
Global Int32	evtFreq = 50;			/* FIFO rate during event			*/
Global Int32	watchFreq = 10;			/* FIFO rate during watch cycle		*/
Global Int32	evtPressureSecs = 1;	/* Period (secs) to get press in event*/
Global Int32	sysRecSecs = 600;		/* Period (secs) to get sys parms in watch*/

Global Int32	accelRange = 16;		/* Accelerometer range (g)			*/
Global Int32	gyroRange = 2000;		/* Gyro range (deg/sec)				*/
Global Int32	filterBW = 42;			/* Digital filter bandwidth (Hz)	*/
Global Int32	debugBaud = 9600;		/* Baud rate on debug port			*/
Global Int32	modemBaud = 9600;		/* Baud rate to acoustic modem		*/
Global Int32	usrTmout = 120;			/* Timeout on user I/F (secs)		*/
Global Int32	modemPort = 1;			/* Serial port for  acoustic modem	*/
Global Int32	modemAddr = 1;			/* Address of acoustic modem		*/
Global Int32	modemPeriod = 3600;		/* Period (secs) between modem-on time*/
Global Int32	modemOffset = 0;		/* Offset from top of modemPeriod	*/
Global Int32	modemOnTime = 300;		/* "On" time for modem				*/
Global Int32	modemWarmup = 15;		/* Warmup time for turning on ac. modem*/
Global Int32	modemWait = 5;			/* Wait time for turning off ac. modem*/
Global Int32	modemTxPwr = 6;			/* S6 setting for modem (TxPower)	*/
Global Int32	deploymentModemPeriod = 600; /* Modem Period during deployment mode*/
Global Int32	deploymentModeSecs = 86400L; /* Time to stay in deployment mode*/
Global Int32	sensorWarmup = 2;		/* Warmup time for turning on system sensors*/
Global Int32	sensorMinSecs = 5;		/* If sysRecSecs < this, leave sensors on*/
Global Int32	maxEvent = 7200;		/* Maximum time for event (secs)	*/
Global Flt32	noMotionThresh = 2.0f;	/* MPU no motion threshold			*/
Global Flt32	noMotionTime = 2.0f;	/* MPU no motion time				*/
Global Flt32	pressFullScale = 100.0f; /* Full scale for external pressure sensor*/
Global Int32	syslogToConsole = 0;	/* Echo syslog to console			*/
Global Int32	xoff_tmout = 120;		/* XOFF timeout in seconds			*/
Global Int32	kermitTmout = 30;		/* Timeout (seconds) for kermit file xfer*/
Global MBool	wdtOn = TRUE;			/* Use Watchdog timer				*/
Global Int32	wdtSecs = 30;			/* Seconds between pings			*/
Global MBool	modemDebug = FALSE;		/* Look for chars even when modem off*/
Global MBool	initModem = FALSE;		/* Send init strings to modem at startup*/
Global MBool	localEcho = TRUE;		/* Echo command line				 */
Global MBool	zmSetRemote = TRUE;		/* Use remote cmds to set SRegs for ZModem*/

/* Following are not available via user interface		*/
Global char		*start = "";			/* Start string						*/
Global char		*BEDSLogLevel = "ERROR"; /* Log level for BEDS logger		*/


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal Var vartbl[] =
  { {"PlatformID", &platformID, VAR_LONG, NULL, 1, 35, "Platform ID"},
	{"DeploymentID", &deploymentID, VAR_LONG, NULL, 1, 99, "Deployment ID"},
	{"EvtFreq", &evtFreq, VAR_LONG, NULL, 10, 1000, "Sample rate during event (Hz)"},
	{"WatchFreq", &watchFreq, VAR_LONG, NULL, 1, 100, "Sample rate in watch cycle (Hz)"},
	{"EvtPressureSecs", &evtPressureSecs, VAR_LONG, NULL, 1, 600,
		"Sample rate (sec) for pressure sensor during event"},
	{"SysRecSecs", &sysRecSecs, VAR_LONG, NULL, 1, 3600,
	 "Sample rate (secs) for getting system parameters during watch cycle"},

#if 0 /* This stuff will probably change with new IMU */
	{"AccelRange", &accelRange, VAR_LONG, checkAccelRange, 2, 16,
	 "Accelerometer range in g's (2, 4, 8, or 16)"},
	{"GyroRange", &gyroRange, VAR_LONG, checkGyroRange, 250, 2000,
	 "Gyro range in degrees/sec (250, 500, 1000, or 2000)"},
	{"DigitalFilterBW", &filterBW, VAR_LONG, checkBW, 5, 256,
	 "Bandwidth of MPU digital filter, Hz (5, 10, 20, 42, 98, 188, or 256)"},
#endif
	{"debugBaud", &debugBaud, VAR_LONG, NULL, 300, 115200, "Baud rate to use on DEBUG port"},
	{"modemBaud", &modemBaud, VAR_LONG, NULL, 300, 115200, "Baud rate to acoustic modem"},
	{"Timeout", &usrTmout, VAR_LONG, NULL, 60, 3600, "Timeout on user I/F (secs)"},
	{"ModemPort", &modemPort, VAR_LONG, NULL, 1, 2,
	 "Serial Port for Acoustic Modem"},
	{"ModemPeriod", &modemPeriod, VAR_LONG, NULL, 300, 7200,
	 "Period (secs) for checking acoustic Modem"},
	{"DeployModemPeriod", &deploymentModemPeriod, VAR_LONG, NULL, 120, 1800,
	 "Modem Period during deployment mode"},
	{"DeployModeSecs", &deploymentModeSecs, VAR_LONG, NULL, 3600, 604800,
	 "How long (seconds) to stay in deployment mode"},
	{"ModemOffset", &modemOffset, VAR_LONG, NULL, 0, 3600,
	 "Offset (secs) from top of ModemPeriod"},
	{"ModemOnTime", &modemOnTime, VAR_LONG, NULL, 60, 3600,
	 "Period we leave modem on (secs)"},
	{"ModemWarmup", &modemWarmup, VAR_LONG, NULL, 5, 600,
	 "Warmup time for turning on acoustic modem, secs"},
	{"ModemWait", &modemWait, VAR_LONG, NULL, 1, 120,
	 "Time before turning off acoustic modem, secs"},
	{"ModemAddr", &modemAddr, VAR_LONG, NULL, 1, 99,
	 "Acoustic Modem address"},
	{"ModemPower", &modemTxPwr, VAR_LONG, NULL, 2, 8,
	 "Acoustic Modem Transmit power (S6 setting)"},
	{"InitModem", &initModem, VAR_BOOL, NULL, 0, 0xffff,
	 "Send init strings to acoustic modem at startup"},
	{"modemDebug", &modemDebug, VAR_BOOL, NULL, 0, 0xffff,
	 "Look for chars on modem port even when off"},
	{"SensorWarmup", &sensorWarmup, VAR_LONG, NULL, 2, 60,
	 "Warmup time for turning on system sensors, secs"},
	{"SensorMinSecs", &sensorMinSecs, VAR_LONG, NULL, 2, 60,
		"If SysRecSys < this, leave sensors on"},
	{"MaxEvent", &maxEvent, VAR_LONG, NULL, 1800, 43200,  "Maximum time for an event (secs)"},
	{"XoffTmout", &xoff_tmout, VAR_LONG, NULL, 2, 3600,
	 "Timeout for XOFF, secs"},
	{"NoMotionThresh", &noMotionThresh, VAR_FLOAT, NULL, 1, 360, "No motion threshold, deg/sec"},
	{"NoMotionTime", &noMotionTime, VAR_FLOAT, NULL, 1, 60, "No motion time (secs)"},
	{"PressFullScale", &pressFullScale, VAR_FLOAT, NULL, 10., 1000.,
	 "Full scale for pressure sensor (bar)"},
	{"Echo", &localEcho, VAR_BOOL, NULL, 0, 0xffff, "Echo user commands"},
	{"zmSetRemote", &zmSetRemote, VAR_BOOL, NULL, 0, 0xffff,
	 "Use remote cmds to set SRegs for ZModem"},
	{"kermitTimeout", &kermitTmout, VAR_LONG, NULL, 10, 300, "Timeout (secs) for kermit protocol"},
	{"logToConsole", &syslogToConsole, VAR_LONG, NULL, 0, 3,
	 "0=no echo, 1 or 2 = echo to that serial port"}
};


Global Nat32 numVarTbl = NumberOf(vartbl);


/************************************************************************/
/* Function	   : findVar												*/
/* Purpose	   : Find system variable									*/
/* Inputs	   : Variable name											*/
/* Outputs	   : Var ptr, or NULL if not found							*/
/************************************************************************/
Var *findVar(char *name)
{
	Nat16		i;
	Var			*varp;

	for (i = NumberOf(vartbl), varp = vartbl; i--; varp++)
		if (strcasecmp(name, varp->vname) == 0)
			return(varp);

	return(NULL);

} /* findVar() */


/************************************************************************/
/* Function	   : checkVar												*/
/* Purpose	   : Check one variable within limits						*/
/* Inputs	   : Var ptr												*/
/* Outputs	   : OK, or ERROR if out of range and adjusted				*/
/************************************************************************/
MLocal Errno	checkVar(Var *varp)
{
	Int32		oldval;

	if (varp->vtype == VAR_LONG)					/* Check only longs*/
	{
		oldval = *(Int32 *)(varp->vval);

		if (oldval < varp->valMin)
			*(Int32 *)(varp->vval) = varp->valMin;
		else if (oldval > varp->valMax)
			*(Int32 *)(varp->vval) = varp->valMax;

		if ((oldval < varp->valMin) || (oldval > varp->valMax))
		{
			sysLogPrintf("Variable %s changed from %d to %d",
						 varp->vname, oldval, *(Int32 *)(varp->vval));
			return(ERROR);
		}
	}

	return(OK);
}


/************************************************************************/
/* Function	   : checkVars												*/
/* Purpose	   : Check variables within limits							*/
/* Inputs	   : None													*/
/* Outputs	   : TRUE if OK, FALSE if out of range and adjusted			*/
/************************************************************************/
void	checkVars(Void)
{
	Nat16		i;
	Var			*varp;

	for (i = NumberOf(vartbl), varp = vartbl; i--; varp++)\
		checkVar(varp);
}


/************************************************************************/
/* Function	   : showVar												*/
/* Purpose	   : Show one system variable								*/
/* Inputs	   : Var ptr												*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void showVar(const Var *varp, MBool showHelp)
{
	switch(varp->vtype)
	{
	  case VAR_LONG:
		  printf("%-16s = %8d	 ",	 varp->vname, *(Int32 *)(varp->vval));
			break;

	  case VAR_FLOAT:
		  printf("%-16s = %8.2f	  ", varp->vname, *(Flt32 *)(varp->vval));
		  break;

	  case VAR_BOOL:
		  printf("%-16s = %8s	", varp->vname, 
				 *(MBool *)(varp->vval) ? "TRUE" : "FALSE");
		  break;

	  case VAR_STRING:
	  case VAR_STRINGA:
		  printf("%-16s = %-10s ", varp->vname, *(char **)(varp->vval));
		  break;
	}

	if (showHelp && varp->vhelp)
		printf("%s\r\n", varp->vhelp);
	else
		newline();
	
} /* showVar() */


/************************************************************************/
/* Function	   : showVarMoref											*/
/* Purpose	   : Function to pass to 'more' for showing variables		*/
/* Inputs	   : Variable number										*/
/* Outputs	   : TRUE if found, FALSE if not							*/
/************************************************************************/
MLocal MBool showVarMoref(int varNo)
{
	if (varNo < NumberOf(vartbl))
	{
		showVar(&vartbl[varNo], TRUE);
		return(TRUE);
	}

	return(FALSE);
}


/************************************************************************/
/* Function	   : setVar													*/
/* Purpose	   : Set system variable									*/
/* Inputs	   : Variable name, Variable value in ASCII, TRUE to display*/
/* Outputs	   : NULL if OK, else error string							*/
/************************************************************************/
char *setVar(char *name, char *value, MBool show)
{
	Var			*varp;
	char		*p, *rtn;
	Int32		vsave, lval;
	Nat16		len;
	MBool		ishex = FALSE;

	if ((varp = findVar(name)) == NULL)
		return("Variable not found");
	
	vsave = *(Int32 *)varp->vval;
	rtn = NULL;

	switch(varp->vtype)
	{
	  case VAR_LONG:
		  p = value + strlen(value) - 1;

		  if ((strncmp(value, "0x", 2) == 0) || (strncmp(value, "0X", 2) == 0))
			  ishex = TRUE;
		  else if ((*p == 'h') || (*p == 'H'))
		  {
			  ishex = TRUE;
			  *p = '\0';
		  }

		  if (ishex)
			  sscanf(value, " %lx", &lval);
		  else
			  sscanf(value, " %ld", &lval);
		  
		  *(Int32 *)varp->vval = lval;

		  if ((checkVar(varp) != OK) && show)
			  rtn = "Variable out of range; adjusted";

		  break;

	  case VAR_FLOAT:
		  sscanf(value, " %f", (Flt32 *)varp->vval);
		  break;

	  case VAR_BOOL:
		  *(MBool *)(varp->vval) = (strncasecmp(value, "TRUE", 4) == 0);
		  break;

	  case VAR_STRINGA:
		  /* String has been malloc'd.	Free it */
		  free(*(char **)(varp->vval));
		  /**** NOTE FALL_THROUGH!!!   *****/

	  case VAR_STRING:
		  len = strlen(value) + 1;
		  if ((p = malloc(len)) == NULL)
			  return("Out of Memory");
		  
		  memcpy(p, value, len);				/* Copy string			*/
		  *(char **)(varp->vval) = p;			/* Save str ptr			*/
		  varp->vtype = VAR_STRINGA;			/* Mark as malloc'd		*/
		  break;

	  default:
		  return("Bad variable name");
	}

	if (varp->vfunc != NULL)
		if ((rtn = (*varp->vfunc)(varp)) != NULL)
		{
			*(Int32 *)varp->vval = vsave;
			return(rtn);
		}

	if (show)
		showVar(varp, FALSE);

//	  printf("setVar() returns %s\r\n", rtn ? rtn : "NULL");
	return(rtn);

} /* setVar() */


/************************************************************************/
/* Function	   : varCmd													*/
/* Purpose	   : User interface command for "VAR"						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn	varCmd(int argc, char **argv)
{
	Var			*varp;
	char		*p, *rtn = NULL;
	int			opt, help=0, moreN=10;

    while ((opt = getopt(argc, argv, "n:h")) != -1)
	{
        switch (opt)
		{
		  case 'h': help=1; break;
		  case 'n': moreN = atoi(optarg); break;
		}
	}

	if (help)
	{
		printf("\r\nUsage:\r\n\r\n"
			   "%s <name> = <value>\r\n"
			   "%s <name> <value>\r\n"
			   "%s <name>\r\n"
			   "%s [/n <n>]\r\n\n"
			   "First two forms assign <value> to variable named <name>\r\n\n"
			   "Next form displays value of <name> variable\r\n\n"
			   "Last form displays all variables with their values\r\n"
			   "\t[/n <n>] n lines at a time in 'more' format\r\n\n",
			   argv[0], argv[0], argv[0], argv[0]);

		return(OK);
	}

	newline();

	if (argc < optind + 1)
	{
		more(moreN, showVarMoref, NULL);
		return(OK);
	}

	if (argc == optind + 1)
	{
		if ((p = strchr(argv[optind], '=')) != NULL)
		{
			*p++ = '\0';
			if ((rtn = setVar(argv[optind], p, TRUE)) != NULL)
				printf("%s\r\n", rtn);
			return(OK);
		}

		if ((varp = findVar(argv[optind])) != NULL)
		{
			showVar(varp, TRUE);
			return(OK);
		}
	}
	else if ((argc >= optind + 3) && (strcmp(argv[optind+1], "=") == 0))
		rtn = setVar(argv[optind], argv[optind+2], TRUE);
	else /* Exactly 2 args	*/
		rtn = setVar(argv[optind], argv[optind+1], TRUE);

	if (rtn)
		printf("%s\r\n", rtn);

	return(OK);

} /* VarCmd() */
