/* PCONF.C */

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

#include "pconf.h"
#include "parse.h"
//extern void quit ();
//void pusage ();



#define	TRUE	1
#define	FALSE	0
//#define QUIT(x)		{fprintf x; exit(-1);}

static int SetVal (register Config_t *cp, char *str);
static void SPrintVal ( register Config_t *cp, char *str);

// method: pconf
//
// arguments:
//  int argc: (input) from commandline
//  char *argv[]: (input) from commandline
//  Config_t *config_p: (input\output) configuration for this executable
//  char **display: (output) seems to contain variables with a ':'
//  char **geometry: (output) seems to contain the letter =
//  char *(*GetDefault)(char *,char *): (input) function to exec command line
//                                      parameters, like pre-processing
//
// return: true = bad usage, false = no bad usage
//
// uses no global variables
//
int pconf (int argc, char *argv[], Config_t *config_p, char **display,
	   char **geometry, char *(*GetDefault)(char *,char *)) {
  int i;
  int parsed;
  int bad_usage = FALSE;
  char *str_p;
  Config_t *cp;
  
  // if a function is defined
  //
  if (GetDefault) {
    for (cp = config_p; cp->arg_type != NOTYPE; cp++) {
      if( (str_p = GetDefault (argv[0], cp->LongName)) ) {
	bad_usage |= SetVal (cp, str_p);
      }
    }
  }
  
  for (i = 1; i < argc; i++) {
    for (parsed = FALSE, cp = config_p; cp->arg_type != NOTYPE; cp++) {
      if (strcasecmp (argv[i], cp->swtch) == 0) {
	parsed = TRUE;
	if (++i < argc)
	  bad_usage |= SetVal (cp, argv[i]);
	else
	  bad_usage = TRUE;
      }
    }
    
    if (parsed)
      continue;
    
    // This seems to, if geometry is given in input, set the array
    // equal the '=' sign
    //
    if (geometry && (*argv[i] == '=')) {
      *geometry = argv[i];
      continue;
    }
    // This seems to, if display is present and there is a : in the
    // given input string, set display equal to it
    if (display && (strchr (argv[i], ':') != 0)) {
      *display = argv[i];
      continue;
    }
    // if -? or -help is given, print usage
    if ((strcasecmp ("-?", argv[i]) == 0) ||
	(strcasecmp ("-help", argv[i]) == 0)) {
      pusage (argv[0], config_p);
    }
    
    fprintf (stderr, "ERROR: %s: Unrecognized argument %s\n",
	     argv[0], argv[i]);
    bad_usage = TRUE;
  }
  return (bad_usage);
}

/*
// method: ppconf
//
// arguments:
//  int argc: (input) from commandline
//  char *argv[]: (input) from commandline
//  Config_t *config_p: (input) configuration for this executable, ex dm_conf[]
//  char **display: (output) seems to contain variables with a ':'
//  char **geometry: (output) seems to contain the letter =
//  char *(*GetDefault)(char *,char *): (input) function to exec command line
//                                      parameters, like pre-processing
//  char last (input): if set, this will stop after comparing commandline to
//                     the config_t
//
// return: true = bad usage, false = no bad usage
//
// uses no global variables
//
int ppconf(int argc, char *argv[], Config_t *config_p, char **display,
char **geometry, char *(*GetDefault)(char *, char *),
char last) {
int i, parsed;
int bad_usage = FALSE;
char *str_p;
Config_t *cp;

if (GetDefault) {
for (cp = config_p; cp->arg_type != NOTYPE; cp++) {
if( (str_p = GetDefault (argv[0], cp->LongName)) ) {
bad_usage |= SetVal (cp, str_p);
}
}
}

for (i = 1; i < argc; i++) {
// argument has been processed already
if (argv[i][0] == '\0') continue;

for (parsed = FALSE, cp = config_p; cp->arg_type != NOTYPE; cp++) {
if (strcasecmp (argv[i], cp->swtch) == 0) {
parsed = TRUE;
// remove this switch from consideration 
argv[i][0] = '\0';
if (++i < argc) {
bad_usage |= SetVal (cp, argv[i]);
argv[i][0] = '\0';
}
else bad_usage = TRUE;
}
}

if (parsed || !last) continue;

if (geometry && (*argv[i] == '=')) {
*geometry = argv[i];
continue;
}

if (display && (strchr (argv[i], ':') != 0)) {
*display = argv[i];
continue;
}

if ((strcasecmp ("-?", argv[i]) == 0) ||
(strcasecmp ("-help", argv[i]) == 0))
pusage (argv[0], config_p);
fprintf(stderr, "ERROR: %s: Unrecognized argument, %s\n",
argv[0], argv[i]);
bad_usage = TRUE;
}
return (bad_usage);
}
*/

