/*
  
================================================================================
 
  Abs:  Helper functions for dynamic_restore.c. Adapted from save_restore.c functions 
        for use on hard and soft IOCs.

 
  Name: restore_utils.c
        read_restore_channel - sets the channel name and value pair given an input string
	restore_str  - restores string.
	restore_line - removes null characters from channel name/value pair line
	save_file_ok - checks validity of given save file
	ChannelType - returns channel type given string input

	NOTE: None of these functions are entry point functions and thus should never be 
              called directly.

  Proto: restore_utils.h

  Auth: 01-Oct-2004, James Silva (jsilva)
  Rev:  04-Nov-2004, James Silva (jsilva)
         Added version argument to save_file_ok.

--------------------------------------------------------------------------------
 
  Mod:  (newest to oldest)  

 
==============================================================================*/


#include	<ctype.h>         /* for isalpha, isdigit, and isspace */
#include	<string.h>        /* for strcat */
#include        <stdio.h>         /* for fgetc */
#include        <stdlib.h>        /* for calloc, atof, atoi */
#include        <time.h>          /* for ctime and time */
#include        <epicsPrint.h>    /* for epicsPrintf */
#include	<cadef.h>         /* for chtype */
#include        <restore_utils.h> /* restore_utils.c function declarations */


#ifdef INCLcadefh
int  read_restore_channel (char *, FILE *, char *, char *, chtype *, unsigned *, char *, void **);
#endif

static int restore_str( FILE *input_fd,int len,char *buffer );

#define max(a, b) (a > b ? a : b)

#define OK     0
#define ERROR -1
#define LF              0xa 


/*
 * Returns channel type given string read from V2.x restore file
 */
static chtype ChannelType (char * type) {
  if (strstr (type, "STRING")) return DBR_STRING;
  if (strstr (type, "INT")) return DBR_INT;
  if (strstr (type, "SHORT")) return DBR_SHORT;
  if (strstr (type, "FLOAT")) return DBR_FLOAT;
  if (strstr (type, "ENUM")) return DBR_ENUM;
  if (strstr (type, "CHAR")) return DBR_CHAR;
  if (strstr (type, "LONG")) return DBR_LONG;
  if (strstr (type, "DOUBLE")) return DBR_DOUBLE;
  return DBF_NO_ACCESS;
  }


static int restore_str( FILE *input_fd,int len,char *buffer )
{ 
   int status = OK;
   int i;
   int tmp;

   for ( i=0; i<len-1; i++ ) {
     tmp = getc(input_fd);
     buffer[i] = (char)tmp;
     if (tmp==EOF) {
       status=EOF;
       break;
       }
     else if ( isspace(buffer[i]) || !buffer[i] )
        break; 
   }/* End for loop */
   buffer[i] = '\0';
   return( status );
}


/*
 * read_restore_channel - one channel from s/r V2.x file format, waveforms will be on multiple lines in the file
 */
