/*
 * file ini_file.h - parsing ini-file into a database
 *
 * $Id: ini_file.c,v 1.25 2006/04/11 16:44:00 fzago Exp $
 *
 * Program XBLAST
 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will be entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "xblast.h"

/*
 * local types
 */
/* single entry */
struct _db_entry
{
	XBAtom atom;				/* atom for entry key */
	char *value;				/* string value of key */
	struct _db_entry *next;		/* next entry for this section */
};

/* one complete section */
struct _db_section
{
	XBAtom atom;				/* name atom of section -> [...] line in file */
	XBBool changed;				/* flag for unsaved changes in section */
	DBEntry *entry;				/* list of entries */
	struct _db_section *next;	/* next section in this database */
};

/* one database */
struct _db_root
{
	DBType type;				/* in which list is database stored -> dirname */
	XBAtom atom;				/* name atom for database -> filename */
	XBBool changed;				/* flag for changes */
	DBSection *section;			/* pointer to sections */
	DBRoot *next;				/* next database of this type */
};

/*
 * local variables
 */

/* database lists */
static DBRoot *db_list[NUM_DT] = {
	NULL, NULL, NULL, NULL,
};

/* paths for database file */
static const char *db_path[NUM_DT] = {
	"config",
	GAME_DATADIR "/level",
	"demo",
	"central",
};

/* file extensions */
static const char *db_ext[NUM_DT] = {
	"cfg",
	"xal",
	"dem",
	"dat",
};

/*******************
 * local functions *
 *******************/

/* free memory of defined structures */

/*
 * free memory of an entry
 */
static void
FreeEntry (DBEntry * entry)
{
	assert (entry != NULL);
	assert (entry->value != NULL);
	free (entry->value);
	free (entry);
}								/* FreeEntry */

/*
 * free memory of a section and all its entries
 */
static void
FreeSection (DBSection * section)
{
	DBEntry *entry;
	DBEntry *nextEntry;

	assert (NULL != section);
	/* clear all entries */
	for (entry = section->entry; entry != NULL; entry = nextEntry) {
		nextEntry = entry->next;
		FreeEntry (entry);
	}
	free (section);
}								/* FreeSection */

/*
 * free memory of a given database
 */
static void
FreeRoot (DBRoot * db)
{
	DBSection *section;
	DBSection *nextSection;

	assert (db != NULL);
	/* clear all sections */
	for (section = db->section; section != NULL; section = nextSection) {
		nextSection = section->next;
		FreeSection (section);
	}
	free (db);
}								/* DB_Delete */

/* parse input lines */

/*
 * parse section name from given line
 */
static const char *
ParseSection (const char *line)
{
	const char *left;
	char *dst;
	static char result[256];

	/* check left side */
	for (left = line; *left != 0 && isspace ((unsigned char)*left); left++)
		continue;
	if (*left != '[') {
		return NULL;
	}
	left++;
	dst = result;
	while (*left != ']') {
		if ('\0' == *left) {
			/* premature end of line */
			return NULL;
		}
		*dst = *left;
		dst++;
		left++;
	}
	*dst = '\0';
	return result;
}								/* ParseSection */

/*
 * parse key part from a given line
 */
static const char *
ParseEntryName (const char *line)
{
	const char *src;
	char *dst;
	static char result[256];

	src = line;
	dst = result;
	while (*src != '=') {
		if ('\0' == *src) {
			/* premature end of line */
			return NULL;
		}
		*dst = *src;
		dst++;
		src++;
	}
	*dst = '\0';
	return result;
}								/* ParseEntryName */

/*
 * return pointer to a named entry in given section
 */
static const DBEntry *
GetEntry (const DBSection * section, XBAtom atom)
{
	const DBEntry *ptr;
	assert (section != NULL);
	/* find entry */
	for (ptr = section->entry; ptr != NULL; ptr = ptr->next) {
		if (ptr->atom == atom) {
			return ptr;
		}
	}
	return NULL;
}								/* GetEntry */

/*
 * return if character is eol
 * External level files could also come from windows environment,
 * where '\r' can be considered an EOL char for ini-file purposes.
 */
static int
XB_iniEOL (char c)
{
	switch (c) {
	case '\n':					/* FALLTHRU */
	case '\r':
		return 1;
	default:
		return 0;
	}
}								/* XB_iniEOL */

/*
 * parse the entry value part of a line
 */
