/****************************************************************************/
/* Copyright 2010-2016 MBARI												*/
/****************************************************************************/
/* Summary	: Routines to parse config file for OASIS3						*/
/* Filename : config.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS5 Mooring Replacement									*/
/* Revision : 1.0															*/
/* Created	: 07/19/2016													*/
/*																			*/
/* 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:													*/
/* 14aug2003 rah - created OASIS3 version									*/
/* 19jul2016 rah - OASIS5 version created from OASIS3 config.c				*/
/* $Log$
*/
/****************************************************************************/

#include <mbariTypes.h>			/* MBARI type definitions				*/
#include <mbariConst.h>			/* MBARI constants						*/
#include <oasis.h>				/* OASIS controller definitions			*/
#include <drvr.h>				/* OASIS driver functions				*/
#include <log.h>				/* Log record definitions				*/
#include <serial.h>				/* OASIS serial I/O definitions			*/
#include <parse.h>				/* Generic parser declarations			*/
#include <config.h>				/* OASIS Config file parser definitions */
#include <variable.h>			/* Variable definitions					*/
#include <parm.h>				/* Parameter table definitions			*/
#include <syslog.h>				/* Oasis System logger					*/
#include <script.h>				/* Scripting Language definitions		*/
#include <otask.h>				/* OASIS task dispatcher				*/
#include <utils.h>				/* OASIS utility functions				*/
#include <drvTbl.h>				/* Default driver headers				*/
#include <remote.h>				/* Remote I/O library					*/
#include <modbus.h>				/* Modbus protocol library				*/
#include <debug.h>
#include <fatFs/ff.h>			/* FatFs file system defns				*/
#include <file.h>				/* Oasis5 file I/O						*/

#include <istdio.h>				/* Standard I/O library					*/
#include <string.h>				/* Standard string library				*/
#include <ctype.h>				/* Standard ctypes						*/
#include <time.h>


/********************************/
/*		Global Data				*/
/********************************/

Global char		  *logTypes[] = 
  {"LOG_ASCII", "LOG_BIN" };

Global int numLogTypes = NumberOf(logTypes);
Global int numDrvDescs = NumberOf(drvDescs);


/********************************/
/*		Module Local Data		*/
/********************************/

/* Note that these buffers can be static ONLY because these functions	*/
/* are ONLY called from a single task, the OASIS scheduler task, only	*/
/* at startup, before any driver threads are created					*/

MLocal char		cfgBuf[512];
MLocal char		cfgCopy[256];
MLocal FIL		cfgFp;

MLocal StrValPair serSetup[] =
  { {"N81", (NO_PTY | BIT8 | STOP1)}, {"XON", FLOW}, {"ECHO", ECHO},
	{"AUTOCR", AUTOCR}, {"NEWCR", NEWCR}, {"NOLF", NOLF}
  };

MLocal StrValPair flagSetup[] =
	{ {"PWRPERM", PWRPERM}, {"CONSOLE", CONSOLE_MODE}
	};

MLocal int numConfigFileErrors = 0;

MLocal Errno parseVirtualResource(char *cmdtail, 
								  Errno (*assignFunc)(char *,Nat32,ModBusType,Nat16,Nat16,Nat16,Flt32,Nat32));


/************************************************************************/
/* Function	   : parseConfigLine										*/
/* Purpose	   : Parse one line of OASIS.CFG							*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno parseConfigLine(void)
{
	char		*keyword, *cmdtail;

#ifdef DEBUG_CONFIG
	dprintf("parseConfigLine(): \"%s\"\r\n", cfgBuf);
#endif

   if ((cmdtail = strchr(cfgBuf, '=')) != NULL)
   {
		keyword = strtok(cfgBuf, "	=\n");
		cmdtail++;
		deblank(cmdtail);

		if (*keyword == '$')
			return(parseVariable(keyword, cmdtail));

		if (cmp_ulc(keyword, "array") != NULL)
			return(parseArray(cmdtail));
	
		if (cmp_ulc(keyword, "driver") != NULL)
			return(parseDriver(cmdtail));

		if (cmp_ulc(keyword, "modbus") != NULL)
			return(parseModBus(cmdtail));

		if (cmp_ulc(keyword, "serial") != NULL)
			return(parseVirtualResource(cmdtail, assignVirtSerial));

		if (cmp_ulc(keyword, "power") != NULL)
			return(parseVirtualResource(cmdtail, assignVirtPwr));

		if (cmp_ulc(keyword, "output") != NULL)
			return(parseVirtualResource(cmdtail, assignVirtOutput));

		if (cmp_ulc(keyword, "script") != NULL)
			return(parseScript(cmdtail));
	
		if (setParm(keyword, cmdtail) == OK)
			return(OK);
   }

   return(executeCmdLine(cfgBuf));

} /* parseConfigLine() */