int read_restore_channel (char * filename, FILE * fid, char * channel, char * line,            /* inputs */
                          chtype * type, unsigned * count, char * value, void ** valueNative) { /* outputs */

  int status = ECA_NORMAL;
  char token[80];
  int i=0;   /* index through line */
  int j;     /* index through token */
  int wc;    /* waveform value counter */
  int index; /* waveform element from file */
  char wfline[120];

  dbr_enum_t * dbr_enum_p = NULL;
  dbr_string_t * dbr_string_p = NULL;
  dbr_int_t * dbr_int_p = NULL;
  dbr_short_t * dbr_short_p = NULL;
  dbr_float_t * dbr_float_p = NULL;
  dbr_char_t * dbr_char_p = NULL;
  dbr_long_t * dbr_long_p = NULL;
  dbr_double_t * dbr_double_p = NULL;
  void * valueNative_p = NULL;

  *count = 1; /* most will be scalar */
  *type = DBR_STRING; /* will restore ANY type of scalar */
  memset ((void *)value, 0, sizeof (value));

  /* get the type */
  memset ((void *)token, 0, sizeof (token));
  for (; (' ' == line[i]); i++); /* skip blanks */
  for (j=0; (' ' != line[i]); i++, j++) token[j] = line[i];
  *type = ChannelType(token);

  /* get the value */
  for (; (' ' == line[i]); i++); /* skip blanks */
  memset ((void *)token, 0, sizeof (token));
  if ('"' == line[i]) { /* double quoted string.  It's either a scalar STRING or ENUM. */
    i++; /* to skip over the double quote */
    for (j=0; ('"' != line[i]); i++, j++) token[j] = line[i];
    strcpy (value, token); /* copy to real place */
    if (' ' == line[i++]) { /* scalar ENUM, get native ENUM value. */
      for (; (' ' == line[i]); i++); /* skip blanks */
      for (j=0; ((' ' != line[i]) && (0 != line[i])); i++, j++) token[j] = line[i];
      dbr_enum_p = (dbr_enum_t *)calloc(1, sizeof(dbr_enum_t));
      *dbr_enum_p = atoi(token);
      valueNative_p = dbr_enum_p;
      }
    else { /* scalar STRING, set native STRING value. */
      dbr_string_p = (dbr_string_t *)calloc(1, sizeof(dbr_string_t));
      strcpy (*dbr_string_p, value);
      valueNative_p = dbr_string_p;
      };
    }
  else { /* not a STRING or ENUM */
    for (j=0; ((' ' != line[i]) && (0 != line[i])); i++, j++) token[j] = line[i];
    strcpy (value, token); /* copy to real place */
    };

  /* check for waveform record */
  if ((' ' == line[i]) && (strstr(value,"waveform"))) {
    /* Does the word "count" appear next in the line? */
    i++; /* skip blank */
    memset ((void *)token, 0, sizeof (token));
    for (j=0; ((' ' != line[i]) && (0 != line[i])); i++, j++) token[j] = line[i];
    if (strstr (token,"count")) {
      i++; /* skip blank */
      memset ((void *)token, 0, sizeof (token));
      for (j=0; ((' ' != line[i]) && (0 != line[i])); i++, j++) token[j] = line[i];
      *count = atoi(token);
      if (0 == *count) *count = 1; /* atoi failed */
      if (*count > 1) {
        /* allocate enough memory to hold the entire waveform */
        if (DBR_STRING == *type) {
          dbr_string_p = (dbr_string_t *)calloc(*count, sizeof(dbr_string_t));
          valueNative_p = dbr_string_p;
          }
        else if (DBR_INT == *type) {
          dbr_int_p = (dbr_int_t *)calloc(*count, sizeof(dbr_int_t));
          valueNative_p = dbr_int_p;
          }
        else if (DBR_SHORT == *type) {
          dbr_short_p = (dbr_short_t *)calloc(*count, sizeof(dbr_short_t));
          valueNative_p = dbr_short_p;
          }
        else if (DBR_FLOAT == *type) {
          dbr_float_p = (dbr_float_t *)calloc(*count, sizeof(dbr_float_t));
          valueNative_p = dbr_float_p;
          }
        else if (DBR_ENUM == *type) { /* hmmm.... waveform of enums? */
          dbr_enum_p = (dbr_enum_t *)calloc(*count, sizeof(dbr_enum_t));
          valueNative_p = dbr_enum_p;
          }
        else if (DBR_CHAR == *type) {
          dbr_char_p = (dbr_char_t *)calloc(*count, sizeof(dbr_char_t));
          valueNative_p = dbr_char_p;
          }
        else if (DBR_LONG == *type) {
          dbr_long_p = (dbr_long_t *)calloc(*count, sizeof(dbr_long_t));
          valueNative_p = dbr_long_p;
          }
        else if (DBR_DOUBLE == *type) {
          dbr_double_p = (dbr_double_t *)calloc(*count, sizeof(dbr_double_t));
          valueNative_p = dbr_double_p;
          }
        else {
          epicsPrintf ("read_restore_channel: Unknown waveform type %d for channel %s count %d", *type, channel, *count);
          };
        /* read the rest of the values from the file and set valueNative_p */
        for (wc=0; wc<*count; wc++) {
          /* read the next line from the file */
          memset ((void *)wfline, 0, sizeof (wfline));
          restore_line (filename,fid,sizeof(wfline),wfline);
          /* make sure channel name still matches our channel */
          memset ((void *)token, 0, sizeof (token));
          i = 0; /* start of new line */
          for (j=0; ((' ' != wfline[i]) && (0 != wfline[i])); i++, j++) token[j] = wfline[i];
          if (0 == strncmp (channel, token, strlen(token))) { /* specific element may be commented out with text editor */
            i++; /* skip blank */
            /* set the waveform index for this value */
            index = -1;
            if ('[' == wfline[i]) {
              i++; /* skip [ */
              memset ((void *)token, 0, sizeof (token));
              for (j=0; ((' ' != wfline[i]) && (']' != wfline[i]) && (0 != wfline[i])); i++, j++) token[j] = wfline[i];
              index = atoi(token); /* no way to tell whether atoi failed since 0 is a valid index */
              if (']' == wfline[i]) i++; /* skip closing bracket */
              };
            /* get the value as a string */
            memset ((void *)token, 0, sizeof (token));
            for (; (' ' == wfline[i]); i++); /* skip blanks */
            if ('"' == wfline[i]) { /* a double quoted string */
              i++; /* skip opening quote */
              for (j=0; (('"' != wfline[i]) && (0 != wfline[i])); i++, j++) token[j] = wfline[i];
              }
            else {
              for (j=0; ((' ' != wfline[i]) && (0 != wfline[i])); i++, j++) token[j] = wfline[i];
              };
            if (valueNative_p) {
              if (index > -1) {
                if (DBR_STRING == *type) {
                  dbr_string_p = (dbr_string_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_string_p++)
                    if (j == index) {
                      strncpy (*dbr_string_p, token, sizeof(dbr_string_t));
                      break;
                      };
                  }
                else if (DBR_INT == *type) {
                  dbr_int_p = (dbr_int_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_int_p++)
                    if (j == index) {
                      *dbr_int_p = atoi(token);
                      break;
                      };
                  }
                else if (DBR_SHORT == *type) {
                  dbr_short_p = (dbr_short_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_short_p++)
                    if (j == index) {
                      *dbr_short_p = atoi(token);
                      break;
                      };
                  }
                else if (DBR_FLOAT == *type) {
                  dbr_float_p = (dbr_float_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_float_p++)
                    if (j == index) {
                      *dbr_float_p = atof(token);
                      break;
                      };
                  }
                else if (DBR_ENUM == *type) {
                  dbr_enum_p = (dbr_enum_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_enum_p++)
                    if (j == index) {
                      *dbr_enum_p = atoi(token);
                      break;
                      };
                  }
                else if (DBR_CHAR == *type) {
                  dbr_char_p = (dbr_char_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_char_p++)
                    if (j == index) {
                      strncpy ((char *)dbr_char_p, token, 1);
                      break;
                      };
                  }
                else if (DBR_LONG == *type) {
                  dbr_long_p = (dbr_long_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_long_p++)
                    if (j == index) {
                      *dbr_long_p = atol(token);
                      break;
                      };
                  }
                else if (DBR_DOUBLE == *type) {
                  dbr_double_p = (dbr_double_t *) valueNative_p;
                  for (j=0; j < *count; j++, dbr_double_p++)
                    if (j == index) {
                      *dbr_double_p = atof(token);
                      break;
                      };
                  };
                }; /* waveform index OK */
              }; /* channel name still OK */
            }; /* calloc OK */
          }; /* parse one waveform value from wfline */
        }; /* waveform found */
      }; /* "count" found */
    };  /* check for waveform */

  *valueNative = valueNative_p;
  return(status);
  }