static const char *
ParseEntryValue (const char *line)
{
	const char *src;
	char *dst;
	static char result[256];
	dst = result;
	src = strchr (line, '=');
	if (NULL == src) {
		return NULL;
	}
	src++;
	while (isspace ((unsigned char)*src)) {
		src++;
	}
	while ((!XB_iniEOL (*src)) && (*src != '\0')) {
		*dst = *src;
		dst++;
		src++;
	}

	while (isspace ((unsigned char)*(dst - 1))) {
		--dst;
	}
	*dst = 0;
	return result;
}								/* ParseEntryValue */

/* file operations */

/*
 * open file corresponding to given database
 */
static FILE *
OpenDBFile (const DBRoot * db, const char *mode)
{
	char db_name[320];
	assert (NULL != db);
	assert (NULL != mode);
	/* add index for demos */
	sprintf (db_name, "%s", GUI_AtomToString (db->atom));
	return FileOpen (db_path[db->type], db_name, db_ext[db->type], mode);
}								/* OpenDBFile */

/*
 * check if file directory database must be updated
 */
static XBBool
CheckInsertFile (const DBRoot * db, const char *name, XBAtom timeAtom, time_t mtime)
{
	const DBSection *section;
	time_t ltime;
	/* does file exist as section name in database */
	section = DB_GetSection (db, GUI_StringToAtom (name));
	if (NULL == section) {
		return XBFalse;
	}
	/* has the file been modified */
	if (!DB_GetEntryTime (section, timeAtom, &ltime) || ltime != mtime) {
		return XBFalse;
	}
	/* file entry exists and is up-to-date */
	return XBTrue;
}								/* CheckInsertFile */

/*
 * insert file into database
 */
static XBBool
InsertFile (DBRoot * db, DBType type, const char *name, XBAtom timeAtom, time_t mtime,
			XBAtom fromAtom, DBLoadDirFunc func)
{
	DBRoot *dbFile = NULL;
	DBSection *toSection = NULL;
	const DBSection *fromSection = NULL;
	const DBEntry *entry = NULL;
	XBAtom toAtom = GUI_StringToAtom (name);
	/* try to load file type/name.ext into a temp database */
	dbFile = DB_Create (type, toAtom);
	assert (dbFile != NULL);
	if (!DB_Load (dbFile)) {
		goto Error;
	}
	/* try to find given section */
	fromSection = DB_GetSection (dbFile, fromAtom);
	if (NULL == fromSection) {
		goto Error;
	}
	/* create new section for file data */
	toSection = DB_CreateSection (db, toAtom);
	assert (NULL != toSection);
	/* create entry for file modification time */
	DB_CreateEntryTime (toSection, timeAtom, mtime);
	/* now copy section */
	for (entry = fromSection->entry; NULL != entry; entry = entry->next) {
		(void)DB_CreateEntryString (toSection, entry->atom, entry->value);
	}
	/* call user function if any */
	if (NULL != func) {
		(*func) (toSection);
	}
	/* clean up */
	DB_Delete (dbFile);
	return XBTrue;

  Error:
	if (NULL != dbFile) {
		DB_Delete (dbFile);
	}
	if (NULL != toSection) {
		DB_DeleteSection (db, toAtom);
	}
	return XBFalse;
}								/* CheckInsertFile */

/*
 * create named section in given database and mark it changed
 */
static void
UpdateFile (DBRoot * db, const char *name)
{
	DBSection *section = DB_CreateSection (db, GUI_StringToAtom (name));
	assert (NULL != section);
	section->changed = XBTrue;
}								/* UpdateFile */

/*
 * delete any unchanged sections
 */
static XBBool
DeleteUnchangedSections (DBRoot * db)
{
	DBSection *ptr;
	DBSection *next;
	XBBool deleted = XBFalse;
	assert (NULL != db);
	/* delete first section, while unchanged */
	while (NULL != db->section && !db->section->changed) {
		next = db->section->next;
		FreeSection (db->section);
		db->section = next;
		deleted = XBTrue;
#ifdef DEBUG
		putchar ('-');
#endif
	}
	/* now proceed with the rest */
	if (NULL != db->section) {
		for (ptr = db->section; ptr != NULL && ptr->next != NULL; ptr = ptr->next) {
			while (NULL != ptr->next && !ptr->next->changed) {
				next = ptr->next->next;
				FreeSection (ptr->next);
				ptr->next = next;
				deleted = XBTrue;
#ifdef DEBUG
				putchar ('-');
#endif
			}
		}
	}
#ifdef DEBUG
	fflush (stdout);
#endif
	return deleted;
}								/* DeleteUnchangedSections */

/*************************
 * get/set database info *
 *************************/

/*
 * create a new database of given type and name
 */