/************************************************************************/
/* Function	   : parseConfigFile										*/
/* Purpose	   : Parse the OASIS.CFG file								*/
/* Inputs	   : Name of config file									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno parseConfigFile(char *cfgFileName)
{
	Errno		rtn;
	FRESULT		fres;
	char		*name = CFG_FILE;

	numConfigFileErrors = 0;
	initVars();
	initScript();
	if (cfgFileName != NULL)
		name = cfgFileName;
  
	if ((fres = f_open(&cfgFp, name, FA_READ)) != FR_OK)
	{
		xprintf("Can't open %s  ", name);
		fperr(fres, NULL);
		sysLogPrintf("Failed to open %s", name);
		return(ERROR);
	}

	while (getOneConfigLine(&cfgFp, cfgBuf, sizeof(cfgBuf)) > 0)
	{
		strncpy(cfgCopy, cfgBuf, sizeof(cfgCopy));
		if ((rtn = parseConfigLine()) != OK)
		{
			numConfigFileErrors++;
			sysLogPrintf("Error parsing \"%s\"\n", cfgCopy);
			xprintf("Error parsing \"%s\"\n", cfgCopy);
		}
	}

	if (numConfigFileErrors > 0)		/* If errors in cfg parsing		*/
		remSetError();					/* blink all remote LEDs		*/

	f_close(&cfgFp);
	return(OK);

} /* parseConfigFile() */


/************************************************************************/
/* Function	   : parseAddDefaultDrivers									*/
/* Purpose	   : Add the drivers in drv_default onto the driver list	*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
void parseAddDefaultDrivers(void)
{
	Reg Nat16		i;

	/* See comments under Module Local Data.  Don't need mutex because	*/
	/* these routines are only called by a single thread at init		*/

	for ( i = 0; i < NumberOf(drv_default); i++ )
		drvr_create(drv_default[i]);

} /* parseAddDefaultDrivers() */


/************************************************************************/
/* Function	   : findDriver												*/
/* Purpose	   : Find driver by name									*/
/* Inputs	   : Driver name											*/
/* Outputs	   : Ptr to default DrvDesc, or NULL if not found			*/
/************************************************************************/
const DrvDesc *findDriver(char *drvName)
{
	Reg const DrvDesc		*ddp;
	Reg Nat16				i;

	for (i = 0; i < NumberOf(drvDescs); i++)
	{
		ddp = drvDescs[i];
		if (cmp_ulc(drvName, ddp->dd_name) != NULL)
			return(ddp);
	}
	return(NULL);

} /* findDriver() */


/************************************************************************/
/* Function	   : matchValString											*/
/* Purpose	   : Match a string in a StrValPair							*/
/* Inputs	   : Ptr to token to match, ptr to value to return			*/
/* Outputs	   : Ptr to string following matched token					*/
/************************************************************************/
MLocal char *matchValString(char *s, Nat32 *valp, StrValPair *pair, Nat32 nvals)
{
	Reg Nat16		i, len;
	Reg StrValPair	*ssp;

	for (i = 0, ssp = pair; i <nvals; i++, ssp++)
	{
		len = strlen(ssp->sv_string);
		if (strncasecmp(s, ssp->sv_string, len) == 0)
		{
			*valp |= ssp->sv_val;
			return(s + len);
		}
	}
	return(s);
  
} /* matchValString() */


