/*****************************************************************************
*   General routines to	configuration file reading:			     *
* The config file (same name as program, type .cfg) must have th following   *
* format: one variable setup per line, each line consists of two params.     *
* The first is variable name, which is text string without white spaces.     *
* The second param. contains its value, which might be boolean (TRUE/FALSE), *
* integer, or real type.						     *
*   The main routine should get a structure consists of 3 elements per	     *
* variable: the string to match, the variable to save the data read from     *
* config file, and the type (Boolean, Integer, Real), for type checking.     *
* See config.h for exact definition of this data structure.		     *
*									     *
* Version 0.2 - adding String Type.					     *
*									     *
* Written by:  Gershon Elber			       Ver 0.2, Jan. 1989    *
*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include "irit_sm.h"
#include "config.h"
#include "imalloc.h"

/* #define	DEBUG1   Uncomment it for simple test case (see config.bat). */

#define TAB	9

/* Some fatal errors that cause this module to die... */
#define	ERR_ONLY_NAME		1
#define	ERR_BOOL_EXPECTED	2
#define	ERR_INT_EXPECTED	3
#define	ERR_REAL_EXPECTED	4
#define	ERR_STRING_EXPECTED	5
#define	ERR_NOT_EXISTS		6

static char *ConfigPath;

static void UpdateVariable(char *Line,
			   ConfigStruct *SetUp,
			   int NumVar,
			   int LineCount);