// method: pusage
//
// arguments:
//  char *prog: (input) the name of the program
//  Config_t *config_p: (input) configuration for this executable, ex dm_conf[]
//  char **display: (output) seems to contain variables with a ':'
//  char **geometry: (output) seems to contain the letter =
//  char *(*GetDefault)(char *,char *): (input) function to exec command line
//                                      parameters, like pre-processing
//  char last (input): if set, this will stop after comparing commandline to
//                     the config_t
//
// return: always exit(-1)
//
// prints out the layout of a Config_t struct to stdout
//
void pusage(char *prog, Config_t *cp) {
  char valstr[256];
  
  printf ("Usage: %s\n", prog);
  for (; cp && cp->arg_type != NOTYPE; cp++) {
    SPrintVal (cp, valstr);
    printf (" [%s %s] %s - %s\n", cp->swtch, valstr,
	    cp->LongName, cp->Doc);
  }
  exit (-1);
}

// method: env_scan
//
// arguments:
//  char *str (input): if set, this will stop after comparing commandline to
//                     the config_t
//
// return: a new UNIQUE pointer to a string that has had its $'s replaced. This
//         must be deallocated by receiving function or it will be a leak.
//
// env_scan: substitutes environment values into a string using getenv()
// 
char* env_scan(register char *str) {
  char buf[1024];		// buffer for temp use 
  register char *p = buf;	// holds place in the buffer 
  char var[50];		// holds the name of the env variable 
  char *val;
  char dollar[]="$";
  
  while (*str) {
    // must have two $'s in a row to get one, $$ ==> $
    if (*str == '$') {
      if (*++str == '$') {
	*p++ = *str++;
	continue;
      }
      val = var;
      while (isalnum((int) *str) || *str == '_')
	*val++ = *str++;
      *p = '\0';
      val = (val == var) ? dollar : getenv(var);
      if (val) {
	strcat(p, val);
	p += strlen(val);
      }
    }
    // else if not $, just move to next buffer
    //
    else
      *p++ = *str++;
  }
  *p = '\0';
  return strdup(buf);
}

// method: SetVal
//
// arguments:
//  Config_t *cp: (input\output) pointer to a Config_t element
//  char *str: (input) value of thing to move into the *cp
//
// return: 0 on success, -1 on error
//
// env_scan: substitutes environment values into a string using getenv()
// 
int SetVal (register Config_t *cp, char *str) {
  switch (cp->arg_type) {
  case CHAR:
    *(cp->var.CharP) = (char) str[0];
    break;
  case BYTE:
    *(cp->var.ByteP) = (char) atoi (str);
    break;
  case U_BYTE:
    *(cp->var.UByteP) = (u_char) atoi (str);
    break;
  case SHORT:
    *(cp->var.ShortP) = (short) atoi (str);
    break;
  case U_SHORT:
    *(cp->var.UShortP) = (u_short) atoi (str);
    break;
  case INT:
    *(cp->var.IntP) = (int) atoi (str);
    break;
  case U_INT:
    *(cp->var.UIntP) = (u_int) atoi (str);
    break;
  case FLOAT:
    *(cp->var.FloatP) = (float) atof (str);
    break;
  case DOUBLE:
    *(cp->var.DoubleP) = (double) atof (str);
    break;
  case BooL:
    if ((strcasecmp ("on", str) == 0) ||
	(strcasecmp ("1", str) == 0) ||
	(strcasecmp ("t", str) == 0) ||
	(strcasecmp ("true", str) == 0) ||
	(strcasecmp ("y", str) == 0) ||
	(strcasecmp ("yes", str) == 0))
      *(cp->var.BoolP) = TRUE;
    else
      if ((strcasecmp ("off", str) == 0) ||
	  (strcasecmp ("0", str) == 0) ||
	  (strcasecmp ("f", str) == 0) ||
	  (strcasecmp ("false", str) == 0) ||
	  (strcasecmp ("n", str) == 0) ||
	  (strcasecmp ("no", str) == 0))
	*(cp->var.BoolP) = FALSE;
      else
	return (-1);
    break;
  case STRING:
    *(cp->var.StringP) = env_scan(str);
    break;
  case DATA_SRC:
    if (strcasecmp("hsa", str) == 0)
      *(cp->var.DataSrcP) = SRC_HSA;
    else if (strcasecmp("vqfile", str) == 0)
      *(cp->var.DataSrcP) = SRC_VQFILE;
    else if (strcasecmp("adcfile", str) == 0)
      *(cp->var.DataSrcP) = SRC_ADCFILE;
    else
      quit(-1, "Unknown data source %s\n", str);
    break;
  default:
    fprintf (stderr, "ERROR: bad param type %d\n", cp->arg_type);
    return (-1);
  }
  return (0);
}

