/****************************************************************************/
/* Copyright 2004 MBARI.                                                    */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : BRS configuration generator                                   */
/* Author   : Mike Risi                                                     */
/* Created  : 7/15/2004                                                     */
/****************************************************************************/

#include <cfxpico.h>    /* Persistor PicoDOS definitions    */
#include <stdio.h>      /* Standard I/O library             */
#include <stdlib.h>     /* Standard C library               */
#include <string.h>     /* Standard string library          */
#include <ctype.h>      /* Character type library           */
#include <time.h>       /* Standard time library            */
#include <unistd.h>     /* for unlink to delete files       */

/* make sure TRUE and FALSE are defined */
#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

/* config limits */
#define OPTRODE_INT_MIN     10
#define OPTRODE_INT_MAX     3600

#define SYRINGE_INT_MIN     0
#define SYRINGE_INT_MAX     14400

#define STIRRER_INT_MIN     0
#define STIRRER_INT_MAX     600

#define STIRRER_DUR_MIN     1
#define STIRRER_DUR_MAX     600

#define TOTAL_SYRINGES      2

#define MAX_CHARS           128

/* oasis config writeFile settings */
#define OPTRODE_0_SER   1
#define OPTRODE_1_SER   2
#define OPTRODE_2_SER   3

#define OPTRODE_0_PWR   2
#define OPTRODE_1_PWR   4
#define OPTRODE_2_PWR   8

#define STIRRER_WAK     0x40

#define SYRINGE_0_WAK   0x01
#define SYRINGE_1_WAK   0x02
#define SYRINGE_DUR     10

/* driver type defs */
#define UNKNOWN_DRIVER      0
#define OPTRODE_DRIVER      1
#define WAKE_ONOFF_DRIVER   2
#define WAKE_ONESHOT_DRIVER 3

/* file names */
#define DATA_FILE   "DATAFILE.TXT"
#define CONFIG_FILE "OASIS.CFG"

/* global config data */
int optInterval;
int syringeInterval[TOTAL_SYRINGES];
int stirrerInterval;
int stirrerDuration;
int removeDataFile;

/* function prototypes */
int validConfig();
void initConfigVars();
int readConfig();
void promptConfig();
int parseDriverLine();
int writeConfig();

/***************************** stolen from oasis ****************************/
#define deblank(s)	while(isspace(*s)) ((s)++)
#define ERROR   -1
#define OK      0

typedef struct      /****************************************/
{				    /* Structure for match string to value  */
    char *current;  /* Current token ptr                    */
    char *next;     /* Next token ptr                       */
} Tokenizer;        /****************************************/

typedef long int Parm_t;    /* User Parameters are 32 bit signed*/

char cfgBuf[512];

int getOneConfigLine(FILE *fp);
void initTokenizer(Tokenizer *tok, char *startp);
char *getNextToken(Tokenizer *tok);
int delimit(char c);
char* cmp_ulc(const char *s, const char *cs);
int isaTok(char *tok);
char *getDecToken(Tokenizer *tok, long *valp);

int strncasecmp(const char *s1, const char *s2, int len);
int sscanNum(char *s, Parm_t *valp);
/***************************** stolen from oasis ****************************/


int main(int argc, char **argv)
{
    /* suppress compiler warning */
    #pragma unused (argv, argc)
    
    printf("\r\nBRS Configurator built on %s at %s \r\n", __DATE__, __TIME__);
    /* Identify the device and its firmware */
    printf("Persistor CF%d SN:%ld   BIOS:%d.%d   PicoDOS:%d.%d\r\n", CFX,
           BIOSGVT.CFxSerNum, BIOSGVT.BIOSVersion, BIOSGVT.BIOSRelease, 
           BIOSGVT.PICOVersion, BIOSGVT.PICORelease);

    /* initialize the config vars */
    initConfigVars();
    
    /* get the current config */
    if ( !readConfig() )
    {
        printf("Couldn't find %s creating default.\r\n", CONFIG_FILE);
        writeConfig();
    }
    
    /* if the current config is good, keep it */
    if ( validConfig() )
        BIOSResetToPicoDOS();

    /* if it's no good make a new one */
    do
    {
        promptConfig();
    } while ( !validConfig() );

    /* write the new config */
    writeConfig();

    /* delete the data file if the user doesn't want it around*/
    if ( removeDataFile )
        if ( unlink(DATA_FILE) )
           printf("\r\nfailed to delete the file %s\r\n", DATA_FILE);

    /* flush stdout before waiting for input */
    fflush(stdout);
    
    /* reset to Pico DOS */
    BIOSResetToPicoDOS();
    return 0;
}

