/****************************************************************************/
/* Copyright 2003 MBARI							    */
/****************************************************************************/
/* Summary  : Routines to parse config file for OASIS3			    */
/* Filename : config.c							    */
/* Author   : Robert Herlien (rah)					    */
/* Project  : OASIS Mooring Replacement (OASIS3)			    */
/* Revision : 1.0							    */
/* Created  : 08/14/2003						    */
/*									    */
/* 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						    */
/****************************************************************************/

#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 <config.h>		/* OASIS Config file parser definitions	*/
#include <parm.h>		/* Parameter table definitions	        */
#include <task.h>		/* OASIS task dispatcher		*/
#include <utils.h>		/* OASIS utility functions		*/
#include <userif.h>		/* OASIS User Interface functions	*/
#include <drvTbl.h>		/* Default driver headers		*/
#include <debug.h>

#include <stdio.h>		/* Standard I/O library			*/
#include <stdlib.h>		/* Standard C library			*/
#include <string.h>		/* Standard string library		*/
#include <ctype.h>		/* Standard ctypes			*/
#include <time.h>

//#define DEBUG_CONFIG


/********************************/
/*	Module Local Data	*/
/********************************/

MLocal char	  cfgBuf[512];
MLocal StrValPair serSetup[] =
{ {"XON", FLOW}, {"ECHO", ECHO}, {"BIGBUF", BIG_TX_BUF}, {"AUTOCR", AUTOCR},
  {"NEWCR", NEWCR}, {"NOLF", NOLF} };

MLocal char	  *logTypes[] = 
  {"LOG_EMPTY", "OASIS_STAT", "ATLAS", "OASIS_CAN", "PAR", "CTD", 
   "SPECTRO", "ADCP", "GPS", "MODEM", "PCO2", "CTD2", "CTD3",
   "SPECTRO2", "SPECTRO3", "NO3", "NO32", "SPEC_PRR", "SATLANTIC",
   "GPS_TYPE2", "NRL", "O2", "FLUOR", "TRANSMISS", "Unused", "Unused",
   "AC9", "CO2_PUMP", "H2O_PUMP", "SHUTTER0", "SHUTTER1", "SPEC_PRR_VOLTS",
   "METSYS", "TSTRING", "GNDFAULT", "MICROCAT", "GPS_TYPE3", "HOBI_AB",
   "HOBI_CB", "HOBI_HS2", "HOBI_HR1", "HOBI_HR2", "HOBI_HR3", "HOBI_HR4",
   "HOBI_DAS", "GASHOUND", "ISUS", "EMETER", "SBE47_CT", "SBE43_O2",
   "HOBI_HR11", "HOBI_HR21", "HOBI_HR22", "HOBI_HR31", "HOBI_HR32",
   "HOBI_HR33", "HOBI_HR41", "HOBI_HR42", "HOBI_HR43", "HOBI_HR44",
   "SERIAL_SHUTTER", "SPECIAL1", "SPECIAL2"
  };


/************************************************************************/
/* Function    : getOneConfigLine					*/
/* Purpose     : Get one (logical) line from OASIS.CFG			*/
/* Inputs      : None							*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
MLocal Errno getOneConfigLine(FILE *fp)
{
  char	*p = cfgBuf, *q;
  int	size = sizeof(cfgBuf);

  memset(cfgBuf, 0, sizeof(cfgBuf));

  while (fgets(p, sizeof(cfgBuf) - (p - cfgBuf), fp) != NULL)
  {
    if ((q = strpbrk(p, "#\n\r")) != NULL)
      *q = '\0';		/* Delete trailing CR, LF, or comment	*/

    deblank(p);
    if (*p == '\0')
    {				/* If blank or comment-only line, ignore*/
      p = cfgBuf;
      *p = '\0';
      continue;
    }

    if ((p = strchr(p, '\\')) == NULL)
      return(OK);		/* '\' is the continuation character	*/
    
    *p++ = ' ';			/* Replace '\' with delimiter and keep going*/
  }

  return((strlen(cfgBuf) > 0) ? OK : ERROR);

} /* getOneConfigLine() */


/************************************************************************/
/* Function    : parseConfigLine					*/
/* Purpose     : Parse one line of OASIS.CFG				*/
/* Inputs      : None							*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
MLocal Errno parseConfigLine(Void)
{
  char		*keyword, *cmdtail;

  if ((cmdtail = strchr(cfgBuf, '=')) != NULL)
  {
    keyword = strtok(cfgBuf, " 	=\n");
    cmdtail++;
    deblank(cmdtail);

    if (cmp_ulc(keyword, "driver") != NULL)
      return(parseDriver(cmdtail));

    if (setParm(keyword, cmdtail) == OK)
      return(OK);
  }

  return(executeCmdLine(cfgBuf));

} /* parseConfigLine() */


/************************************************************************/
/* Function    : parseConfigFile					*/
/* Purpose     : Parse the OASIS.CFG file				*/
/* Inputs      : None							*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
Errno parseConfigFile(Void)
{
  FILE		*cfgFp;
  char		*name = CFG_FILE;

  if ((cfgFp = fopen(name, "r")) == NULL)
  {
    printf("Can't open %s\n", name);
    return(ERROR);
  }

  while (getOneConfigLine(cfgFp) == OK)
    parseConfigLine();

  fclose(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;

  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		*/
/************************************************************************/
MLocal const DrvDesc *findDriver(char *drvName)
{
  Reg const DrvDesc	*ddp;
  Reg Nat16		i;

  for (i = 0, ddp = &drvDescs[0]; i < NumberOf(drvDescs); i++, ddp++)
    if (cmp_ulc(drvName, ddp->dd_name) != NULL)
      return(ddp);

  return(NULL);

} /* findDriver() */


