/** \file partcatalog.c
* Manage the catalog of track parameter files
*/
/*  XTrackCAD - Model Railroad CAD
*  Copyright (C) 2019 Martin Fischer
*
*  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 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "dynstring.h"
#include "fileio.h"
#include "include/levenshtein.h"
#include "misc.h"
#include "include/paramfile.h"
#include "include/partcatalog.h"
#include "paths.h"
#include "include/stringxtc.h"
#include "include/utf8convert.h"
#include "include/utlist.h"

#define PUNCTUATION "+-*/.,&%=#"
#define SEARCHDELIMITER " \t\n\r/"
#define LDISTANCELIMIT (2)

static char *stopwords = {
	"scale",
};

static int log_params;

/**
 * Create and initialize the linked list for the catalog entries
 *
 * \return pointer to first element
 */

Catalog *
InitCatalog(void)
{
	Catalog *newCatalog = MyMalloc(sizeof(Catalog));
	if (newCatalog) {
		newCatalog->head = NULL;
	}
	return (newCatalog);
}

/**
 * Destroys the catalog
 *
 * \param [in] catalog
 */

void
DestroyCatalog(Catalog *catalog)
{
	CatalogEntry *current = catalog->head;
	CatalogEntry *entry = NULL;
	CatalogEntry *tmp = NULL;
//	CatalogEntry *old = NULL;
	DL_FOREACH_SAFE(current, entry, tmp) {
		//if (old) MyFree(old);
//		old = NULL;
		for (unsigned int i = 0; i < entry->files; i++) {
			MyFree(entry->fullFileName[i]);
			entry->fullFileName[i] = NULL;
		}
		entry->files = 0;
		MyFree(entry->contents);
		entry->contents = NULL;
		MyFree(entry->tag);
		entry->tag = NULL;
//		old = entry;
		DL_DELETE(catalog->head,entry);
	}

	catalog->head = NULL;
}

#if 0
/**
 * Create a new CatalogEntry and add it to the linked list. The newly
 * created entry is inserted into the list after the given position
 *
 * \param entry IN insertion point
 * \return pointer to new entry
 */

static CatalogEntry *
InsertIntoCatalogAfter(CatalogEntry *entry)
{
	CatalogEntry *newEntry = (CatalogEntry *)MyMalloc(sizeof(CatalogEntry));
	newEntry->next = entry->next;
	newEntry->prev = entry;
	entry->next = newEntry;
	newEntry->files = 0;
	newEntry->contents = NULL;
	newEntry->tag = NULL;

	return (newEntry);
}
#endif

/**
 * Count the elements in the linked list
 *
 * \param catalog IN
 * \return the number of elements
 */

unsigned
CountCatalogEntries(Catalog *catalog)
{
	CatalogEntry * entry;
	unsigned count = 0;
	DL_COUNT(catalog->head, entry, count);
	return (count);
}

/**
 * Empty a catalog. All data nodes including their allocated memory are freed. On
 *
 * \param listHeader IN the list
 */

EXPORT void
CatalogDiscard(Catalog *catalog)
{
	CatalogEntry *current = catalog->head;
	CatalogEntry *element;
	CatalogEntry *tmp;
//  CatalogEntry *old = NULL;

	DL_FOREACH_SAFE(current, element, tmp) {
		//if (old) MyFree(old);
//  	old = NULL;
		MyFree(element->contents);
		element->contents = NULL;
		MyFree(element->tag);
		element->tag = NULL;
		for (unsigned int i = 0; i < element->files; i++) {
			MyFree(element->fullFileName[i]);
			element->fullFileName[i] = NULL;
		}
		element->files = 0;
//      old = element;
		DL_DELETE(catalog->head,element);
	}

	catalog->head = NULL;
}

/**
 * Compare entries
 *
 * \param [in] a If non-null, a CatalogEntry to compare.
 * \param [in] b If non-null, a CatalogEntry to compare.
 *
 * \returns An int.
 */

static int
CompareEntries(CatalogEntry *a, CatalogEntry *b)
{
	return XtcStricmp(a->contents, b->contents);
}

/**
 * Create a new CatalogEntry and insert it keeping the list sorted
 *
 * \param [in] catalog
 * \param [in] contents to include.
 * \param [in] tag
 *
 * \returns CatalogEntry
 */