void initConfigVars()
{
    int i;
    
    /* initialize global config variables */
    /* clear config entries and do it again */
    optInterval = 0;

    for (i = 0; i < TOTAL_SYRINGES; i++)
        syringeInterval[i] = 0;

    stirrerInterval = 0;
    stirrerDuration = 0;

    removeDataFile = FALSE;
}

int readConfig()
{
    FILE* cfg_fp;
    int i = 0;

    /* open the file */
    cfg_fp = fopen(CONFIG_FILE, "r");
    
    if ( cfg_fp == NULL )
        return FALSE;

    /* grab a line at a time and parse it*/
    while ( getOneConfigLine(cfg_fp) == OK )
        parseDriverLine();

    /* close the file */
    fclose(cfg_fp);
    
    return TRUE;
}

void promptConfig()
{
    int valid_config = FALSE;
    int i;
    char choice;
    char buff[MAX_CHARS];
    time_t systime;
    struct tm date;
    
    /* blow out existing vars */
    initConfigVars();

    /* get the sample interval */
    while ( (optInterval < OPTRODE_INT_MIN) || 
            (optInterval > OPTRODE_INT_MAX) )
    {
        printf("\r\nEnter Optrode sample interval in seconds [%d, %d] ? ", 
               OPTRODE_INT_MIN, OPTRODE_INT_MAX);

        /* flush stdout before waiting for input */
        fflush(stdout);

        /* wait for input */
        scanf("%d", &optInterval);
    }

    /* get one shot interval for syringes */
    i = 0;
    while ( i < TOTAL_SYRINGES )
    {
        printf("\r\nEnter Syringe %d sample interval in seconds, " 
               "or 0 to disable [%d, %d] ? ", i, SYRINGE_INT_MIN, 
               SYRINGE_INT_MAX);

        /* flush stdout before waiting for input */
        fflush(stdout);

        /* wait for input */
        scanf("%d", &syringeInterval[i]);

        if ( (syringeInterval[i] >= SYRINGE_INT_MIN) &&
             (syringeInterval[i] <= SYRINGE_INT_MAX) )
        {
            /* you got a keeper, get the next one */
            ++i;
        }
    }


    /* get stirrer motor interval and duration */
    while ( (stirrerInterval <= STIRRER_INT_MIN) || 
            (stirrerInterval > STIRRER_INT_MAX) )
    {
        printf("\r\nEnter Stirrer interval in seconds, or 0 to disable "
               "[%d, %d]? ", STIRRER_INT_MIN, STIRRER_INT_MAX);

        /* flush stdout before waiting for input */
        fflush(stdout);

        /* wait for input */
        scanf("%d", &stirrerInterval);

        /* if it's 0 bail out */
        if ( !stirrerInterval )
            break;

        /* if the stirrer interval is in range get the duration */
        if ( (stirrerInterval >= STIRRER_INT_MIN) &&
             (stirrerInterval <= STIRRER_INT_MAX) )
        {
            while ( (stirrerDuration < STIRRER_DUR_MIN) || 
                    (stirrerDuration >= stirrerInterval) )
            {
                printf("\r\nEnter Stirring duration in seconds [%d, %d] ? ", 
                       STIRRER_DUR_MIN, (stirrerInterval - 1));

                /* flush stdout before waiting for input */
                fflush(stdout);

                /* wait for input */
                scanf("%d", &stirrerDuration);
            }
        }
    }

    /* set date and time */
    i = TRUE;
    while ( i )
    {

        printf("\r\nThe date/time is ");
        systime = time(NULL);
        puts(ctime(&systime));

        printf("Press [(A)ccept, (C)hange] the time ? ");

        /* flush stdout before waiting for input */
        fflush(stdout);

        choice = toupper(getch());

        /* accept the time */
        if ( choice == 'A' )
            i = FALSE;

        /* change the time */
        if ( choice == 'C' )
        {

            /* prompt for date */
            printf("\r\nEnter date [MM:DD:YY] ? ");

            /* flush stdout before waiting for input */
            fflush(stdout);
            i = 0;
            do
            {
                buff[i] = getch();
                putch(buff[i]);
                fflush(stdout);
            }
            while ( (buff[i] != '\r') && (++i < MAX_CHARS) );

            /* get the date */
            sscanf(buff, "%d:%d:%d", &date.tm_mon, &date.tm_mday, 
                   &date.tm_year);

            /* prompt for time */
            printf("\r\nEnter time [HH:MM:SS] ? ");

            /* flush stdout before waiting for input */
            fflush(stdout);

            i = 0;
            do
            {
                buff[i] = getch();
                putch(buff[i]);
                fflush(stdout);
            }
            while ( (buff[i] != '\r') && (++i < MAX_CHARS) );

            /* get the time */
            sscanf(buff, "%d:%d:%d", &date.tm_hour, &date.tm_min, &date.tm_sec);

            date.tm_mon -= 1;
            date.tm_year += 100;
            date.tm_isdst = -1;

            systime = mktime(&date);

            RTCSetTime((ulong)systime, 0);

            i = TRUE;
        }
    }

    /* remove old datafile.txt */
    i = TRUE;
    while ( i )
    {
        printf("\r\nRemove the old data file [(Y)es, (N)o] ? ");

        fflush(stdout);
        choice = getch();

        switch ( toupper(choice) )
        {
            case 'Y':   removeDataFile = TRUE;  i = FALSE;  break;
            case 'N':   removeDataFile = FALSE; i = FALSE;  break;
        }
    }
}