DBRoot *
DB_Create (DBType type, XBAtom atom)
{
	DBRoot *db;
	assert (ATOM_INVALID != atom);
	/* create new database */
	db = calloc (1, sizeof (DBRoot));
	assert (NULL != db);
	db->atom = atom;
	db->type = type;
	db->changed = XBTrue;
	/* store in list */
	db->next = db_list[type];
	db_list[type] = db;
	/* thatīs all */
	return db;
}								/* DB_Create */

/*
 * return pointer of an existing database
 */
const DBRoot *
DB_Get (DBType type, XBAtom atom)
{
	const DBRoot *db;
	assert (ATOM_INVALID != atom);
	for (db = db_list[type]; db != NULL; db = db->next) {
		if (db->atom == atom) {
			return db;
		}
	}
	return NULL;
}								/* DB_Get */

/*
 * get name atom of database (filename)
 */
XBAtom
DB_Atom (const DBRoot * db)
{
	assert (NULL != db);
	return db->atom;
}								/* DB_Changed */

/*
 * check if database has changed
 */
XBBool
DB_Changed (const DBRoot * db)
{
	const DBSection *section;
	if (db->changed) {
		return XBTrue;
	}
	for (section = db->section; section != NULL; section = section->next) {
		if (section->changed) {
			return XBTrue;
		}
	}
	return XBFalse;
}								/* DB_Changed */

/*
 * set name atom of given database (filename)
 */
void
DB_SetAtom (DBRoot * db, XBAtom atom)
{
	db->atom = atom;
	db->changed = XBTrue;
}								/* DB_SetAtom */

/*
 * mark given database as unchanged
 */
static void
MarkUnchanged (DBRoot * db)
{
	DBSection *section;
	/* mark all as unchanged */
	db->changed = XBFalse;
	for (section = db->section; section != NULL; section = section->next) {
		section->changed = XBFalse;
	}
}								/* MarkUnchanged */

/*
 * delete a given database
 */
void
DB_Delete (DBRoot * db)
{
	DBRoot *ptr;
	assert (db_list[db->type] != NULL);
	/* check if first element in list */
	if (db_list[db->type] == db) {
		db_list[db->type] = db_list[db->type]->next;
	}
	else {
		for (ptr = db_list[db->type]; ptr->next != NULL; ptr = ptr->next) {
			if (db == ptr->next) {
				ptr->next = db->next;
				break;
			}
		}
	}
	FreeRoot (db);
}								/* DB_Delete */

/************************
 * get/set section info *
 ************************/

/*
 * create a new section in a given database
 */
DBSection *
DB_CreateSection (DBRoot * db, XBAtom atom)
{
	DBSection *section;

	/* create new data structure */
	assert (NULL != db);
	assert (ATOM_INVALID != atom);
	/* try to find section first */
	for (section = db->section; section != NULL; section = section->next) {
		if (section->atom == atom) {
			return section;
		}
	}
	/* create new section */
	db->changed = XBTrue;
	section = calloc (1, sizeof (DBSection));
	assert (NULL != section);
	section->atom = atom;
	section->changed = XBTrue;
	/* store in database */
	section->next = db->section;
	db->section = section;
	/* thatīs all */
	return section;
}								/* DB_CreateSection */

/*
 * return pointer to a named section in given database
 */
DBSection *
DB_GetSection (const DBRoot * db, XBAtom atom)
{
	DBSection *ptr;

	assert (NULL != db);
	assert (ATOM_INVALID != atom);
	/* find section */
	for (ptr = db->section; ptr != NULL; ptr = ptr->next) {
		if (ptr->atom == atom) {
			return ptr;
		}
	}
	return NULL;
}								/* DB_GetSection */

/*
 * get section atom
 */
XBAtom
DB_SectionAtom (const DBSection * section)
{
	assert (section != NULL);
	return section->atom;
}								/* DB_SectionAtom */

/*
 * get the name of the nth section in given database
 */
XBAtom
DB_IndexSection (const DBRoot * db, int index)
{
	const DBSection *ptr;
	int num = 0;
	assert (NULL != db);
	/* find section */
	for (ptr = db->section; ptr != NULL; ptr = ptr->next) {
		if (num == index) {
			return ptr->atom;
		}
		num++;
	}
	return ATOM_INVALID;
}								/* DB_IndexSection */

/*
 * number of sections in a given database
 */
int
DB_NumSections (const DBRoot * db)
{
	const DBSection *ptr;
	int num = 0;
	assert (NULL != db);
	/* find section */
	for (ptr = db->section; ptr != NULL; ptr = ptr->next) {
		num++;
	}
	return num;
}								/* DB_NumSections */

/*
 * delete a section in a given database
 */