/************************************************************************/
/* Function	   : getLogType												*/
/* Purpose	   : Get Log (Sample) type									*/
/* Inputs	   : Ptr to token to match									*/
/* Outputs	   : Sample type, or -1 if none found						*/
/************************************************************************/
MLocal Int32 getLogType(char *tok)
{
	Reg Nat16		i;
	Reg char		*p;

	for (i = 0; i < NumberOf(logTypes); i++)
	{
		p = logTypes[i];
		if (cmp_ulc(tok, p) != NULL)
			return(i);
	}

	return(-1);
}


/************************************************************************/
/* Function	   : parseSerMode											*/
/* Purpose	   : Parse the serial mode token							*/
/* Inputs	   : Token													*/
/* Outputs	   : Value for SER_MODE										*/
/************************************************************************/
MLocal Nat32 parseSerMode(char *tok)
{
	Nat32		val = 0;
	char		c;

	if (isdigit((int)*tok & 0xff))
		sscanNum(tok, (Parm_t *)&val);
	else
	{
		c = tolower((int)*tok & 0xff);
		tok++;
		if (c == 'e')
			val |= EVEN_PTY;
		if (c == 'o')
			val |= ODD_PTY;
		if (*tok++ == '7')
			val |= BIT7;
		if (*tok++ == '2')
			val |= STOP2;
		while (*tok)
			if (*tok++ == '_')
				tok = matchValString(tok, &val, serSetup, NumberOf(serSetup));
	}

	return(val);

} /* parseSerMode() */


/************************************************************************/
/* Function	   : parseFlags												*/
/* Purpose	   : Parse the FLAGS token									*/
/* Inputs	   : Token													*/
/* Outputs	   : Value for FLAGS										*/
/************************************************************************/
MLocal Nat32 parseFlags(char *flagStr)
{
	Nat32		val = 0;
	char		*tok, *newTok;

	tok = flagStr;

	if (isdigit((int)*tok & 0xff))
		sscanNum(tok, (Parm_t *)&val);
	else
	{
		while (*tok && ((newTok = matchValString(tok, &val, flagSetup, NumberOf(flagSetup)))
						!= tok))
		{
			tok = newTok;
			if (*tok == '_')
				tok++;
		}
	}

	return(val);

} /* parseFlags() */


/************************************************************************/
/* Function	   : parseArray												*/
/* Purpose	   : Parse a "array = " line in config file					*/
/* Inputs	   : Ptr to remainder of parse line							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno parseArray(char* cmdtail)
{
	Reg char	*tok;
	Reg int		i;
	Reg Nat16	size;
	Tokenizer		tokenizer;
	char			name[32];

	/* get array name */
	initTokenizer(&tokenizer, cmdtail);
	tok = getNextToken(&tokenizer);
	strncpy(name, tok, 32);

	/* lookup up array in parm array table */
	size = getParmArraySize(name);
  
	if ( size == 0 )
	{
		xprintf("Array not found: \"%s\"\n", name);
		sysLogPrintf("Array not found: \"%s\"\n", name);
		return ERROR;
	}

	/* set array elements */
	for (i = 0; i < size; ++i)
	{
		tok = getNextToken(&tokenizer);
	  
		if ( tok == NULL )
			setParmArray(name, "0", i);
		else
			setParmArray(name, tok, i);
	}

	return(OK);

} /* parseArray() */

/************************************************************************/
/* Function	   : parseDriver											*/
/* Purpose	   : Parse a "driver = " line in config file				*/
/* Inputs	   : Ptr to remainder of parse line							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno parseDriver(char *cmdtail)
{
	Reg const DrvDesc *ddp;
	Reg char		*tok;
	Tokenizer		tokenizer;
	Nat32			val;
	DrvDesc			newDriver;

	/* First find driver	*/
	initTokenizer(&tokenizer, cmdtail);
	tok = getNextToken(&tokenizer);
  
#ifdef DEBUG_CONFIG
	dprintf("parseDriver: tok = \"%s\"\r\n", tok);
