/*****************************************************************************
*   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    *
*****************************************************************************/

#ifdef __MSDOS__
#include <stdlib.h>
#include <alloc.h>
#include <conio.h>
#include <dir.h>
#endif /* __MSDOS__ */

#include <stdio.h>
#include <string.h>
#include "program.h"
#include "config.h"

/* #define	DEBUG   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 *FileName);

/*****************************************************************************
* Routine to print the current configuration data structure contents.	     *
*****************************************************************************/
void ConfigPrint(ConfigStruct *SetUp, int NumVar)
{
    int i;

    fprintf(stderr, "\nCurrent defaults:\n\n");

    for (i=0; i<NumVar; i++)
        switch (SetUp[i].VarType) {
	    case SU_BOOLEAN_TYPE:
		if (* ((int *) SetUp[i].VarData))
		     fprintf(stderr, "\t%-20s = TRUE\n", SetUp[i].VarName);
		else fprintf(stderr, "\t%-20s = FALSE\n", SetUp[i].VarName);
		break;
	    case SU_INTEGER_TYPE:
		fprintf(stderr, "\t%-20s = %d\n", SetUp[i].VarName,
					* ((int *) SetUp[i].VarData));
		break;
	    case SU_REAL_TYPE:
		fprintf(stderr, "\t%-20s = %lg\n", SetUp[i].VarName,
					* ((double *) SetUp[i].VarData));
		break;
	    case SU_STRING_TYPE:
		fprintf(stderr, "\t%-20s = \"%s\"\n", SetUp[i].VarName,
					* ((char **) SetUp[i].VarData));
		break;
	}
}

/*****************************************************************************
* Main routine of config module. Gets the program name (it updates the type) *
* PrgmName, and the structure defines the acceptable variables Setup.	     *
*****************************************************************************/
void Config(char *PrgmName, ConfigStruct *SetUp, int NumVar)
{
    int i, LineCount = 0;
    char CfgName[FILE_NAME_LEN], Line[LINE_LEN], *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(CfgName)) == NULL) return;     /* Search via path var. */

    while (!feof(f)) {
	fgets(Line, LINE_LEN, 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);
}

/*****************************************************************************
*   Routine to interpret the input Line and update the appropriate variable  *
* in SetUp data structure. NumVar holds number of entries in SetUp Table.    *
*****************************************************************************/
static void UpdateVariable(char *Line, ConfigStruct *SetUp, int NumVar,
							 int LineCount)
{
    int i, j;
    char VarName[LINE_LEN], VarData[LINE_LEN], *StrStart, *StrEnd, *NewStr;
    double 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:
		if (sscanf(VarData, "%lf", &Dummy) != 1) {
		    PrintConfigError(ERR_REAL_EXPECTED, LineCount);
		    return;
		}
		*((double *) SetUp[i].VarData) = Dummy;
		return;
	    case SU_STRING_TYPE:
		if ((StrStart = strchr(VarData, '"')) != NULL &&
		    (StrEnd = strrchr(VarData, '"')) != NULL &&
		    StrEnd != StrStart) {
		    NewStr = malloc(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);
}

/*****************************************************************************
*   Routine to print fatal configuration file error, and die.		     *
*****************************************************************************/
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;
    }

    MyExit('c');
}

/*****************************************************************************
*   Routine to search for a file and open it according to attribute at all   *
* directories defined by PATH environment variable and current dir.	     *
*****************************************************************************/
static FILE *FindFile(char *FileName)
{
    FILE *f;

    ConfigPath = searchpath(FileName);
    if ((f = fopen(ConfigPath, "rt")) != NULL) return f;

    fprintf(stderr, "Warning: No config. file (%s) found.\n", FileName);

    return (FILE *) NULL;
}

#ifdef DEBUG

/*****************************************************************************
*   Exit routine							     *
*****************************************************************************/
void MyExit(int ExitCode)
{
    exit(ExitCode);
}

/*****************************************************************************
*   Simple test routine. use it with cnfgtest.bat which rename the program   *
* name so that this module will recognize the appropriate configuration file *
*****************************************************************************/
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 */