EXPORT CatalogEntry *
InsertInOrder(Catalog *catalog, const char *contents, const char *tag)
{
	CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry));
	newEntry->files = 0;

	if (contents) {
		newEntry->contents = MyStrdup(contents);
	}
	if (tag) {
		newEntry->tag = MyStrdup(tag);
	}

	DL_INSERT_INORDER(catalog->head, newEntry, CompareEntries);

	return newEntry;
}

/**
 * Find an existing list element for a given content
 *
 * \param [in] catalog
 * \param [in] contents contents to search.
 * \param [in] silent   we log error messages or not.
 *
 * \returns CatalogEntry if found, NULL otherwise.
 */

static CatalogEntry *
IsExistingContents(Catalog *catalog, const char *contents, BOOL_T silent)
{
	CatalogEntry *head = catalog->head;
	CatalogEntry *currentEntry;

	DL_FOREACH(head, currentEntry) {
		if (!XtcStricmp(currentEntry->contents, contents)) {
			if (!silent) {
				printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]);
			}
			return (currentEntry);
		}
	}
	return (NULL);
}

/**
 * Store information about a parameter file. The filename is added to the array
 * of files with identical content. If not already present, in addition
 * the content is stored
 *
 * \param entry existing entry to be updated
 * \param path filename to add
 * \param contents contents description
 */

EXPORT void
UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents, char *tag)
{
	if (!entry->contents) {
		MyFree(entry->contents);
		entry->contents = NULL;
	}
	if (contents) {
		entry->contents = MyStrdup(contents);
	}

	if (!entry->tag) {
		MyFree(entry->tag);
		entry->tag = NULL;
	}
	if (tag) {
		entry->tag = MyStrdup(tag);
	}

	CHECK( entry->files < MAXFILESPERCONTENT );
	entry->fullFileName[entry->files++] = MyStrdup(path);
}

/**
 * Scan opened directory for the next parameter file
 *
 * \param dir IN opened directory handle
 * \param dirName IN name of directory
 * \param fileName OUT fully qualified filename, must be free()'d by caller
 *
 * \return TRUE if file found, FALSE if not
 */

static bool
GetNextParameterFile(DIR *dir, const char *dirName, char **fileName)
{
	bool done = false;
	bool res = false;

	/*
	* get all files from the directory
	*/
	while (!done) {
		struct stat fileState;
		struct dirent *ent;

		ent = readdir(dir);

		if (ent) {
			if (!XtcStricmp(FindFileExtension(ent->d_name), "xtp")) {
				/* create full file name and get the state for that file */
				MakeFullpath(fileName, dirName, ent->d_name, NULL);

				if (stat(*fileName, &fileState) == -1) {
					fprintf(stderr, "Error getting file state for %s\n", *fileName);
					continue;
				}

				/* ignore any directories */
				if (!(fileState.st_mode & S_IFDIR)) {
					done = true;
					res = true;
				}
			}
		} else {
			done = true;
			res = false;
		}
	}
	return (res);
}

/*!
 * Filter keywords. Current rules:
 *	- single character string that only consist of a punctuation char
 *
 * \param word IN keyword
 * \return true if any rule applies, false otherwise
 */

bool
FilterKeyword(char *word)
{
	if (strlen(word) == 1 && strpbrk(word, PUNCTUATION)) {
		return (true);
	}

	for (int i = 0; i < sizeof(stopwords) / sizeof(char *); i++) {
		if (!XtcStricmp(word, stopwords+i)) {
			return (true);
		}
	}
	return (false);
}

int KeyWordCmp(void *a, void *b)
{
	return XtcStricmp(((IndexEntry *)a)->keyWord,((IndexEntry *)b)->keyWord);
}


/**
 * Standardize spelling: remove some typical spelling problems. It is assumed that the word
 * has already been converted to lower case
 *
 * \param [in,out] word If non-null, the word.
 */

void
StandardizeSpelling(char *word)
{
	char *p = strchr(word, '-');
	// remove the word 'scale' from combinations like N-scale
	if (p) {
		if (!XtcStricmp(p+1, "scale")) {
			*p = '\0';
		}
	}

	if (!strncasecmp(word, "h0", 2)) {
		strcpy(word, "ho");
	}

	if (!strncasecmp(word, "00", 2)) {
		strcpy(word, "oo");
	}

	if (word[0] == '0') {
		word[0] = 'o';
	}
}