int validConfig()
{
    char choice = 'X';
    int i;
    time_t systime;

    while ( TRUE )
    {
        printf("\r\nOptrode sample interval : %d seconds\r\n", optInterval);
        
        for (i = 0; i < TOTAL_SYRINGES; ++i)
        {
            printf("Syringe %d interval      : ", i);
            if ( syringeInterval[i] )
                printf("%d seconds\r\n", syringeInterval[i]);
            else
                printf("disabled\r\n");
        }
        
        if ( stirrerInterval )
        {
            printf("Stirrer interval        : %d seconds\r\n", stirrerInterval);
            printf("Stirrer duration        : %d seconds\r\n", stirrerDuration);
        }
        else
        {
            printf("Stirrer                 : disabled\r\n");
        }
        
        systime = time(NULL);
        printf("Date/Time               : %s", ctime(&systime));
        printf("Remove old data file    : ");
        if ( removeDataFile )
            printf("YES\r\n");
        else
            printf("NO\r\n");

        printf("\r\nIs the following config OK [(A)ccept, (C)hange, (Q)uit] ? ");

        fflush(stdout);
        choice = getch();

        switch ( toupper(choice) )
        {
            case 'A':   return TRUE;
            case 'C':   return FALSE;
            case 'Q':   printf("\r\nBye Bye"); 
                        fflush(stdout); 
                        BIOSResetToPicoDOS(); 
        }
    }

    /* this should be unreachable */
    return FALSE;
}

