/*
 * Very simple configuration handling functions
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2004 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "th_config.h"
#include "th_util.h"
#include "th_string.h"


void th_config_error(char *pcFilename, size_t lineNum, const char *pcFormat, ...)
{
 va_list ap;
 va_start(ap, pcFormat);
 fprintf(stderr, "%s: Error in '%s', line #%d: ", th_prog_name, pcFilename, lineNum);
 vfprintf(stderr, pcFormat, ap);
 va_end(ap);
}

t_config * th_config_new(void)
{
 t_config *cfg;
 
 cfg = (t_config *) calloc(1, sizeof(t_config));
 if (!cfg) return NULL;

 return cfg;
}


void th_config_free(t_config *cfg)
{
 t_config_item *pCurr, *pNext;

 if (!cfg) return;

 pCurr = cfg->pItems;
 while (pCurr)
	{
	pNext = pCurr->pNext;
	free(pCurr->itemName);
	free(pCurr);
	pCurr = pNext;
	}
}


/*
 * Allocate and add new item to configuration
 */
t_config_item * th_config_add(t_config *cfg, char *itemName, int itemType, BOOL (*itemValidate)(t_config_item *), void *itemData)
{
 t_config_item *pNode;

 if (!cfg) return NULL;
 
 /* Allocate new item */
 pNode = (t_config_item *) calloc(1, sizeof(t_config_item));
 if (!pNode) return NULL;
 
 /* Set values */
 pNode->itemType = itemType;
 pNode->itemData = itemData;
 pNode->itemValidate = itemValidate;
 th_strcpy(&pNode->itemName, itemName);
 
 /* Insert into linked list */ 
 if (cfg->pItems)
	{
	/* The first node's pPrev points to last node */
	LPREV = cfg->pItems->pPrev;	/* New node's prev = Previous last node */
	cfg->pItems->pPrev->pNext = pNode;	/* Previous last node's next = New node */
	cfg->pItems->pPrev = pNode;		/* New last node = New node */
	LNEXT = NULL;				/* But next is NULL! */
 	} else {
	cfg->pItems = pNode;			/* First node ... */
	LPREV = pNode;				/* ... it's also last */
	LNEXT = NULL;				/* But next is NULL! */
 	}

 return pNode;
}


/* Add integer type setting into give configuration
 */
int th_config_add_int(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), int *itemData, int itemDef)
{
 t_config_item *pNode;
 
 pNode = th_config_add(cfg, itemName, ITEM_INT, itemValidate, (void *) itemData);
 if (!pNode) return -1;

 *itemData = itemDef; 

 return 0;
}


/* Add unsigned integer type setting into give configuration
 */
int th_config_add_uint(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), t_uint *itemData, t_uint itemDef)
{
 t_config_item *pNode;
 
 pNode = th_config_add(cfg, itemName, ITEM_UINT, itemValidate, (void *) itemData);
 if (!pNode) return -1;

 *itemData = itemDef; 

 return 0;
}


/* Add strint type setting into given configuration
 */
int th_config_add_str(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), char **itemData, char *itemDef)
{
 t_config_item *pNode;
 
 pNode = th_config_add(cfg, itemName, ITEM_STRING, itemValidate, (void *) itemData);
 if (!pNode) return -1;

 if (itemDef != NULL)
 	*itemData = th_strdup(itemDef);
 	else
 	*itemData = NULL;
 
 return 0;
}


/* Add boolean type setting into given configuration
 */
int th_config_add_bool(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), BOOL *itemData, BOOL itemDef)
{
 t_config_item *pNode;
 
 pNode = th_config_add(cfg, itemName, ITEM_BOOL, itemValidate, (void *) itemData);
 if (!pNode) return -1;

 *itemData = itemDef;

 return 0;
}


/* Read a given file into configuration structure and variables
 */
enum {
	PM_EOF,
	PM_ERROR,
	PM_NORMAL,
	PM_COMMENT,
	PM_NEXT,
	PM_KEYNAME,
	PM_KEYSET,
	PM_STRING,
	PM_INT,
	PM_BOOL,
};

#define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c))