/**
 * Create the keyword index from a list of parameter files
 *
 * \param [in] library initialized library
 *
 * \returns number of indexed keywords.
 */

static unsigned
CreateKeywordIndex(ParameterLib *library)
{
	CatalogEntry *listOfEntries = library->catalog->head;
	CatalogEntry *curParamFile;
	size_t totalMemory = 0;
	size_t wordCount = 0;
	char *wordList;
	char *wordListPtr;
	IndexEntry *index = library->index;

	// allocate a  buffer for the complete set of keywords
	DL_FOREACH(listOfEntries, curParamFile) {
		totalMemory += strlen(curParamFile->contents) + 1;
	}
	wordList = MyMalloc((totalMemory + 1) * sizeof(char));

	wordListPtr = wordList;

	DL_FOREACH(listOfEntries, curParamFile) {
		char *word;
		char *content = strdup(curParamFile->contents);

		word = strtok(content, SEARCHDELIMITER);
		while (word) {
			strcpy(wordListPtr, word);

			XtcStrlwr(wordListPtr);
			if (!FilterKeyword(wordListPtr)) {
				IndexEntry *searchEntry = MyMalloc(sizeof(IndexEntry));
				IndexEntry *existingEntry = NULL;
				searchEntry->keyWord = wordListPtr;
				StandardizeSpelling(wordListPtr);

				if (index) {
					DL_SEARCH(index, existingEntry, searchEntry, KeyWordCmp);
				}
				if (existingEntry) {
					DYNARR_APPEND(CatalogEntry *, *(existingEntry->references), 5);
					DYNARR_LAST(CatalogEntry *, *(existingEntry->references)) = curParamFile;
					MyFree(searchEntry);
				} else {
					searchEntry->references = calloc(1, sizeof(dynArr_t));
					DYNARR_APPEND(CatalogEntry *, *(searchEntry->references), 5);
					DYNARR_LAST(CatalogEntry *, *(searchEntry->references)) = curParamFile;
					DL_APPEND(index, searchEntry);
					LOG1(log_params, ("Index Entry: <%s>\n", searchEntry->keyWord))
				}

				wordListPtr += strlen(word) + 1;
				wordCount++;
			}
			word = strtok(NULL, SEARCHDELIMITER);
		}
		free(content);
	}
	*wordListPtr = '\0';

	DL_SORT(index, KeyWordCmp);
	library->index = index;
	library->words = wordList;

	IndexEntry *existingEntry;
	DL_FOREACH(index, existingEntry) {
		LOG1(log_params, ("Index Entry: <%s> Count: %d\n", existingEntry->keyWord,
		                  existingEntry->references->cnt));
	}
	return (unsigned)(wordCount);
}

/**
 * Search the index for a keyword. The index is assumed to be sorted. Each
 * keyword has one entry in the index list.
 *
 * \param [in]  index   index list.
 * \param 	    length  number of entries index.
 * \param [in]  search  search string.
 * \param [out] entries array of found entry.
 *
 * \returns TRUE if found, FALSE otherwise.
 */

unsigned int
FindWord(IndexEntry *index, int length, char *search, IndexEntry **entries)
{
	IndexEntry *result = NULL;

	IndexEntry searchWord;
	searchWord.keyWord = search;

	*entries = NULL;

	DL_SEARCH(index, result, &searchWord, KeyWordCmp);
	if (!result) {
		int maxdistance = 1;
		while (maxdistance <= LDISTANCELIMIT && !result ) {
			IndexEntry *current;
//			size_t minDistance = LDISTANCELIMIT + 1;
			int maxProbability = 0;
			LOG1(log_params, ("Close match for: <%s> maxdistance: %d\n", search,
			                  maxdistance));

			DL_FOREACH(index, current) {
				size_t ldist = levenshtein(search, current->keyWord);
				LOG1(log_params, ("Distance of: <%s> is %d\n", current->keyWord, ldist));
				if (ldist == maxdistance) {
					if (current->references->cnt > maxProbability) {
						if (!result) {
							result = MyMalloc(sizeof(IndexEntry));
						}
						memcpy(result, current, sizeof(IndexEntry));
						maxProbability = current->references->cnt;
					}
				}
			}

			maxdistance++;
		}
	}

	*entries = result;
	return (result != NULL);
}