int parseDriverLine()
{
    char* tok;
    Tokenizer tokenizer;
    int driver_type;
    int instance;
    long interval;
    long timeout;

    /* First find driver	*/
    initTokenizer(&tokenizer, cfgBuf);

    /* is it a driver line */
    tok = getNextToken(&tokenizer);
    if ( cmp_ulc(tok, "driver") == NULL )
        return FALSE;
    
    /* throw away the '=' char */
    tok = getNextToken(&tokenizer);
    
    /* is it a driver you care about */
    driver_type = UNKNOWN_DRIVER;
    
    tok = getNextToken(&tokenizer);
    if ( cmp_ulc(tok, "Optrode") != NULL )
        driver_type = OPTRODE_DRIVER;

    if ( cmp_ulc(tok, "WakeOnOff") != NULL )
        driver_type = WAKE_ONOFF_DRIVER;
            
    if ( cmp_ulc(tok, "WakeOneShot") != NULL )
        driver_type = WAKE_ONESHOT_DRIVER;

    switch ( driver_type )
    {
        case UNKNOWN_DRIVER:
            return FALSE;
        case OPTRODE_DRIVER:
            /* throw away the instance tok */
            getNextToken(&tokenizer);
            /* grab the interval */
            interval = 0;
            getDecToken(&tokenizer, &interval);
            optInterval = interval;
            break;
        case WAKE_ONOFF_DRIVER:
            /* throw away the instance tok */
            getNextToken(&tokenizer);
            /* grab the interval */
            interval = 0;
            getDecToken(&tokenizer, &interval);
            stirrerInterval = (int)interval;
            /* throw away the next 6 tokens */
            getNextToken(&tokenizer);
            getNextToken(&tokenizer);
            getNextToken(&tokenizer);
            getNextToken(&tokenizer);
            getNextToken(&tokenizer);
            getNextToken(&tokenizer);
            /* grab the timeout */
            timeout = 0;
            getDecToken(&tokenizer, &timeout);
            stirrerDuration = (int)timeout;
            break;
        case WAKE_ONESHOT_DRIVER:
            /* grab the driver instance */
            tok = getNextToken(&tokenizer);
            if ( sscanf(tok, "syringe%d", &instance) != 1)
                return FALSE;
            /* grab the interval */
            if ( (instance >= 0) && (instance < TOTAL_SYRINGES) )
            {
                interval = 0;
                getDecToken(&tokenizer, &interval);
                syringeInterval[instance] = (int)interval;
            }
            else
            {
                return FALSE;
            }
            break;
    }
    
	return TRUE;
}


int writeConfig()
{
    FILE* fd = fopen(CONFIG_FILE, "w");
    
    if ( fd == NULL )
    {
        printf("Can't open %s for writing, bailing out!\r\n", CONFIG_FILE);
        BIOSResetToPicoDOS();
    }
    
    /* write the header */  
    fprintf(fd, "## Configuration File for OASIS3/BRS\r\n"); 
    fprintf(fd, "\r\n"); 
    fprintf(fd, "## This file was machine generated, so don't\r\n"); 
    fprintf(fd, "## edit it unless you're a machine\r\n"); 
    fprintf(fd, "##                      -Thanks, the Machines\r\n"); 
    fprintf(fd, "\r\n"); 
    
    /* write the userif and oasis driver section */
    fprintf(fd, "## User interface and OASIS drivers\r\n"); 
    fprintf(fd, "driver = UserIF\r\n"); 
    fprintf(fd, "driver = OASIS\r\n"); 
    fprintf(fd, "\r\n"); 
        
    /* write the optrode driver section */
    fprintf(fd, "## Optrode driver section\r\n");
    if ( optInterval )
    {
        fprintf(fd, "driver = Optrode, optrode0,%d,%d,,,%d,,,5\r\n", optInterval, 
                OPTRODE_0_SER, OPTRODE_0_PWR); 
        fprintf(fd, "driver = Optrode, optrode1,%d,%d,,,%d,,,5\r\n", optInterval, 
                OPTRODE_1_SER, OPTRODE_1_PWR); 
        fprintf(fd, "driver = Optrode, optrode2,%d,%d,,,%d,,,5\r\n", optInterval, 
                OPTRODE_2_SER, OPTRODE_2_PWR); 
    }
    fprintf(fd, "\r\n"); 
    
    /* write the syringe driver section */
    if ( syringeInterval[0] )
    {
        fprintf(fd, "driver = WakeOneShot, syringe0, %d,,,,,,,%d,%d\r\n", 
                syringeInterval[0], SYRINGE_DUR, SYRINGE_0_WAK); 
    }
    
    if ( syringeInterval[1] )
    {
        fprintf(fd, "driver = WakeOneShot, syringe1, %d,,,,,,,%d,%d\r\n", 
                syringeInterval[1], SYRINGE_DUR, SYRINGE_1_WAK); 
    }
    
    fprintf(fd, "\r\n"); 

    /* write the stirrer driver section */
    if ( stirrerInterval )
    {
        fprintf(fd, "driver = WakeOnOff, stirrer,%d,,,,,,,%d,%d\r\n", 
                stirrerInterval, stirrerDuration, STIRRER_WAK); 
    }
    
    fprintf(fd, "\r\n"); 

    /* close the config file */
    fclose(fd);
    return 0;
}