// method: SPrintVal
//
// arguments:
//  Config_t *cp: (input) pointer to a Config_t element
//  char *str: (output) Prints the config_t element to this string
//
// return: 0 on success, -1 on error
//
// Prints the config_t element\type to 'str' string.
// 
void SPrintVal (register Config_t *cp, char *str) {

  // Error check
  if (!cp || !str)
    return;
  
  switch (cp->arg_type) {
  case CHAR:
    sprintf (str, "%c", *(cp->var.CharP));
    break;
  case BYTE:
    sprintf (str, "%d", *(cp->var.ByteP));
    break;
  case U_BYTE:
    sprintf (str, "%u", *(cp->var.UByteP));
    break;
  case SHORT:
    sprintf (str, "%d", *(cp->var.ShortP));
    break;
  case U_SHORT:
    sprintf (str, "%u", *(cp->var.UShortP));
    break;
  case INT:
    sprintf (str, "%d", *(cp->var.IntP));
    break;
  case U_INT:
    sprintf (str, "%u", *(cp->var.UIntP));
    break;
  case FLOAT:
    sprintf (str, "%f", *(cp->var.FloatP));
    break;
  case DOUBLE:
    sprintf (str, "%f", *(cp->var.DoubleP));
    break;
  case BooL:
    sprintf (str, "%s", (*(cp->var.BoolP) ? "TRUE" : "FALSE"));
    break;
  case STRING:
    sprintf (str, "%s", *(cp->var.StringP));
    break;
  default:
    sprintf (str, "bad param type %d\n", cp->arg_type);
  }
}

#define MAX_NAME_LEN 32
//#define MAX_VALUE_LEN 80 //old define, not large enough for large directories
#define MAX_VALUE_LEN 512

#define NAME 0
#define VALUE 1
#define COMMENT 2

// method: fpconf
//
// arguments:
//  FILE *config_fp: (input) open FILE containing the config file
//  Config_t config_p: (input\output) array of config elements
//  char **display: not used 
//  char **geometry: not used
//  char *(*GetDefault)(char *, char *): not used
//
// return: true = bad usage, false = good usage
//
// this function will go through the file and compare each (name, value) to
// the config_p array and fill in the config_p array. initial FILE* is not
// closed in here.
// 
int fpconf (FILE *config_fp, Config_t config_p[], char **display,
	    char **geometry, char *(*GetDefault)(char *, char *)) {
  int parsed;
  int read_mode = NAME;
  int inchar;
  int bad_usage = FALSE;
  Config_t *cp;
  char name[MAX_NAME_LEN+1];
  char value[MAX_VALUE_LEN+1];
  char name_fm[12];
  char value_fm[12];
  
  sprintf(name_fm, "%%%d[^:]", MAX_NAME_LEN);
  sprintf(value_fm, "%%%d[^\n]", MAX_VALUE_LEN);
  
  for (;;) {
    inchar = fgetc(config_fp);
    
    if (inchar == EOF) {
      if (read_mode == VALUE)
	fprintf(stderr,"ERROR: Value expected after name, %s.\n", name);
      
      return bad_usage;
    }
    
    if (inchar == '\n') {
      read_mode = NAME;
      continue;
    }
    
    // skip whitespace or comments 
    if ( read_mode == COMMENT || inchar == ' ' || inchar == '\t')
      continue;
    
    if ( inchar == ';') {
      read_mode = COMMENT;
      continue;
    }
    
    // put first char of name or value back 
    ungetc(inchar, config_fp);
    
    if (read_mode == NAME) {
      fscanf(config_fp, name_fm, name);
      
      if (fgetc(config_fp) != ':') {
	fprintf(stderr, "ERROR: fpconf: Parameter name too long.");
	exit(-1);
      }
      
      read_mode = VALUE;
      continue;
    }
    
    if (read_mode == VALUE) {
      fscanf(config_fp, value_fm, value);
      read_mode = NAME;
    }
    
    // got a (name, value) pair 
    
    for (parsed = FALSE, cp = config_p; cp->arg_type != NOTYPE; cp++) {
      if (strcasecmp (name, cp->LongName) == 0) {
	parsed = TRUE;
	bad_usage |= SetVal (cp, strdup(value));
	
      }
    }
    
    if (!parsed) {
      fprintf(stderr, "ERROR: fpconf: Unknown parameter %s\n", name);
      bad_usage = TRUE;
    }
  }
  return (bad_usage);
}


void quit(int err, char *fmt, char *str) {
  printf(fmt, str);
  exit(err);
}