/**
 * Create and initialize the data structure for the track library
 *
 * \param trackLibrary OUT the newly allocated track library
 * \return TRUE on success
 */

ParameterLib *
InitLibrary(void)
{
	ParameterLib *trackLib = MyMalloc(sizeof(ParameterLib));

	if (trackLib) {
		trackLib->catalog = InitCatalog();
		trackLib->index = NULL;
		trackLib->wordCount = 0;
		trackLib->parameterFileCount = 0;
	}

	return (trackLib);
}

/**
 * Destroys the library freeing all associated memory
 *
 * \param [in] library If non-null, the library.
 */

void
DestroyLibrary(ParameterLib *library)
{
	if (library) {
		DestroyCatalog(library->catalog);
		MyFree(library);
	}
}

/**
 * Scan directory and add all parameter files found to the catalog
 *
 * \param trackLib IN the catalog
 * \param directory IN directory to scan
 * \return number of files found
 */

bool
CreateCatalogFromDir(ParameterLib *paramLib, char *directory)
{
	DIR *d;
	Catalog *catalog = paramLib->catalog;

	d = opendir(directory);
	if (d) {
		char *fileName = NULL;

		while (GetNextParameterFile(d, directory, &fileName)) {
			CatalogEntry *existingEntry;

			char *contents = GetParameterFileContent(fileName);

			char *scale = GetParameterFileScale(fileName);


			if ((existingEntry = IsExistingContents(catalog, contents, FALSE))) {
				UpdateCatalogEntry(existingEntry, fileName, contents, scale);
			} else {
				CatalogEntry *newEntry;
				newEntry = InsertInOrder(catalog, contents, scale);
				UpdateCatalogEntry(newEntry, fileName, contents, scale);
			}
			MyFree(contents);
			MyFree(scale);
			free(fileName);
			fileName = NULL;
		}
		closedir(d);
	}
	paramLib->parameterFileCount = CountCatalogEntries(paramLib->catalog);
	return (paramLib->parameterFileCount);
}

/**
 * Discard the complete catalog from a library
 *
 * \param [in] library
 */

void
DiscardCatalog(ParameterLib *library)
{
	CatalogEntry *entry;
	CatalogEntry *temp;

	DL_FOREACH_SAFE(library->catalog->head, entry, temp) {
		MyFree(entry->contents);
		MyFree(entry->tag);
		for (unsigned int i = 0; i < entry->files; i++) {
			MyFree(entry->fullFileName[i]);
		}
		DL_DELETE(library->catalog->head, entry);
		MyFree(entry);
	}

}


/**
 * Create the search index from the contents description for the whole
 * catalog.
 *
 * \param [in] parameterLib IN the catalog.
 *
 * \returns the number of words indexed.
 */

unsigned
CreateLibraryIndex(ParameterLib *parameterLib)
{
	parameterLib->index = NULL;

	parameterLib->wordCount = CreateKeywordIndex(parameterLib);

	return (parameterLib->wordCount);
}

/**
 * Discard library index freeing all memory used
 * references were created using MakeFullPath. These were allocated using malloc and
 * not MyMalloc
 *
 * \param [in] trackLib the track library.
 */

void
DiscardLibraryIndex(ParameterLib *trackLib)
{
	IndexEntry *indexEntry;
	IndexEntry *tmp;

	DL_FOREACH_SAFE(trackLib->index, indexEntry, tmp) {
		DYNARR_FREE(CatalogEntry *, *(indexEntry->references));
		free(indexEntry->references);
		DL_DELETE(trackLib->index, indexEntry);
		MyFree(indexEntry);
	}

	MyFree(trackLib->words);
	trackLib->index = NULL;
	trackLib->wordCount = 0;
}


/**
 * Create the library and index of parameter files in a given directory.
 *
 * \param directory IN directory to scan
 * \return  NULL if error or empty directory, else library handle
 */

