/*-*-c++-*-
 * $Id: cconfigmanager.cpp,v 1.1 2002/01/28 15:38:32 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (let@users.sourceforge.net)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#include "cconfigmanager.h"

#include <stdio.h>
#include <stdlib.h>
#ifndef WIN32
#include <regex.h>
#include <unistd.h>
#include <pwd.h>
#else
#include <qdir.h>
#endif
#include <assert.h>
#include <sys/types.h>
#include <iostream>

using namespace std;

/* Config file version */
#define CFG_VERSION "0"

CConfigManager::CConfigManager(QObject* parent, const char* name) : QObject(parent, name)
{
}

CConfigManager::~CConfigManager()
{
	clear();
}

void
CConfigManager::clear()
{
	// free allocated maps
	for (map<QString, map<QString, QString>*>::iterator it = oMap.begin(); it != oMap.end(); ++it) {
		delete (*it).second;
	}
}

QString
CConfigManager::homeDir() const
{
#ifdef WIN32
	// On Windows, we use the Registry anyway ...
	return QDir::homeDirPath();
#else
	uid_t uid = getuid();
	passwd* pwd = getpwuid(uid);

	return (pwd != 0) ? pwd->pw_dir : "";
#endif
}

bool
CConfigManager::setFile(const QString& fname)
{
	bool retval = true;

#ifdef WIN32
	HKEY kHandle;

	if (RegOpenKeyEx(MY_KEYROOT, MY_KEYHOME, 0, KEY_READ, &kHandle) != ERROR_SUCCESS)
		retval = false;
	else {
		QString category;
		unsigned char kValue[1024];
		unsigned long lValue = sizeof(kValue);
		unsigned long kType = REG_SZ;

		if ((RegQueryValueEx(kHandle, "Version", NULL, &kType, kValue, &lValue) ==
			ERROR_SUCCESS) && (kType == REG_SZ)) {
			if (::strcmp(CFG_VERSION, (const char *)kValue) == 0) {
				unsigned long kIndex = 0;
				unsigned char kName[1024];
				unsigned long lName = sizeof(kName);
				FILETIME tStamp;

				// Enumerate SubKeys (aka our "sections")
				while (RegEnumKeyEx(kHandle, kIndex++, (char *)kName, &lName,
									0, 0, 0, &tStamp) == ERROR_SUCCESS) {
					category = (const char *)kName;
					HKEY subHandle;

					// Open a SubKey
					if (RegOpenKeyEx(kHandle, (const char *)kName, 0, KEY_READ,
									 &subHandle) == ERROR_SUCCESS) {
						unsigned long subIndex = 0;
						unsigned char kSubName[1024];
						unsigned long lSubName = sizeof(kSubName);

						lValue = sizeof(kValue);
						kType = REG_SZ;
						// Enumerate values in the Subkey
						while (RegEnumValue(subHandle, subIndex++, (char *)kSubName, &lSubName, 0,
											&kType, kValue, &lValue) == ERROR_SUCCESS) {
							if (kType == REG_SZ) { // We only use keys of type String
								// When we got here, we have the key name in kSubName and
								// the corresponding value in kValue.

								map<QString, map<QString, QString>* >::iterator it = oMap.find(category);
								map<QString, QString>* mm = 0;
								if (it == oMap.end()) {
									mm = new map<QString, QString>;
								} else {
									mm = (*it).second;
								}
								(*mm)[QString((const char *)kSubName)] =
									QString((const char *)kValue);
								oMap[category] = mm;
							}
							lValue = sizeof(kValue);
							lSubName = sizeof(kSubName);
							kType = REG_SZ;
						}
						RegCloseKey(subHandle);
					}
					lName = sizeof(kName);
				}
			}
		}
		RegCloseKey(kHandle);
	}
#else
	oFile = fname;
	
	FILE* fptr = fopen(fname.latin1(), "r");
	if (fptr != 0) {
		char* line = 0;
		size_t len;
		int i;
		QString category;
		regex_t reg;
		regmatch_t sub[3];
		const int subMax = 3;
		int count = 0; // line counter
		
		// retrieve version number
		int ret = regcomp(&reg, "VERSION\\([ \t]*([0-9]+)[ \t]*\\)", REG_EXTENDED);
		assert(ret == 0);
		while (getline(&line, &len, fptr) != -1) {
			count++;
			ret = regexec(&reg, line, subMax, sub, 0);
			if (ret == 0) {
				QString str(line);
				ret = strcmp(CFG_VERSION, str.mid(sub[1].rm_so, sub[1].rm_eo - sub[1].rm_so).latin1());
				break;
			}
		}
		regfree(&reg);
		if (ret != 0) { // don't read an incompatible file, but use it for writing
			fclose(fptr);
			return true;
		}
		
		ret = regcomp(&reg, "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]+)\"", REG_EXTENDED);
		assert(ret == 0);
		
		while (getline(&line, &len, fptr) != -1) {
			count++;
			QString str(line);
			str = str.stripWhiteSpace();
			if (str.length() < 6) {
				continue;
			}
			if (str[0].latin1() == '[') { // new category
				category = "";
				int n = str.length();
				for (i = 1; i < n; i++) {
					if (str[i].latin1() == ']') {
						break;
					}
					category += str[i];
				}
			}
			else {
				if (category.length() == 0) {
					cerr << tr("Error in") << " " << fname << ": " << tr("Config item without category") << endl;
					continue;
				}
				
				ret = regexec(&reg, str.latin1(), subMax, sub, 0);
				if (ret != 0) {
					cerr << tr("Error in") << " " << fname << " " << tr("line") << ": " << count << endl;
					continue;
				}
				
				QString name = str.mid(sub[1].rm_so, sub[1].rm_eo - sub[1].rm_so);
				QString value = str.mid(sub[2].rm_so, sub[2].rm_eo - sub[2].rm_so);
				
				map<QString, map<QString, QString>* >::iterator it = oMap.find(category);
				map<QString, QString>* mm = 0;
				if (it == oMap.end()) {
					mm = new map<QString, QString>;
				}
				else {
					mm = (*it).second;
				}

				(*mm)[name] = value;
				oMap[category] = mm;
			}
		}
		
		free(line);
		regfree(&reg);
		fclose(fptr);
	}
	else {
		retval = false;
	}
#endif
	
	return retval;
}