void
DB_DeleteSection (DBRoot * db, XBAtom atom)
{
	DBSection *save, *ptr;
	assert (db != NULL);
	db->changed = XBTrue;
	/* do we have any sections ? */
	if (NULL == db->section) {
		return;
	}
	assert (ATOM_INVALID != atom);
	/* it is the first section ? */
	if (db->section->atom == atom) {
		save = db->section;
		db->section = db->section->next;
		FreeSection (save);
	}
	else {
		for (ptr = db->section; ptr->next != NULL; ptr = ptr->next) {
			if (ptr->next->atom == atom) {
				save = ptr->next;
				ptr->next = ptr->next->next;
				FreeSection (save);
				break;
			}
		}
	}
}								/* DB_DeleteSection */

/*
 * delete all sections in a given database
 */
void
DB_DeleteAll (DBRoot * db)
{
	DBSection *save;
	assert (db != NULL);
	db->changed = XBTrue;
	/* do we have any sections ? */
	if (NULL == db->section) {
		return;
	}
	while (db->section != NULL) {
		save = db->section;
		db->section = db->section->next;
		FreeSection (save);
	}
}								/* DB_DeleteAll */

/*************************
 * get entry information *
 *************************/

/*
 * create a string entry in a given section
 */
XBBool
DB_CreateEntryString (DBSection * section, XBAtom atom, const char *value)
{
	DBEntry *entry;

	assert (ATOM_INVALID != atom);
	assert (NULL != section);
	/* mark as changed */
	section->changed = XBTrue;
	/* try to find entry first */
	for (entry = section->entry; entry != NULL; entry = entry->next) {
		if (entry->atom == atom) {
			if (NULL != entry->value) {
				free (entry->value);
			}
			entry->value = DupString (value);
			assert (NULL != entry->value);
			return XBTrue;
		}
	}
	/* create new data strutcure */
	entry = calloc (1, sizeof (DBEntry));
	assert (NULL != entry);
	entry->atom = atom;
	entry->value = DupString (value);
	assert (NULL != entry->value);
	/* store in database */
	entry->next = section->entry;
	section->entry = entry;
	/* that's all */
	return XBTrue;
}								/* DB_CreateEntryString */

/*
 * create an atom entry in a given section
 */
XBBool
DB_CreateEntryAtom (DBSection * section, XBAtom atom, XBAtom value)
{
	return DB_CreateEntryString (section, atom, GUI_AtomToString (value));
}								/* DB_CreateEntryAtom */

/*
 * create an integer entry in a given section
 */