ParameterLib *
CreateLibrary(char *directory)
{
	ParameterLib *library;

	log_params = LogFindIndex("params");

	library = InitLibrary();
	if (library) {
		if (!CreateCatalogFromDir(library, directory)) {
			return (NULL);
		}

		CreateLibraryIndex(library);
	}
	return (library);
}

/**
 * Discard library freeing all memory used
 *
 * \param [in,out] library If non-null, the library.
 */

void
DiscardLibrary(ParameterLib* library)
{
	CatalogEntry *entry = library->catalog->head;
	CatalogEntry *element;
	CatalogEntry *tmp;

	DiscardLibraryIndex(library);

	DL_FOREACH_SAFE(entry, element, tmp) {
		MyFree(element->contents);
		MyFree(element->tag);
		for (unsigned int i = 0; i < element->files; i++) {
			MyFree(element->fullFileName[i]);
		}
		DL_DELETE(entry, element);
		MyFree(element);
	}
	MyFree(library->words);
	MyFree(library);
}

/**
 * Create a statistic for a finished search. The returned string has to be MyFreed() after usage
 *
 * \param [in] result the finished search
 *
 * \returns Null if it fails, else the found statistics.
 */

char *
SearchStatistics(SearchResult *result)
{
	DynString buffer;
	DynString subStats[STATE_COUNT];

	unsigned searched = 0;
	unsigned discarded = 0;
	unsigned notfound = 0;
	unsigned close = 0;

	char *resStat;
	DynStringMalloc(&buffer, 16);

	for (int i = SEARCHED; i < STATE_COUNT; i++) {
		DynStringMalloc(subStats + i, 16);
	}

	DynStringCatCStr(subStats + SEARCHED, _("Found: "));
	DynStringCatCStr(subStats + CLOSE, _("Similar: "));
	DynStringCatCStr(subStats + DISCARDED, _("Ignored: "));
	DynStringCatCStr(subStats + NOTFOUND, _("Not found: "));

	for (unsigned int i = 0; i < result->words; i++) {
		switch (result->kw[i].state) {
		case SEARCHED:
			DynStringPrintf(&buffer, "%s (%d) ", result->kw[i].keyWord,
			                result->kw[i].count);
			searched++;
			break;
		case DISCARDED:
			DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
			discarded++;
			break;
		case NOTFOUND:
			DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
			notfound++;
			break;
		case CLOSE:
			DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord);
			close++;
			break;
		default:
			break;
		}
		DynStringCatStr(subStats + result->kw[i].state, &buffer);
	}

	DynStringReset(&buffer);
	if (searched) {
		DynStringCatStr(&buffer, subStats + SEARCHED);
	}
	if (close) {
		DynStringCatStr(&buffer, subStats + CLOSE);
	}
	if (notfound) {
		DynStringCatStr(&buffer, subStats + NOTFOUND);
	}
	if (discarded) {
		DynStringCatStr(&buffer, subStats + DISCARDED);
	}

	resStat = MyStrdup(DynStringToCStr(&buffer));
	DynStringFree(&buffer);
	for (int i = SEARCHED; i < STATE_COUNT; i++) {
		DynStringFree(subStats + i);
	}
	return (resStat);
}

/**
 * returns number of words in str.
 *
 * \param [in] str the string.
 *
 * \returns The total number of words.
 */

unsigned countWords(char *str)
{
	int state = FALSE;
	unsigned wc = 0;  // word count

	// Scan all characters one by one
	while (*str) {
		// If next character is a separator, set the
		// state as FALSE
		if (*str == ' ' || *str == '\n' || *str == '\t' || *str == '\r'
		    || *str == '/') {
			state = FALSE;
		}

		// If next character is not a word separator and
		// state is OUT, then set the state as IN and
		// increment word count
		else if (state == FALSE) {
			state = TRUE;
			++wc;
		}

		// Move to next character
		++str;
	}

	return wc;
}

/**
 * Search the library for a keyword string and return the result list
 *
 * Each key word exists only once in the index.
 *
 * \param library IN the library
 * \param searchExpression	IN keyword to search for
 * \param resultEntries IN list header for result list
 * \return number of found entries
 */