bool
CConfigManager::saveConfig()
{
	bool retval = true;
#ifdef WIN32
	HKEY kHandle;

	if (RegCreateKey(MY_KEYROOT, MY_KEYHOME, &kHandle) != ERROR_SUCCESS)
		retval = false;
	else {
		if (RegSetValueEx(kHandle, "Version", 0, REG_SZ,
						  (const unsigned char *)CFG_VERSION, strlen(CFG_VERSION)+1) ==
			ERROR_SUCCESS) {

			map<QString, map<QString, QString>* >::iterator dit = oMap.begin();
			int count = 1;

			while (retval && (dit != oMap.end())) {
				HKEY subHandle;
				map<QString, QString>* mm = (*dit).second;
				map<QString, QString>::iterator mit = mm->begin();
				count++;
			
				QString cat = (*dit).first;
				if (RegCreateKey(kHandle, cat.latin1(), &subHandle) != ERROR_SUCCESS) {
					retval = false;
					break;
				}		
				while (mit != mm->end()) {
					const char *value = (*mit).second.latin1();
					if (!value)
						value = "";
					if (RegSetValueEx(subHandle, (*mit).first.latin1(), 0, REG_SZ,
									  (const unsigned char *)value,
									  strlen(value)+1) != ERROR_SUCCESS) {
						retval = false;
						break;
					}
					++mit;
				}
				RegCloseKey(subHandle);
				++dit;
			}
		} else
			retval = false;
	}
	RegCloseKey(kHandle);
#else
	FILE* fptr = fopen(oFile.latin1(), "w");
	
	if (fptr != 0) {
		map<QString, map<QString, QString>* >::iterator dit = oMap.begin();
		int count = 1;
		
		// write version string
		fputs("VERSION(" CFG_VERSION ")\n\n", fptr);
		
		while (dit != oMap.end()) {
			map<QString, QString>* mm = (*dit).second;
			map<QString, QString>::iterator mit = mm->begin();
			count++;
			
			QString cat = (*dit).first;
			cat.prepend("[");
			if (count > 1) {
				cat.prepend("\n");
			}
			cat.append("]\n");
			fwrite(cat.latin1(), 1, cat.length(), fptr);
			
			while (mit != mm->end()) {
				cat = (*mit).first + " = \"" + (*mit).second + "\"\n";
				fwrite(cat.latin1(), 1, cat.length(), fptr);
				++mit;
			}
			++dit;
		}
		
		fclose(fptr);
	}
	else {
		retval = false;
	}
#endif

	return retval;
}

void
CConfigManager::setItem(const QString& category, const QString& name, const QString& value)
{
	map<QString, map<QString, QString>*>::iterator it = oMap.find(category);
	map<QString, QString>* mm = 0;
	
	if (it == oMap.end()) {
		mm = new map<QString, QString>;
	}
	else {
		mm = (*it).second;
	}
	(*mm)[name] = value;
	oMap[category] = mm;
}

void
CConfigManager::setItem(const QString& category, const QString& name, int value)
{
	map<QString, map<QString, QString>*>::iterator it = oMap.find(category);
	map<QString, QString>* mm = 0;
	
	if (it == oMap.end()) {
		mm = new map<QString, QString>;
	}
	else {
		mm = (*it).second;
	}
	(*mm)[name] = QString::number(value);
	oMap[category] = mm;
}

QString
CConfigManager::item(const QString& category, const QString& name, const QString& def) const
{
	map<QString, map<QString, QString>*>::const_iterator it = oMap.find(category);
	
	if (it == oMap.end()) {
		return def;
	}
	
	if ((*it).second->find(name) != (*it).second->end()) {
		return (*(*it).second)[name];
	}
	
	return def;
}

// this is redundant...
int
CConfigManager::item(const QString& category, const QString& name, int def) const
{
	map<QString, map<QString, QString>*>::const_iterator it = oMap.find(category);
	
	if (it == oMap.end()) {
		return def;
	}
	
	if ((*it).second->find(name) != (*it).second->end()) {
		return (*(*it).second)[name].toInt();
	}
	
	return def;
}