static void PrintConfigError(int ErrorNum, int LineCount);
static FILE *FindFile(char *PrgmName, char *FileName);

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to print the current configuration data structure contents.	     M
*                                                                            *
* PARAMETERS:                                                                M
*   SetUp:     Configuration data based.                                     M
*   NumVar:    Number of entries on configuration data base.                 M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   ConfigPrint, configuration, cfg files                                    M
*****************************************************************************/
void ConfigPrint(ConfigStruct *SetUp, int NumVar)
{
    int i;

    fprintf(stderr, "\nCurrent defaults:\n\n");

    for (i = 0; i < NumVar; i++) {
	char VarName[LINE_LEN];

	if (SetUp[i].SomeInfo != NULL && strlen(SetUp[i].SomeInfo) > 0)
	    sprintf(VarName, "%s [%s]", SetUp[i].VarName, SetUp[i].SomeInfo);
	else
	    sprintf(VarName, SetUp[i].VarName);

        switch (SetUp[i].VarType) {
	    case SU_BOOLEAN_TYPE:
		if (* ((int *) SetUp[i].VarData))
		    fprintf(stderr, "\t%-20s = TRUE\n", VarName);
		else
		    fprintf(stderr, "\t%-20s = FALSE\n", VarName);
		break;
	    case SU_INTEGER_TYPE:
		fprintf(stderr, "\t%-20s = %d\n", VarName,
					* ((int *) SetUp[i].VarData));
		break;
	    case SU_REAL_TYPE:
		fprintf(stderr, "\t%-20s = %g\n", VarName,
					* ((RealType *) SetUp[i].VarData));
		break;
	    case SU_STRING_TYPE:
		fprintf(stderr, "\t%-20s = \"%s\"\n", VarName,
					* ((char **) SetUp[i].VarData));
		break;
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Main routine of configuration file handling.				     M
*  Gets the program name, PrgmName, and the configuration data base that     M
*  defines the acceptable variables, Setup, with Numvarentries.		     M
*                                                                            *
* PARAMETERS:                                                                M
*   PrgmName:  Name of program that uses this data base.                     M
*   SetUp:     Configuration data based.                                     M
*   NumVar:    Number of entries on configuration data base.                 M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* KEYWORDS:                                                                  M
*   Config, configuration, cfg files                                         M
*****************************************************************************/
void Config(char *PrgmName, ConfigStruct *SetUp, int NumVar)
{
    int i, LineCount = 0;
    char CfgName[PATH_NAME_LEN], Line[LINE_LEN_LONG], *Cptr;
    FILE *f;

    i = strlen(PrgmName) - 1;			 /* Skip the full path name: */
    while (i && PrgmName[i] != '\\' && PrgmName[i] != '/'
	      && PrgmName[i] != ':') i--;
    if (i)
	i++;

    strcpy(CfgName, &PrgmName[i]);
    Cptr = strchr(CfgName, '.');
    if (Cptr != NULL)
	*Cptr = 0;					 /* Delete old type. */
    strcat(CfgName, ".cfg");		    /* And add the config file type. */
    if ((f = FindFile(PrgmName, CfgName)) == NULL)
	return;					     /* Search via path var. */

    while (!feof(f)) {
	fgets(Line, LINE_LEN_LONG, f);
	LineCount++;

	i = 0;	/* Delete all the part after the ; (The comment) if was any. */
	while (Line[i] != 0 && Line[i] != ';') i++;
        if (Line[i])
	    Line[i] = 0;

	i = 0;			   /* Now test if that line is empty or not: */
	while (Line[i] != 0 && Line[i] <= ' ') i++;    /* Skip white spaces. */

	if (Line[i])
	    UpdateVariable(Line, SetUp, NumVar, LineCount);
    }

    fclose(f);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Routine to interpret the input Line and update the appropriate variable  *
* in SetUp data structure. NumVar holds number of entries in SetUp Table.    *
*                                                                            *
* PARAMETERS:                                                                *
*   Line:       Single line from a configuration file to interpret.          *
*   SetUp:      Configuration data based.                                    *
*   NumVar:     Number of entries on configuration data base.                *
*   LineCount:  Line number.                                                 *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void UpdateVariable(char *Line,
			   ConfigStruct *SetUp,
			   int NumVar,
			   int LineCount)
{
    int i, j;
    char VarName[LINE_LEN_LONG], VarData[LINE_LEN_LONG], *StrStart, *StrEnd,
	*NewStr;
    RealType Dummy;	  /* Force linking of floating point scanf routines. */

    i = j = 0;
    while (Line[i] > ' ') {			  /* Copy the Variable name: */
	VarName[i] = Line[i];
	i++;
    }
    VarName[i] = 0;

    while (Line[i] != 0 && Line[i] <= ' ') i++;
    if (Line[i] == 0)
	PrintConfigError(ERR_ONLY_NAME, LineCount);

    while (Line[i] >= ' ' || Line[i] == TAB)	  /* Copy the Variable data: */
	VarData[j++] = Line[i++];
    VarData[j] = 0;

    for (i = 0; i < NumVar; i++)
	if (strcmp(VarName, SetUp[i].VarName) == 0)
	    switch (SetUp[i].VarType) {
	        case SU_BOOLEAN_TYPE:
		    if (strnicmp(VarData, "True", 4) == 0 ||
			strnicmp(VarData, "False", 5) == 0)
		        *((int *) SetUp[i].VarData) =
			    (strnicmp (VarData, "True", 4) == 0);
		    else
		        PrintConfigError(ERR_BOOL_EXPECTED, LineCount);
		    return;
		case SU_INTEGER_TYPE:
		    if (sscanf(VarData, "%d", (int *) SetUp[i].VarData) != 1)
		        PrintConfigError(ERR_INT_EXPECTED, LineCount);
		    return;
		case SU_REAL_TYPE:
#ifdef DOUBLE
		    if (sscanf(VarData, "%lf", &Dummy) != 1) {
#else
		    if (sscanf(VarData, "%f", &Dummy) != 1) {
#endif /* DOUBLE */
			PrintConfigError(ERR_REAL_EXPECTED, LineCount);
			return;
		    }
		    *((RealType *) SetUp[i].VarData) = Dummy;
		    return;
		case SU_STRING_TYPE:
		    if ((StrStart = strchr(VarData, '"')) != NULL &&
			(StrEnd = strrchr(VarData, '"')) != NULL &&
			StrEnd != StrStart) {
			NewStr = IritMalloc(1 + ((unsigned int)
							(StrEnd - StrStart)));
			j = 0;
			while (++StrStart != StrEnd)
			    NewStr[j++] = *StrStart;
			NewStr[j] = 0;
			*((char **) SetUp[i].VarData) = NewStr;
		    }
		    else
			PrintConfigError(ERR_STRING_EXPECTED, LineCount);
		    return;
	   }

    PrintConfigError(ERR_NOT_EXISTS, LineCount);
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Routine to print fatal configuration file error, and die.		     *
*                                                                            *
* PARAMETERS:                                                                *
*   ErrorNum:   Error number.                                                *
*   LineCount:  Line where error had occured, in configuration file.         *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void PrintConfigError(int ErrorNum, int LineCount)
{
    fprintf(stderr, "Config. file error [%s line %d]: ",
						ConfigPath, LineCount);

    switch (ErrorNum) {
	case ERR_ONLY_NAME:
	    fprintf(stderr, "Only Name found.\n");
	    break;
        case ERR_BOOL_EXPECTED:
	    fprintf(stderr, "Boolean type expected.\n");
	    break;
	case ERR_INT_EXPECTED:
	    fprintf(stderr, "Integer type expected.\n");
	    break;
	case ERR_REAL_EXPECTED:
	    fprintf(stderr, "Real type expected.\n");
	    break;
	case ERR_STRING_EXPECTED:
	    fprintf(stderr, "String (within \") type expected.\n");
	    break;
	case ERR_NOT_EXISTS:
	    fprintf(stderr, "No such set up option.\n");
	    break;
    }

    exit('c');
}

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Routine to search for a file and open it according to attribute at all   *
* directories defined by PATH environment variable and current dir.	     *
*                                                                            *
* PARAMETERS:                                                                *
*   PrgmName:   Name of program that uses the configuration file.            *
*   FileName:   Name of configuration file.                                  *
*                                                                            *
* RETURN VALUE:                                                              *
*   FILE *:    Open file is found, NULL otherwise                            *
*****************************************************************************/
static FILE *FindFile(char *PrgmName, char *FileName)
{
    FILE *f;

    ConfigPath = searchpath(FileName);
    if ((f = fopen(ConfigPath, "rt")) != NULL)
	return f;

    fprintf(stderr, "%s: Warning: No config. file (%s) found.\n",
	    PrgmName, FileName);

    return NULL;
}

#ifdef DEBUG_CONFIG

/*****************************************************************************
* DESCRIPTION:                                                               *
*   Simple test routine.                                                     *
*****************************************************************************/
void main(int argc, char **argv)
{
    static int Test1, Test2, Test3, Test4;
    static double Test5, Test6;
    static char *Test7, *Test8;

    ConfigStruct SetUp[] =
	{ { "TestOne",   (VoidPtr) &Test1, SU_BOOLEAN_TYPE },
	  { "TestTwo",   (VoidPtr) &Test2, SU_BOOLEAN_TYPE },
	  { "Test333",   (VoidPtr) &Test3, SU_INTEGER_TYPE },
	  { "Testing4",  (VoidPtr) &Test4, SU_INTEGER_TYPE },
	  { "TestReal5", (VoidPtr) &Test5, SU_REAL_TYPE },
	  { "TestReal6", (VoidPtr) &Test6, SU_REAL_TYPE },
	  { "String7",	 (VoidPtr) &Test7, SU_STRING_TYPE },
	  { "String8",   (VoidPtr) &Test8, SU_STRING_TYPE } };

    Test1 = Test2 = Test3 = Test4 = 9999;
    Test5 = Test6 = 0.9999;
    Test7 = Test8 = NULL;

    printf("Before:\nTest1 = %d, Test2 = %d, Test3 = %d, Test4 = %d\n"
	    "Test5 = %lf, Test6 = %lf\nTest7 = \"%s\"\nTest8 = \"%s\"\n",
	    Test1, Test2, Test3, Test4, Test5, Test6, Test7, Test8);

    Config(*argv, SetUp, 8); /* Do it! */

    printf("After:\nTest1 = %d, Test2 = %d, Test3 = %d, Test4 = %d\n"
	    "Test5 = %lf, Test6 = %lf\nTest7 = \"%s\"\nTest8 = \"%s\"\n",
	    Test1, Test2, Test3, Test4, Test5, Test6, Test7, Test8);

    printf("\nConfigPrint prints:\n");
    ConfigPrint(SetUp, 8);
}

#endif /* DEBUG_CONFIG */