/************************ stolen from oasis config.c ************************/


/************************************************************************/
/* Function    : getOneConfigLine					*/
/* Purpose     : Get one (logical) line from OASIS.CFG			*/
/* Inputs      : None							*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
int 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    : initTokenizer						*/
/* Purpose     : Initialize the Tokenizer to start at ptr p		*/
/* Inputs      : Tokenizer to initialize, where to initialize it to	*/
/* Outputs     : None							*/
/************************************************************************/
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	*/
/************************************************************************/
char *getNextToken(Tokenizer *tok)
{
  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	*/
/************************************************************************/
int 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				*/
/************************************************************************/
char *getDecToken(Tokenizer *tok, long *valp)
{
  char	*nextTok;

  nextTok = getNextToken(tok);
  if (isaTok(nextTok))
    sscanNum(nextTok, valp);

  return(nextTok);

} /* getDecToken() */



/************************ stolen from oasis config.c ************************/

/************************ stolen from oasis utils.c *************************/

/************************************************************************/
/* Function : delimit                                                   */
/* Purpose  : Determine if character is a delimiter                     */
/* Inputs   : Character                                                 */
/* Outputs  : TRUE if character is ',', NULL, or space                  */
/************************************************************************/
int delimit(char c)
{
  return( isspace(c) || (c == '\0') || (c == ',') );

} /* delimit() */

/************************************************************************/
/* Function : cmp_ulc                                                   */
/* Purpose  : Compare strings, case insensitive                         */
/* Inputs   : String ptr (mixed case, leading blanks), string           */
/*            ptr (mixed case, no blanks)                               */
/* Outputs  : NULL if strings don't match, else ptr to remainder of     */
/*            user input string                                         */
/************************************************************************/
	char *
cmp_ulc( const char *s, const char *cs )
{
    deblank(s);
    while( *cs )
    {
	if ( toupper(*s) != toupper(*cs) )
	    return( NULL );
	s++;
	cs++;
    }

    return((char *)(delimit(*s) ? s : NULL));

} /* cmp_ulc() */


/************************************************************************/
/* Function    : strncasecmp						*/
/* Purpose     : Compare strings, case insensitive			*/
/* Inputs      : 2 string ptrs, length to match				*/
/* Outputs     : An integer less than, equal to, or greater than zero,	*/
/*		 depending on if s1 < s2, s1 == s2, or s1 > s2		*/
/* Comments    : Tried to make this identical to std C strncasecmp(),	*/
/*		 which is missing from the Metrowerks C RTL		*/
/************************************************************************/
int strncasecmp(const char *s1, const char *s2, int len)
{
  const	char	*p1 = s1;
  const	char	*p2 = s2;
  char		c1, c2;
  int		n = len;

  if (len <= 0)
    return(0);

  while ((n-- > 0) && ((c1 = toupper(*p1)) == (c2 = toupper(*p2))))
  {
    if (!c1)
      return(0);
    p1++;
    p2++;
  }

  return((unsigned)c1 - (unsigned)c2);

} /* strncasecmp() */

/************************************************************************/
/* Function    : sscanNum						*/
/* Purpose     : Similar to sscanf(s, "%d"), but understands 0xnnn and nnnH*/
/* Inputs      : String to scan, long int to put it in			*/
/* Outputs     : Number of items found (0 or 1)				*/
/************************************************************************/
int sscanNum(char *s, Parm_t *valp)
{
  if (strncasecmp(s, "0x", 2) == 0)
    return(sscanf(s+2, "%lx", valp));

  if (strpbrk(s, "Hh") != NULL)
    return(sscanf(s, "%lx", valp));

  return(sscanf(s, "%ld", valp));

} /* sscanNum() */



/************************ stolen from oasis utils.c *************************/
                                            