/*
 * save_file_ok - check is filename made by the function save_file is ok to restore
 *
 * If the file exists and is less than version V1.6 then it is OK.
 * If the file exists and is version V1.6 or later then it must have file_ok_tag on the last line of the file.
 *
 */
#define version_index 15
int save_file_ok(char *filename, char *version) {
   int i,iss = 0;
   FILE *fid;
   char file_line[120];
   char dummy[120];

   /* open the file for read access */
   fopen_macro ("save_file_ok", fid, filename, "r");

   /* Make sure version and file_line are clear */
   memset ((void *)version, 0, sizeof (version));
   memset ((void *)file_line, 0, sizeof (file_line));

   /* read the first line of the file to get the version number */
   /*   fscanf(fid, "%[^\n]", file_line); */
   iss = restore_line(filename,fid,sizeof(file_line),file_line);
   if ((iss==OK) && (file_line[version_index] == 'V')) {
     /* We probably have the correct type of file */
     /* Get the version number out of the file_line */
     for (i = 0; i < sizeof(version); i++)
       if ((file_line[i+version_index] == ' ') || (file_line[i+version_index] == '\t'))  {
         version[i] = 0;
         break;
         }
       else {
         version[i] = file_line[i+version_index];
         };
     /* Check against save/restore version V1.6 */
     if ((strcmp(version,"V1.6")) < 0) {
       /* File is older than version V1.6.  Assume it's OK to restore */
       epicsPrintf("save_file_ok: %s is older than V1.6\n",filename);
       }
     else { /* File is version V1.6 or newer, check file_ok_tag */
       iss = -1; /* Assume file_ok_tag is not here unless found */
       /*       while (fscanf(fid, "%s", dummy) != EOF) {       */
       while (restore_str(fid,sizeof(dummy),dummy) != EOF) {     
         /* fscanf (fid, "%[^\n]", file_line); */
         iss = restore_line(filename,fid,sizeof(file_line),file_line);
         if (iss==ERROR)
           break;
         if (strstr(file_line,file_ok_tag) != 0) {
           iss = 0; 
           break; 
           }; /* file_ok_tag found */
         }; /* EOF or file_ok_tag found */
       if (iss == -1) 
         epicsPrintf ("save_file_ok: %s is version %s, but doesn't contain %s\n",
                      filename, version, file_ok_tag);
       }; /* File is version V1.6 or newer, check file_ok_tag */
     }
   else {
     /* This file was not generated by save/restore! */
     epicsPrintf ("save_file_ok: %s doesn't seem to have a version number.\n", filename);
     epicsPrintf ("save_file_ok: unable to find version number in `%s'\n", file_line);
     iss = -1;
     };

   /* Close the file */
   fclose_macro ("save_file_ok", filename, fid);

   return (iss);
   } /* save_file_ok */


int restore_line( char *filename, FILE *input_fd,
                   int len,char *buffer )
{
   int status = OK;
   int i;
   int null_cnt=0;
   int tmp;

   for ( i=0; i<len-1; i++ ) {
     tmp = getc(input_fd);
     buffer[i] = (char)tmp;
     if ( !buffer[i] ) {
       i = max(0,i-1);
       null_cnt++;
     }
          else if ((tmp==EOF) || (tmp==LF))
       break;
   }/* End for loop */
   if ( null_cnt ) {
     status = ERROR;
     epicsPrintf("restore_line: have %d NULL's in %s\n",
                  null_cnt,filename);
   }
   buffer[i] = '\0';
   return( status );
}