#endif

	if ((ddp = findDriver(tok)) == NULL)
	{
		xprintf("Driver not found:  \"%s\"\n", tok);
		sysLogPrintf("Driver not found:  \"%s\"\n", tok);
		return(ERROR);
	}

	memcpy(&newDriver, ddp, sizeof(DrvDesc));

	tok = getNextToken(&tokenizer);
	if (isaTok(tok))
		newDriver.dd_name = tok;

	getDecToken(&tokenizer, &newDriver.dd_parms[INTERVAL]);

	getDecToken(&tokenizer, &newDriver.dd_parms[SER_PORT]);
	if (newDriver.dd_parms[SER_PORT] >= VIRT_SER_PORTS)
		newDriver.dd_parms[SER_PORT] = NO_SERIAL;

	val = MAX_BAUD+1;
	getDecToken(&tokenizer, &val);
	if (val <= MAX_BAUD)
		newDriver.dd_parms[SER_BAUD] = val;

	tok = getNextToken(&tokenizer);
	if (isaTok(tok))
		newDriver.dd_parms[SER_SETUP] = parseSerMode(tok);

	tok = getNextToken(&tokenizer);
	if (isaTok(tok))
		newDriver.dd_parms[FLAGS] = parseFlags(tok);
	
	tok = getNextToken(&tokenizer);
	if (isaTok(tok))
	{
		if (isdigit((int)*tok & 0xff))
			sscanNum(tok, (Parm_t *)&val);
		else
			val = getLogType(tok);
		if ((val >= 0) && (val < 65535))
			newDriver.dd_parms[SAMPLE_TYPE] = val;
	}

	getDecToken(&tokenizer, &newDriver.dd_parms[TOD_MASK]);
	getDecToken(&tokenizer, &newDriver.dd_parms[TIMEOUT]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM0]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM1]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM2]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM3]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM4]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM5]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM6]);
	getDecToken(&tokenizer, &newDriver.dd_parms[PARM7]);
	drvr_create(&newDriver);

	return(OK);

} /* parseDriver() */


/************************************************************************/
/* Function	   : parseModBus											*/
/* Purpose	   : Parse a "modbus = " line in config file				*/
/* Inputs	   : Ptr to remainder of parse line							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno parseModBus(char *cmdtail)
{
	Reg char		*tok;
	Tokenizer		tokenizer;
	Nat32			serport=0, baud=DEFAULT_BAUD, sermode=DEFAULT_MODE, flags = 0;
	Flt32			surge = DFLT_SURGE/1000.;

#ifdef DEBUG_CONFIG
	dprintf("parseModBus: \"%s\"\r\n", cmdtail);
#endif

	initTokenizer(&tokenizer, cmdtail);

	getDecToken(&tokenizer, &serport);
	getDecToken(&tokenizer, &baud);

	tok = getNextToken(&tokenizer);
	if (isaTok(tok))
		sermode = parseSerMode(tok);

	getFltToken(&tokenizer, &surge);
  
	getDecToken(&tokenizer, &flags);

	return(modBusConfigureSer((Port_t)serport, baud, sermode, (Nat32)(surge*1000.), flags));

} /* parseModBus() */


/************************************************************************/
/* Function	   : parseVirtualResource									*/
/* Purpose	   : Parse a "serial = " or "power = " line in config file	*/
/* Inputs	   : Ptr to remainder of parse line							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno parseVirtualResource(char *cmdtail, 
								  Errno (*assignFunc)(char *,Nat32,ModBusType,Nat16,Nat16,Nat16,Flt32,Nat32))
{
	Tokenizer	tokenizer;
	char		*name;
	Nat32		virtPort = 0, bus = 0, slave = 0, port = -1;
	Flt32		mult = DFLT_MULT, surge = DFLT_SURGE/1000.;

#ifdef DEBUG_CONFIG
	printf("parseVirtualResource: \"%s\"\n", cmdtail);
#endif

	initTokenizer(&tokenizer, cmdtail);

	name = getNextToken(&tokenizer);
	if (!isaTok(name))
		return(ERROR);

	if (!isdigit((int)*name & 0xff))
		getDecToken(&tokenizer, &virtPort);
	else
	{
		sscanNum(name, (Parm_t *)&virtPort);
		name = NULL;
	}

	getDecToken(&tokenizer, &bus);
	getDecToken(&tokenizer, &slave);
	getDecToken(&tokenizer, &port);
	getFltToken(&tokenizer, &mult);
	getFltToken(&tokenizer, &surge);

	return((*assignFunc)(name, virtPort, MB_SER, bus, 
						 slave, port, mult, (Nat32)(surge*1000.)));

} /* parseVirtualResource() */