XBBool
DB_CreateEntryInt (DBSection * section, XBAtom atom, int value)
{
	char tmp[32];
	sprintf (tmp, "%d", value);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryInt */

/*
 * create a game result entry in a given section
 */
XBBool
DB_CreateEntryGameResult (DBSection * section, XBAtom atom, int score)
{
	char tmp[32];
	sprintf (tmp, "%d", score);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntry */

/*
 * create a player rating entry in a given section
 */
XBBool
DB_CreateEntryPlayerRating (DBSection * section, XBAtom atom, int PID, float rating)
{
	char tmp[32];
	sprintf (tmp, "%d %f", PID, rating);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryPlayerRating */

/*
 * create a bool entry in given section
 */
XBBool
DB_CreateEntryBool (DBSection * section, XBAtom atom, XBBool value)
{
	return DB_CreateEntryString (section, atom, value ? "true" : "false");
}								/* DB_CreateEntryBool */

/*
 * create a color entry in given section
 */
XBBool
DB_CreateEntryColor (DBSection * section, XBAtom atom, XBColor color)
{
	char tmp[32];
	sprintf (tmp, "#%02x%02x%02x", GET_RED (color) << 3, GET_GREEN (color) << 3,
			 GET_BLUE (color) << 3);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryColor */

/*
 * create a time entry in given section
 */
XBBool
DB_CreateEntryTime (DBSection * section, XBAtom atom, time_t value)
{
	char tmp[64];
	size_t len;
	len = sprintf (tmp, "%lu ; %s", value, ctime (&value));
	/* cut trailing newline */
	tmp[len - 1] = 0;
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryTime */

/*
 * create a float entry in given section
 */
XBBool
DB_CreateEntryFloat (DBSection * section, XBAtom atom, double value)
{
	char tmp[32];
	sprintf (tmp, "%f", value);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryFloat */

/*
 * create a position entry in given section
 */
XBBool
DB_CreateEntryPos (DBSection * section, XBAtom atom, BMPosition * pValue)
{
	char tmp[64];
	sprintf (tmp, "%d %d", pValue->x, pValue->y);
	return DB_CreateEntryString (section, atom, tmp);
}								/* DB_CreateEntryPos */

/*
 * create entry in given section, defined by given key=value line
 */
XBBool
DB_ParseEntry (DBSection * section, const char *line)
{
	const char *entryName;
	assert (NULL != section);
	assert (NULL != line);
	/* parse entry */
	if (NULL == (entryName = ParseEntryName (line))) {
		return XBFalse;
	}
	return DB_CreateEntryString (section, GUI_StringToAtom (entryName), ParseEntryValue (line));
}								/* DB_ParseEntry */

/*
 * copy name section/entry from given section to given database
 */
XBBool
DB_CopyEntry (DBRoot * db, const DBSection * section, const XBAtom atom)
{
	DBSection *sec;
	const DBEntry *ptr;
	assert (NULL != db);
	assert (NULL != section);
	assert (NULL != db);
	/* try to create section in database */
	sec = DB_CreateSection (db, section->atom);
	if (NULL == sec) {
		return XBFalse;
	}
	/* try to get entry */
	ptr = GetEntry (section, atom);
	if (NULL == ptr) {
		return XBFalse;
	}
	/* now duplicate entry */
	return DB_CreateEntryString (sec, atom, ptr->value);
}								/* DB_CopyEntry */

/*
 * get the name of the nth entry in given section
 */
XBAtom
DB_IndexEntry (const DBSection * section, int index)
{
	const DBEntry *ptr;
	int num = 0;
	assert (NULL != section);
	/* find section */
	for (ptr = section->entry; ptr != NULL; ptr = ptr->next) {
		if (num == index) {
			return ptr->atom;
		}
		num++;
	}
	return ATOM_INVALID;
}								/* DB_IndexSection */

/*
 * number of entries in given database
 */
int
DB_NumAllEntries (const DBRoot * db)
{
	const DBSection *sec;
	int num = 0;
	assert (NULL != db);
	for (sec = db->section; sec != NULL; sec = sec->next) {
		num += DB_NumEntries (sec);
	}
	return num;
}								/* DB_NumAllEntries */

/*
 * number of entries in given section
 */
int
DB_NumEntries (const DBSection * section)
{
	const DBEntry *ptr;
	int num = 0;
	assert (NULL != section);
	/* find section */
	for (ptr = section->entry; ptr != NULL; ptr = ptr->next) {
		num++;
	}
	return num;
}								/* DB_NumEntries */

/*
 * get string value for named entry in given section
 */
XBBool
DB_GetEntryString (const DBSection * section, XBAtom atom, const char **pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (entry->value != NULL);
	*pValue = entry->value;
	return XBTrue;
}								/* DB_GetEntryString */

/*
 * get atom value for named entry in given section
 */
XBBool
DB_GetEntryAtom (const DBSection * section, XBAtom atom, XBAtom * pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	*pValue = GUI_StringToAtom (entry->value);
	return (ATOM_INVALID != *pValue);
}								/* DB_GetEntryAtom */

/*
 * get int value for named entry in given section
 */
XBBool
DB_GetEntryInt (const DBSection * section, XBAtom atom, int *pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	return (1 == sscanf (entry->value, "%d", pValue)) ? XBTrue : XBFalse;
}								/* DB_GetEntryInt */

/*
 * get bool value for named entry in given section
 */
XBBool
DB_GetEntryBool (const DBSection * section, XBAtom atom, XBBool * pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	if (0 == strcmp (entry->value, "true")) {
		*pValue = XBTrue;
	}
	else if (0 == strcmp (entry->value, "false")) {
		*pValue = XBFalse;
	}
	else {
		return XBFalse;
	}
	return XBTrue;
}								/* DB_GetEntryBool */

/*
 * get color value for named entry in given section
 */
XBBool
DB_GetEntryColor (const DBSection * section, XBAtom atom, XBColor * pValue)
{
	const DBEntry *entry;
	unsigned red, green, blue;
	char hash;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	if (4 != sscanf (entry->value, "%c%2X%2X%2X", &hash, &red, &green, &blue)) {
		return XBFalse;
	}
	if (hash != '#') {
		return XBFalse;
	}
	*pValue = SET_COLOR (red >> 3, green >> 3, blue >> 3);
	return XBTrue;
}								/* DB_GetEntryColor */

/*
 * get time value for named entry in given sectiong
 */
XBBool
DB_GetEntryTime (const DBSection * section, XBAtom atom, time_t * pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	return (1 == sscanf (entry->value, "%lu", pValue)) ? XBTrue : XBFalse;
}								/* DB_GetEntryTime */

/*
 * get float value for named entry in given section
 */
XBBool
DB_GetEntryFloat (const DBSection * section, XBAtom atom, double *pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	return (1 == sscanf (entry->value, "%lf", pValue)) ? XBTrue : XBFalse;
}								/* DB_GetEntryFloat */

/*
 * get position value for named entry in given section
 */
XBBool
DB_GetEntryPos (const DBSection * section, XBAtom atom, BMPosition * pValue)
{
	const DBEntry *entry;
	assert (pValue != NULL);
	entry = GetEntry (section, atom);
	if (NULL == entry) {
		return XBFalse;
	}
	assert (NULL != entry->value);
	return (2 == sscanf (entry->value, "%hd%hd", &pValue->x, &pValue->y)) ? XBTrue : XBFalse;
}								/* DB_GetEntryPos */

/*
 * get string value for named entry in given section and convert to integer
 */
DBConversionResult
DB_ConvertEntryInt (const DBSection * section, XBAtom atom, int *pValue, const DBToInt * convTable)
{
	const DBEntry *entry;
	const DBToInt *ptr;
	int cmp;
	if (NULL == (entry = GetEntry (section, atom))) {
		return DCR_NoSuchEntry;
	}
	assert (convTable != NULL);
	for (ptr = convTable; ptr->key != NULL; ptr++) {
		cmp = strcmp (ptr->key, entry->value);
		if (0 == cmp) {
			/* match */
			assert (pValue != NULL);
			*pValue = ptr->value;
			return DCR_Okay;
		}
		else if (cmp > 0) {
			return DCR_Failure;
		}
	}
	return DCR_Failure;
}								/* DB_ConvertEntryInt */

/*
 * get string value for named entry in given section and convert to a generic pointer
 */
DBConversionResult
DB_ConvertEntryData (const DBSection * section, XBAtom atom, void **pValue,
					 const DBToData * convTable)
{
	const DBEntry *entry;
	const DBToData *ptr;
	int cmp;
	if (NULL == (entry = GetEntry (section, atom))) {
		return DCR_NoSuchEntry;
	}
	assert (convTable != NULL);
	for (ptr = convTable; ptr->key != NULL; ptr++) {
		cmp = strcmp (ptr->key, entry->value);
		if (0 == cmp) {
			/* match */
			assert (pValue != NULL);
			*pValue = ptr->value;
			return DCR_Okay;
		}
		else if (cmp > 0) {
			return DCR_Failure;
		}
	}
	return DCR_Failure;
}								/* DB_ConvertEntryData */

/*
 * get string value for named entry in given section and convert to an entry flag number
 */
DBConversionResult
DB_ConvertEntryFlags (const DBSection * section, XBAtom atom, unsigned *pValue,
					  const DBToInt * convTable)
{
	const DBEntry *entry;
	const DBToInt *ptr;
	int cmp;
	int i, argc;
	char **argv;
	if (NULL == (entry = GetEntry (section, atom))) {
		return DCR_NoSuchEntry;
	}
	assert (convTable != NULL);
	assert (pValue != NULL);
	*pValue = 0;
	/* split value string */
	argv = SplitString (entry->value, &argc);
	assert (NULL != argv);
	/* parse list */
	for (i = 0; i < argc; i++) {
		for (ptr = convTable; ptr->key != NULL; ptr++) {
			cmp = strcmp (ptr->key, argv[i]);
			if (0 == cmp) {
				/* match */
				*pValue |= ptr->value;
				break;
			}
			else if (cmp > 0) {
				free ((char *)argv);
				return DCR_Failure;
			}
		}
	}
	free ((char *)argv);
	return DCR_Okay;
}								/* DB_ConvertEntryFlag */

/*
 * create a string line for n-th entry in given section, return length
 */
size_t
DB_PrintEntry (char *buffer, const DBSection * section, int index)
{
	const DBEntry *ptr;
	int num = 0;
	assert (NULL != buffer);
	assert (NULL != section);
	/* find entry */
	for (ptr = section->entry; ptr != NULL; ptr = ptr->next) {
		if (num == index) {
			return sprintf (buffer, "%s=%s", GUI_AtomToString (ptr->atom), ptr->value);
		}
		num++;
	}
	/* entry not found */
	buffer[0] = 0;
	return 0;
}								/* DB_PrintEntry */

/*
 * add string to sized buffer, fill with x if added string does not fit
 */
static XBBool
AddStringToBuffer (char *buf, size_t len, const char *add)
{
	int i, nul, write;
	/* sanity checks */
	assert (NULL != buf);
	assert (NULL != add);
	/* find first null byte */
	nul = -1;
	for (i = 0; (i < len) && (nul < 0); i++) {
		if (0 == *(buf + i)) {
			nul = i;
		}
	}
	/* check if buffer is full */
	if (nul == len - 1) {
		return XBFalse;
	}
	/* check if there is a null byte */
	if (nul < 0) {
		Dbg_Out ("Buffer has no null byte!\n");
		memset (buf + len - 1, 0, 1);
		return XBFalse;
	}
	/* check if add string fits */
	write = strlen (add);
	if (nul + write < len) {
		memcpy (buf + nul, add, write + 1);
		return XBTrue;
	}
	/* no, does not fit */
	memset (buf + nul, (unsigned char)"x", len - nul - 1);
	memset (buf + len - 1, 0, 1);
	return XBFalse;
}								/* AddStringToBuffer */

/*
 * return a string for entry in given section
 */
char *
DB_SectionEntryString (const DBSection * section, XBAtom atom)
{
	static char buffer[100];
	const char *tmp;
	const DBEntry *ptr;
	/* init buffer */
	memset (buffer, 0, 1);
	/* get section name */
	AddStringToBuffer (buffer, sizeof (buffer), "[");
	assert (NULL != section);
	tmp = GUI_AtomToString (section->atom);
	if (NULL == tmp) {
		tmp = "???";
	}
	AddStringToBuffer (buffer, sizeof (buffer), tmp);
	AddStringToBuffer (buffer, sizeof (buffer), "] ");
	/* get entry name */
	tmp = GUI_AtomToString (atom);
	if (NULL == tmp) {
		tmp = "???";
	}
	AddStringToBuffer (buffer, sizeof (buffer), tmp);
	AddStringToBuffer (buffer, sizeof (buffer), "=");
	/* find entry value */
	ptr = GetEntry (section, atom);
	if (NULL == ptr) {
		tmp = "<none>";
	}
	else {
		tmp = ptr->value;
	}
	AddStringToBuffer (buffer, sizeof (buffer), tmp);
	return buffer;
}								/* DB_PrintEntry */

/*
 * delete an entry from section
 */
void
DB_DeleteEntry (DBSection * section, XBAtom atom)
{
	DBEntry *save, *ptr;
	assert (section != NULL);
	section->changed = XBTrue;
	/* do we have any entries ? */
	if (NULL == section->entry) {
		return;
	}
	assert (ATOM_INVALID != atom);
	/* it is the first entry ? */
	if (section->entry->atom == atom) {
		save = section->entry;
		section->entry = section->entry->next;
		FreeEntry (save);
	}
	else {
		for (ptr = section->entry; ptr->next != NULL; ptr = ptr->next) {
			if (ptr->next->atom == atom) {
				save = ptr->next;
				ptr->next = ptr->next->next;
				FreeEntry (save);
				break;
			}
		}
	}
}								/* DB_DeleteEntry */

/************************
 * conversion functions *
 ************************/

/*
 * translate an integer value into its conversion string
 */
const char *
DB_IntToString (const DBToInt * table, int value)
{
	const DBToInt *ptr;
	assert (NULL != table);
	for (ptr = table; ptr->key != NULL; ptr++) {
		if (ptr->value == value) {
			return ptr->key;
		}
	}
	return NULL;
}								/* DB_IntToString */

/*
 * translate a data pointer into its conversion string
 */
const char *
DB_DataToString (const DBToData * table, void *pValue)
{
	const DBToData *ptr;
	assert (NULL != table);
	for (ptr = table; ptr->key != NULL; ptr++) {
		if (ptr->value == pValue) {
			return ptr->key;
		}
	}
	return NULL;
}								/* DB_DataToString */

/*******************
 * file operations *
 *******************/

/*
 * load given database from file
 */
XBBool
DB_Load (DBRoot * db)
{
	FILE *fp;
	DBSection *section;
	char line[256];
	const char *sectionName;
	const char *entryName;
	assert (db != NULL);
	assert (ATOM_INVALID != db->atom);
	/* try to open file for reading */
	if (NULL == (fp = OpenDBFile (db, "r"))) {
		return XBFalse;
	}
	/* parse it */
	section = NULL;
	while (NULL != fgets (line, sizeof (line), fp)) {
		if (NULL != (sectionName = ParseSection (line))) {
			section = DB_CreateSection (db, GUI_StringToAtom (sectionName));
		}
		else if (NULL != section && NULL != (entryName = ParseEntryName (line))) {
			(void)DB_CreateEntryString (section, GUI_StringToAtom (entryName),
										ParseEntryValue (line));
		}
	}
	/* mark all as unchanged */
	MarkUnchanged (db);
	/* that's all */
	fclose (fp);
	return XBTrue;
}								/* DB_Load */

/*
 * store database in file
 */
XBBool
DB_Store (DBRoot * db)
{
	FILE *fp;
	const DBSection *section;
	const DBEntry *entry;
	/* try to open file for writing */
	if (NULL == (fp = OpenDBFile (db, "w"))) {
		return XBFalse;
	}
	/* now write it */
	for (section = db->section; section != NULL; section = section->next) {
		fprintf (fp, "[%s]\n", GUI_AtomToString (section->atom));
		for (entry = section->entry; entry != NULL; entry = entry->next) {
			fprintf (fp, "%s=%s\n", GUI_AtomToString (entry->atom), entry->value);
		}
		fprintf (fp, "\n");
	}
	/* mark all as unchanged */
	MarkUnchanged (db);
	/* that's all */
	fclose (fp);
	return XBTrue;
}								/* DB_Store */

/*
 * append database to file
 */
XBBool
DB_Append (DBRoot * db)
{
	FILE *fp;
	const DBSection *section;
	const DBEntry *entry;
	/* try to open for appending */
	if (NULL == (fp = OpenDBFile (db, "a"))) {
		return XBFalse;
	}
	/* now write it */
	for (section = db->section; section != NULL; section = section->next) {
		fprintf (fp, "[%s]\n", GUI_AtomToString (section->atom));
		for (entry = section->entry; entry != NULL; entry = entry->next) {
			fprintf (fp, "%s=%s\n", GUI_AtomToString (entry->atom), entry->value);
		}
		fprintf (fp, "\n");
	}
	/* mark all as unchanged */
	MarkUnchanged (db);
	/* that's all */
	fclose (fp);
	return XBTrue;
}								/* DB_Append */

/*
 * dump database to stderr
 */
XBBool
DB_Dump (const DBRoot * db)
{
	const DBSection *section;
	const DBEntry *entry;
	/* now write it */
	fprintf (stderr, "---database dump----\n");
	for (section = db->section; section != NULL; section = section->next) {
		fprintf (stderr, "[%s]\n", GUI_AtomToString (section->atom));
		for (entry = section->entry; entry != NULL; entry = entry->next) {
			fprintf (stderr, "%s=%s\n", GUI_AtomToString (entry->atom), entry->value);
		}
		fprintf (stderr, "\n");
	}
	fprintf (stderr, "---end of database dump---\n");
	/* that's all */
	return XBTrue;
}								/* DB_Dump */

/*
 * load complete directory of files into database,
 */
XBBool
DB_LoadDir (DBRoot * db, const char *path, const char *ext, DBType type, XBAtom timeAtom,
			XBAtom section, DBLoadDirFunc func, XBBool rec)
{
	XBBool changed;
	XBDir *fileList;
	XBDir *ptr;
	/* sanity checks */
	assert (NULL != db);
	assert (NULL != path);
	assert (NULL != ext);
	/* load database from file */
	DB_Load (db);
	/* now compare with current directory contents */
	changed = XBFalse;
	fileList = CreateFileList (path, ext, rec);
	for (ptr = fileList; NULL != ptr; ptr = ptr->next) {
		if (!CheckInsertFile (db, ptr->name, timeAtom, ptr->mtime)) {
			InsertFile (db, type, ptr->name, timeAtom, ptr->mtime, section, func);
			changed = XBTrue;
#ifdef DEBUG
			putchar ('*');
#endif
		}
		else {
			UpdateFile (db, ptr->name);
#ifdef DEBUG
			putchar ('.');
#endif
		}
#ifdef DEBUG
		fflush (stdout);
#endif
	}
	/* now delete "unchanged section", because they do not have any related files */
	if (DeleteUnchangedSections (db)) {
		changed = XBTrue;
	}
	if (NULL != fileList) {
		DeleteFileList (fileList);
#ifdef DEBUG
		putchar ('\n');
#endif
	}
	return changed;
}								/* DB_LoadDir */

/*
 * read message of the day, return line count
 */
int
ReadMessageOfTheDay (int m, char *s, int *l)
{
	FILE *fp;
	int i;
	char *c, *d;
	i = 0;
	/* try to open the file */
	fp = FileOpen (db_path[3], "message", "txt", "r");
	if (NULL != fp) {
		c = s;
		i = 0;
		l[i] = 0;
		while (1) {
			/* get character */
			d = fgets (c, m - l[i], fp);
			if (d == NULL) {
				break;
			}
			i++;
			l[i] = strlen (s) - 1;
			c = s + strlen (s) - 1;
		}
		l[i + 1] = strlen (s);
	}
	return i;
}								/* ReadmessageOfTheDay */

/*
 * clean up databases
 */
void
DB_Finish (void)
{
	DBType t;
	for (t = DT_Config; t < NUM_DT; t++) {
		while (NULL != db_list[t]) {
			DB_Delete (db_list[t]);
		}
		db_list[t] = NULL;
	}
}								/* FinishControl */

/*
 * end of file ini_file.c
 */