unsigned
SearchLibrary(ParameterLib *library, char *searchExpression,
              SearchResult *results)
{
	CatalogEntry *element;
	IndexEntry *entries;
//  unsigned entryCount = 0;
	char *searchWord;
	unsigned words = countWords(searchExpression);
	char *searchExp = MyStrdup(searchExpression);
	unsigned i = 0;

	if (library->index == NULL || library->wordCount == 0) {
		return (0);
	}

	results->kw = MyMalloc(words * sizeof(struct sSingleResult));
	results->subCatalog.head = NULL;

	searchWord = strtok(searchExp, SEARCHDELIMITER);
	while (searchWord) {
		XtcStrlwr(searchWord);
		if (!FilterKeyword(searchWord)) {
			StandardizeSpelling(searchWord);
			results->kw[i].state = SEARCHED;
		} else {
			results->kw[i].state = DISCARDED;
		}
		results->kw[i++].keyWord = MyStrdup(searchWord);
		searchWord = strtok(NULL, SEARCHDELIMITER);
	}
	results->words = words;

	i = 0;
	while (i < words) {
		if (results->kw[i].state == DISCARDED) {
			i++;
			continue;
		}
		FindWord(library->index, library->wordCount, results->kw[i].keyWord, &entries);
		if (entries) {
			results->kw[i].count = entries->references->cnt;
			if (XtcStricmp(results->kw[i].keyWord, entries->keyWord)) {
				results->kw[i].state = CLOSE;
				MyFree(results->kw[i].keyWord);
				results->kw[i].keyWord = MyStrdup(entries->keyWord);
			}

			if (results->subCatalog.head == NULL) {
				// if first keyword -> initialize result set
				for (int j = 0; j < entries->references->cnt; j++) {
					CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry));
					CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j);
					newEntry->contents = MyStrdup(foundEntry->contents);
					newEntry->tag = MyStrdup(foundEntry->tag);
					newEntry->files = foundEntry->files;
					for (unsigned int i=0; i<newEntry->files; i++) {
						newEntry->fullFileName[i] = MyStrdup(foundEntry->fullFileName[i]);
					}

					DL_APPEND(results->subCatalog.head, newEntry);
				}
			} else {
				// follow up keyword, create intersection with current result set
				CatalogEntry *current;
				CatalogEntry *temp;

				DL_FOREACH_SAFE(results->subCatalog.head, current, temp) {
					int found = 0;
					for (int j = 0; j < entries->references->cnt; j++) {
						CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j);

						if (strcmp(foundEntry->contents,current->contents)==0) {
							found = TRUE;
							break;
						}
					}
					if (!found) {
						DL_DELETE(results->subCatalog.head, current);
						MyFree(current->contents);
						MyFree(current->tag);
						for (unsigned int i=0; i<current->files; i++) {
							MyFree(current->fullFileName[i]);
						}
						MyFree(current);
					}
				}
			}
		} else {
			// Searches that don't yield a result are ignored
			results->kw[i].state = NOTFOUND;
			results->kw[i].count = 0;
		}
		i++;
	}

	DL_COUNT(results->subCatalog.head, element, results->totalFound);
	MyFree(searchExp);
	return (results->totalFound);
}

/**
 * Discard results. The memory allocated with the search is freed
 *
 * \param [in] res If non-null, the results.
 */

void
SearchDiscardResult(SearchResult *res)
{
	if (res) {
		CatalogEntry *current = res->subCatalog.head;
		CatalogEntry *element;
		CatalogEntry *tmp;

		DL_FOREACH_SAFE(current, element, tmp) {
			DL_DELETE(current, element);
			MyFree(element);
		}

		for (unsigned int i = 0; i < res->words; i++) {
			MyFree(res->kw[i].keyWord);
		}
		MyFree(res->kw);
	}
}

/**
 * Get the contents description from a parameter file. Returned string has to be MyFree'd after use.
 *
 * \param file IN xtpfile
 * \return pointer to found contents or NULL if not present
 */

char *
GetParameterFileContent(char *file)
{
	FILE *fh;
	char *result = NULL;

	fh = fopen(file, "rt");
	if (fh) {
		bool found = false;

		while (!found) {
			char buffer[512];
			if (fgets(buffer, sizeof(buffer), fh)) {
				char *ptr = strtok(buffer, " \t");
				if (!XtcStricmp(ptr, CONTENTSCOMMAND)) {
					/* if found, store the rest of the line and the filename	*/
					ptr = ptr+strlen(CONTENTSCOMMAND)+1;
					ptr = strtok(ptr, "\r\n");
					result = MyStrdup(ptr);
#ifdef UTFCONVERT
					ConvertUTF8ToSystem(result);
#endif // UTFCONVERT
					found = true;
				}
			} else {
				fprintf(stderr, "Nothing found in %s\n", file);
				found = true;
			}
		}
		fclose(fh);
	}
	return (result);
}