int th_config_read(char *pcFilename, t_config *cfg)
{
 FILE *inFile;
 t_config_item *pItem;
 char tmpStr[SET_MAX_BUF + 1];
 size_t lineNum, strPos;
 int c, parseMode, prevMode, nextMode, tmpCh;
 BOOL isFound, isStart, tmpBool, isError;

 assert(cfg);

 /* Open the file */
 if ((inFile = fopen(pcFilename, "ra")) == NULL)
	return -1;

 /* Parse the configuration */
 lineNum = 1;
 c = -1;
 nextMode = prevMode = parseMode = PM_NORMAL;
 while ((parseMode != PM_EOF) && (parseMode != PM_ERROR))
 {
 	if (c == -1)
 		{
 		/* Get next character */
 		switch (c = fgetc(inFile)) {
 		case EOF:
 			if (parseMode != PM_NORMAL)
 				{
				th_config_error(pcFilename, lineNum,
				"Unexpected end of file.\n");
				parseMode = PM_ERROR;
 				} else
 				parseMode = PM_EOF;
			break;
			
		case '\n':
			lineNum++;
 		}
 		}
	
	switch (parseMode) {
	case PM_COMMENT:
		/* Comment parsing mode */
		if (c == '\n')
			{
			/* End of line, end of comment */
			parseMode = prevMode;
			prevMode = PM_COMMENT;
			}
		c = -1;
		break;
		
 	case PM_NORMAL:
 		/* Normal parsing mode */
 		if (c == '#')
 			{
 			prevMode = parseMode;
			parseMode = PM_COMMENT;
			c = -1;
			} else
		if (VISEND(c))
			{
			c = -1;
			} else
		if (th_isalpha(c))
			{
			/* Start of key name found */
			prevMode = parseMode;
			parseMode = PM_KEYNAME;
			strPos = 0;
			} else
			{
			/* Error! Invalid character found */
			th_config_error(pcFilename, lineNum,
				"Unexpected character '%c'\n", c);
			parseMode = PM_ERROR;
			}
 		break;
	
	case PM_KEYNAME:
		/* Configuration KEY name parsing mode */
		if (c == '#')
 			{
 			/* Start of comment */
 			prevMode = parseMode;
 			parseMode = PM_COMMENT;
			c = -1;
			} else
		if (th_iscrlf(c) || th_isspace(c) || c == '=')
			{
			/* End of key name */
			prevMode = parseMode;
			parseMode = PM_NEXT;
			nextMode = PM_KEYSET;
			} else
		if (th_isalnum(c) || (c == '_'))
			{
			/* Add to key name string */
			VADDCH(c) else
				{
				/* Error! Key name string too long! */
				th_config_error(pcFilename, lineNum,
					"Config key name too long!");
				parseMode = PM_ERROR;
				}
			c = -1;
			} else
			{
			/* Error! Invalid character found */
			th_config_error(pcFilename, lineNum,
				"Unexpected character '%c'\n", c);
			parseMode = PM_ERROR;
			}
		break;

	case PM_KEYSET:
		if (c == '=')
			{
			/* Find key from configuration */
			tmpStr[strPos] = 0;
			isFound = FALSE;
			pItem = cfg->pItems;
			while (pItem && !isFound)
				{
				if (strcmp(pItem->itemName, tmpStr) == 0)
					isFound = TRUE;
					else
					pItem = pItem->pNext;
				}
			
			/* Check if key was found */
			if (isFound)
				{
				/* Okay, set next mode */
				switch (pItem->itemType) {
				case ITEM_STRING: nextMode = PM_STRING; break;

				case ITEM_INT:
				case ITEM_UINT: nextMode = PM_INT; break;

				case ITEM_BOOL: nextMode = PM_BOOL; break;
				}
				
				prevMode = parseMode;
				parseMode = PM_NEXT;
				isStart = TRUE;
				strPos = 0;
				} else
				{
				/* Error! No configuration key by this name found */
				th_config_error(pcFilename, lineNum,
					"No such configuration setting ('%s')\n", tmpStr);
				parseMode = PM_ERROR;
				}

			c = -1;
			} else
			{
			/* Error! '=' expected! */
			th_config_error(pcFilename, lineNum,
				"Unexpected character '%c', '=' expected.\n", c);
			parseMode = PM_ERROR;
			}
		break;

	case PM_NEXT:
		/* Search next item parsing mode */
		if (c == '#')
 			{
 			/* Start of comment */
 			prevMode = parseMode;
 			parseMode = PM_COMMENT;
			} else
		if (th_isspace(c) || th_iscrlf(c))
			{
			/* Ignore whitespaces and linechanges */
			c = -1;
			} else
			{
			/* Next item found */
			prevMode = parseMode;
			parseMode = nextMode;
			}
		break;

	case PM_STRING:
		/* String parsing mode */
		if (isStart)
			{
			/* Start of string, get delimiter */
			tmpCh = c;
			isStart = FALSE;
			strPos = 0;
			} else
		if (c == tmpCh)
			{
			/* End of string, set the value */
			tmpStr[strPos] = 0;
			th_strcpy((char **) pItem->itemData, tmpStr);
			if (pItem->itemValidate)
				pItem->itemValidate(pItem);
			
			prevMode = parseMode;
			parseMode = PM_NORMAL;
			} else
			{
			/* Add character to string */
			VADDCH(c) else
				{
				/* Error! String too long! */
				th_config_error(pcFilename, lineNum,
					"String too long! Maximum is %d characters.", SET_MAX_BUF);
				parseMode = PM_ERROR;
				}
			}

		c = -1;
		break;
		
	case PM_INT:
		/* Integer parsing mode */
		if (isStart && (pItem->itemType == ITEM_UINT) && (c == '-'))
			{
			/* Error! Negative values not allowed for unsigned ints */
			th_config_error(pcFilename, lineNum,
			"Negative value specified, unsigned value expected.");
			parseMode = PM_ERROR;
			} else
		if (isStart && (c == '-' || c == '+'))
			{
			VADDCH(c) else isError = TRUE;
			} else
		if (th_isdigit(c))
			{
			VADDCH(c) else isError = TRUE;
			} else
		if (VISEND(c))
			{
			/* End of integer parsing mode */
			tmpStr[strPos] = 0;
			switch (pItem->itemType) {
			case ITEM_INT:
				*((int *) pItem->itemData) = atoi(tmpStr);
				break;
			
			case ITEM_UINT:
				*((t_uint *) pItem->itemData) = atol(tmpStr);
				break;
			}
			if (pItem->itemValidate)
				pItem->itemValidate(pItem);
			
			prevMode = parseMode;
			parseMode = PM_NORMAL;
			} else
			{
			/* Error! Unexpected character. */
			th_config_error(pcFilename, lineNum,
			"Unexpected character, ", SET_MAX_BUF);
			parseMode = PM_ERROR;			
			}

		if (isError)
			{
			/* Error! String too long! */
			th_config_error(pcFilename, lineNum,
			"String too long! Maximum is %d characters.", SET_MAX_BUF);
			parseMode = PM_ERROR;
			}

		isStart = FALSE;
		c = -1;
		break;
	
	case PM_BOOL:
		/* Boolean parsing mode */
		if (isStart)
			{
			tmpCh = c;
			isStart = FALSE;
			} else
		if (VISEND(c))
			{
			/* End of boolean parsing */
			switch (tmpCh) {
			case 'Y': case 'y':
			case 'T': case 't':
			case '1':
				tmpBool = TRUE;
				break;

			default:
				tmpBool = FALSE;
				break;
			}
			
			/* Set the value */
			*((BOOL *) pItem->itemData) = tmpBool;
			if (pItem->itemValidate)
				pItem->itemValidate(pItem);
			
			prevMode = parseMode;
			parseMode = PM_NORMAL;
			}

		c = -1;
		break;
	}
 }
 
 
 /* Close files */
 fclose(inFile);

 /* Return result */
 if (parseMode == PM_ERROR)
 	return -2;
 	else
 	return 0;
}


/* Validate a given configuration. If setting does not have a defined
 * validation function (function pointer is NULL), value is assumed to be valid.
 *
 * Returns 0 if ok, otherwise positive integer with number of failed validations
 * is returned.
 */
int th_config_validate(t_config *cfg)
{
 t_config_item *pNode;
 int nFailed;
 assert(cfg);

 nFailed = 0;
 pNode = cfg->pItems;
 while (pNode)
 	{
 	if (pNode->itemValidate && !pNode->itemValidate(pNode))
 		nFailed++;

	pNode = pNode->pNext;
 	}
 	
 return nFailed;
}



syntax highlighted by Code2HTML, v. 0.9.1