/************************************************************************/
/* Function    : initTokenizer						*/
/* Purpose     : Initialize the Tokenizer to start at ptr p		*/
/* Inputs      : Tokenizer to initialize, where to initialize it to	*/
/* Outputs     : None							*/
/************************************************************************/
MLocal Void initTokenizer(Tokenizer *tok, char *startp)
{
  tok->current = tok->next = startp;

} /* initToken() */


/************************************************************************/
/* Function    : getNextToken						*/
/* Purpose     : Get next token, return NULL if end of line		*/
/* Inputs      : Ptr to Tokenizer					*/
/* Outputs     : Ptr to next token (NULL terminated), or NULL if none	*/
/************************************************************************/
MLocal char *getNextToken(Tokenizer *tok)
{
  Reg char	*nextTok, *endOfTok;

  if ((nextTok = tok->next) == NULL)
    return(NULL);

  deblank(nextTok);
  tok->current = nextTok;

  if ((endOfTok = strpbrk(nextTok, " 	,\n")) != NULL)
  {
    nextTok = endOfTok;
    deblank(nextTok);
    if (*nextTok == ',')
      nextTok++;
    *endOfTok = '\0';
  }
  else
    nextTok = NULL;

  tok->next = nextTok;
#ifdef DEBUG_CONFIG
  printf("getNextToken: current = \"%s\"  next = \"%s\"\n",
	 tok->current, tok->next);
#endif
  return(tok->current);

} /* getNextToken() */
  

/************************************************************************/
/* Function    : isaTok							*/
/* Purpose     : Determine whether ptr points to valid token		*/
/* Inputs      : Token Ptr						*/
/* Outputs     : FALSE if NULL string, or comma (','); else TRUE	*/
/************************************************************************/
MLocal MBool isaTok(char *tok)
{
  return(tok && *tok && (*tok != ','));

} /* isaTok() */


/************************************************************************/
/* Function    : getDecToken						*/
/* Purpose     : Get a decimal value from next token			*/
/* Inputs      : Ptr to tokenizer, ptr to decimal value to fill in	*/
/* Outputs     : Ptr to token that was parsed				*/
/************************************************************************/
MLocal char *getDecToken(Tokenizer *tok, Nat32 *valp)
{
  Reg char	*nextTok;

  nextTok = getNextToken(tok);
  if (isaTok(nextTok))
    sscanNum(nextTok, valp);

  return(nextTok);

} /* getDecToken() */


/************************************************************************/
/* Function    : strToLower						*/
/* Purpose     : Change a string to lower case				*/
/* Inputs      : Ptr to string						*/
/* Outputs     : None							*/
/************************************************************************/
MLocal void strToLower(char *s)
{
  Reg char	*p;

  for (p = s; *p; p++)
    if (isalpha(*p))
      *p = tolower(*p);

} /* strToLower() */


/************************************************************************/
/* Function    : matchSerString						*/
/* Purpose     : Match one of the serial mode strings			*/
/* Inputs      : Ptr to token to match, ptr to value to return		*/
/* Outputs     : Ptr to string following matched token			*/
/************************************************************************/
MLocal char *matchSerString(char *s, Nat32 *valp)
{
  Reg Nat16	i, len;
  Reg StrValPair *ssp;

  for (i = 0, ssp = serSetup; i < NumberOf(serSetup); i++, ssp++)
  {
    len = strlen(ssp->sv_string);
    if (strncasecmp(s, ssp->sv_string, len) == 0)
    {
      *valp |= ssp->sv_val;
      return(s + len);
    }
  }
  return(s);

} /* matchSerString() */


/************************************************************************/
/* 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);
  }

  if (cmp_ulc(tok, "LOG_ERROR") != NULL)
    return(LOG_ERROR);

  if (cmp_ulc(tok, "LOG_COMMENT") != NULL)
    return(LOG_COMMENT);
  
  return(-1);
}


/************************************************************************/
/* Function    : parseDriver						*/
/* Purpose     : Parse a "drvr = " 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;
  char		c;
  DrvDesc	newDriver;

  /* First find driver	*/
  initTokenizer(&tokenizer, cmdtail);
  tok = getNextToken(&tokenizer);
#ifdef DEBUG_CONFIG
  printf("parseDriver: tok = \"%s\"\n", tok);
#endif
  if ((ddp = findDriver(tok)) == NULL)
  {
    printf("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] = -1;

  val = MAX_BAUD+1;
  getDecToken(&tokenizer, &val);
  if (val <= MAX_BAUD)
    newDriver.dd_parms[SER_BAUD] = val;

  tok = getNextToken(&tokenizer);
  if (isaTok(tok))
  {
    if (isdigit(*tok))
      sscanNum(tok, &val);
    else
    {
      val = 0;
      c = tolower(*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 = matchSerString(tok, &val);
    }
    newDriver.dd_parms[SER_SETUP] = val;
  }

  tok = getNextToken(&tokenizer);
  if (isaTok(tok))
    if (sscanf(tok, "%lx", &val) > 0)
      newDriver.dd_parms[PWR_CTRL] = val;

  tok = getNextToken(&tokenizer);
  if (isaTok(tok))
  {
    if (isdigit(*tok))
      sscanNum(tok, &val);
    else
      val = getLogType(tok);
    if ((val >= 0) && (val < 256))
      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]);
  drvr_create(&newDriver);

  return(OK);

} /* parseDriver() */