/**
 * Get the first scale values from a parameter file. Returned strings have to be MyFreed after use
 *
 * \param file IN xtpfile
 * \param array of one of three char results (Track, Structure and Car)
 */

char *
GetParameterFileScale(char *file)
{
	FILE *fh;
	char *scale = NULL;


	fh = fopen(file, "rt");
	if (fh) {
		bool found = FALSE, found_Turnout = FALSE, found_Structure = FALSE,
		     found_Car = FALSE;

		while (!found) {
			char buffer[512];
			if (fgets(buffer, sizeof(buffer), fh)) {
				char *ptr = strtok(buffer, " \t");
				if (!found_Turnout && !XtcStricmp(ptr, TURNOUTCOMMAND)) {
					/* if found, store the rest of the line and the filename	*/
					ptr = ptr+strlen(TURNOUTCOMMAND)+1;
					ptr = strtok(ptr, " \t");
					scale = MyMalloc(strlen(TURNOUTCOMMAND)+2+strlen(ptr));
					strcpy(scale,TURNOUTCOMMAND);
					char * cp = scale + strlen(TURNOUTCOMMAND);
					cp[0] = ' ';
					cp++;
					strcpy(cp,ptr);
					found_Turnout = true;
				} else if (!found_Structure && !XtcStricmp(ptr, STRUCTURECOMMAND)) {
					/* if found, store the rest of the line and the filename	*/
					ptr = ptr+strlen(STRUCTURECOMMAND)+1;
					ptr = strtok(ptr, " \t");
					scale = MyMalloc(strlen(STRUCTURECOMMAND)+2+strlen(ptr));
					strcpy(scale,STRUCTURECOMMAND);
					char * cp = scale + strlen(STRUCTURECOMMAND)+1;
					cp[-1] = ' ';
					strcpy(cp,ptr);
					found_Structure = true;
				} else if (!found_Car && !XtcStricmp(ptr, CARCOMMAND)) {
					/* if found, store the rest of the line and the filename	*/
					ptr = ptr+strlen(CARCOMMAND)+1;
					ptr = strtok(ptr, " \t");
					scale = MyMalloc(strlen(CARCOMMAND)+2+strlen(ptr));
					strcpy(scale,CARCOMMAND);
					char * cp = scale + strlen(CARCOMMAND)+1;
					cp[-1] = ' ';
					strcpy(cp,ptr);
					found_Car = true;
				} else if (!found_Car && !XtcStricmp(ptr, CARPROTOCOMMAND)) {
					/* if found, store the rest of the line and the filename	*/
					scale = MyMalloc(strlen(CARPROTOCOMMAND)+3);
					strcpy(scale,CARPROTOCOMMAND);
					char * cp = scale + strlen(CARPROTOCOMMAND);
					strcpy(cp," *");
					found_Car = true;
				}
			} else {
				if (!found_Turnout && !found_Structure && !found_Car) {
					fprintf(stderr, "Nothing found in %s\n", file);
					found = true;
				}
			}
			if (found_Turnout || found_Structure || found_Car) { found = TRUE; }
		}
		fclose(fh);
	}
	return scale;

}

#ifdef MEMWATCH
/** this is used to test for memory leaks. It should show no leaks from functions in this source file */
RunMemoryTest(char *directory)
{
	ParameterLib *library;
	SearchResult *results;

	mwInit();
	library = InitLibrary();
	if (library) {
		CreateCatalogFromDir(library, directory);
		CreateLibraryIndex(library);
		results = MyMalloc(sizeof(SearchResult));
		SearchLibrary(library, "peco", results);
		SearchDiscardResult(results);
		MyFree(results);
		DiscardLibraryIndex(library);
		DiscardCatalog(library);
	}
	DestroyLibrary(library);
	mwTerm();
}
#endif //MEMWATCH
