
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>

#include "wconfig.h"

#include "WINGs.h"

typedef struct W_UserDefaults {
	WMPropList *defaults;

	WMPropList *appDomain;

	WMPropList *searchListArray;
	WMPropList **searchList;	/* cache for searchListArray */

	char dirty;

	char dontSync;

	char *path;		/* where is db located */

	time_t timestamp;	/* last modification time */

	struct W_UserDefaults *next;

} UserDefaults;

static UserDefaults *sharedUserDefaults = NULL;

char *WMUserDefaultsDidChangeNotification = "WMUserDefaultsDidChangeNotification";

static void synchronizeUserDefaults(void *foo);

extern char *WMGetApplicationName();

#define DEFAULTS_DIR "/Defaults"

char *wusergnusteppath()
{
	static char *path = NULL;
	char *gspath;
	int pathlen;

	if (!path) {
		gspath = getenv("GNUSTEP_USER_ROOT");
		if (gspath) {
			gspath = wexpandpath(gspath);
			pathlen = strlen(gspath) + 4;
			path = wmalloc(pathlen);
			strcpy(path, gspath);
			wfree(gspath);
		} else {
			pathlen = strlen(wgethomedir()) + 10;
			path = wmalloc(pathlen);
			strcpy(path, wgethomedir());
			strcat(path, "/GNUstep");
		}
	}

	return path;
}

char *wdefaultspathfordomain(char *domain)
{
	char *path;
	char *gspath;

	gspath = wusergnusteppath();
	path = wmalloc(strlen(gspath) + strlen(DEFAULTS_DIR) + strlen(domain) + 4);
	strcpy(path, gspath);
	strcat(path, DEFAULTS_DIR);
	strcat(path, "/");
	strcat(path, domain);

	return path;
}

static void
#ifdef HAVE_ATEXIT
saveDefaultsChanges(void)
#else
saveDefaultsChanges(int foo, void *bar)
#endif
{
	/* save the user defaults databases */
	synchronizeUserDefaults(NULL);
}

/* set to save changes in defaults when program is exited */
static void registerSaveOnExit(void)
{
	static Bool registeredSaveOnExit = False;

	if (!registeredSaveOnExit) {
#ifdef HAVE_ATEXIT
		atexit(saveDefaultsChanges);
#else
		on_exit(saveDefaultsChanges, (void *)NULL);
#endif
		registeredSaveOnExit = True;
	}
}

static void synchronizeUserDefaults(void *foo)
{
	UserDefaults *database = sharedUserDefaults;

	while (database) {
		if (!database->dontSync)
			WMSynchronizeUserDefaults(database);
		database = database->next;
	}
}

void WMEnableUDPeriodicSynchronization(WMUserDefaults * database, Bool enable)
{
	database->dontSync = !enable;
}

void WMSynchronizeUserDefaults(WMUserDefaults * database)
{
	Bool fileIsNewer = False, release = False, notify = False;
	WMPropList *plF, *key;
	char *path;
	struct stat stbuf;

	if (!database->path) {
		path = wdefaultspathfordomain(WMGetApplicationName());
		release = True;
	} else {
		path = database->path;
	}

	if (stat(path, &stbuf) >= 0 && stbuf.st_mtime > database->timestamp)
		fileIsNewer = True;

	if (database->appDomain && (database->dirty || fileIsNewer)) {
		if (database->dirty && fileIsNewer) {
			plF = WMReadPropListFromFile(path);
			if (plF) {
				plF = WMMergePLDictionaries(plF, database->appDomain, False);
				WMReleasePropList(database->appDomain);
				database->appDomain = plF;
				key = database->searchList[0];
				WMPutInPLDictionary(database->defaults, key, plF);
				notify = True;
			} else {
				/* something happened with the file. just overwrite it */
				wwarning(_("cannot read domain from file '%s' when syncing"), path);
				WMWritePropListToFile(database->appDomain, path, True);
			}
		} else if (database->dirty) {
			WMWritePropListToFile(database->appDomain, path, True);
		} else if (fileIsNewer) {
			plF = WMReadPropListFromFile(path);
			if (plF) {
				WMReleasePropList(database->appDomain);
				database->appDomain = plF;
				key = database->searchList[0];
				WMPutInPLDictionary(database->defaults, key, plF);
				notify = True;
			} else {
				/* something happened with the file. just overwrite it */
				wwarning(_("cannot read domain from file '%s' when syncing"), path);
				WMWritePropListToFile(database->appDomain, path, True);
			}
		}

		database->dirty = 0;

		if (stat(path, &stbuf) >= 0)
			database->timestamp = stbuf.st_mtime;

		if (notify) {
			WMPostNotificationName(WMUserDefaultsDidChangeNotification, database, NULL);
		}
	}

	if (release)
		wfree(path);

}

void WMSaveUserDefaults(WMUserDefaults * database)
{
	if (database->appDomain) {
		struct stat stbuf;
		char *path;
		Bool release = False;

		if (!database->path) {
			path = wdefaultspathfordomain(WMGetApplicationName());
			release = True;
		} else {
			path = database->path;
		}
		WMWritePropListToFile(database->appDomain, path, True);
		database->dirty = 0;
		if (stat(path, &stbuf) >= 0)
			database->timestamp = stbuf.st_mtime;
		if (release)
			wfree(path);
	}
}

WMUserDefaults *WMGetStandardUserDefaults(void)
{
	WMUserDefaults *defaults;
	WMPropList *domain;
	WMPropList *key;
	struct stat stbuf;
	char *path;
	int i;

	if (sharedUserDefaults) {
		defaults = sharedUserDefaults;
		while (defaults) {
			/* path == NULL only for StandardUserDefaults db */
			if (defaults->path == NULL)
				return defaults;
			defaults = defaults->next;
		}
	}

	/* we didn't found the database we are looking for. Go read it. */
	defaults = wmalloc(sizeof(WMUserDefaults));
	memset(defaults, 0, sizeof(WMUserDefaults));

	defaults->defaults = WMCreatePLDictionary(NULL, NULL);

	defaults->searchList = wmalloc(sizeof(WMPropList *) * 3);

	/* application domain */
	key = WMCreatePLString(WMGetApplicationName());
	defaults->searchList[0] = key;

	/* temporary kluge. wmaker handles synchronization itself */
	if (strcmp(WMGetApplicationName(), "WindowMaker") == 0) {
		defaults->dontSync = 1;
	}

	path = wdefaultspathfordomain(WMGetFromPLString(key));

	if (stat(path, &stbuf) >= 0)
		defaults->timestamp = stbuf.st_mtime;

	domain = WMReadPropListFromFile(path);

	if (!domain)
		domain = WMCreatePLDictionary(NULL, NULL);

	if (path)
		wfree(path);

	defaults->appDomain = domain;

	if (domain)
		WMPutInPLDictionary(defaults->defaults, key, domain);

	/* global domain */
	key = WMCreatePLString("WMGLOBAL");
	defaults->searchList[1] = key;

	path = wdefaultspathfordomain(WMGetFromPLString(key));

	domain = WMReadPropListFromFile(path);

	wfree(path);

	if (!domain)
		domain = WMCreatePLDictionary(NULL, NULL);

	if (domain)
		WMPutInPLDictionary(defaults->defaults, key, domain);

	/* terminate list */
	defaults->searchList[2] = NULL;

	defaults->searchListArray = WMCreatePLArray(NULL, NULL);

	i = 0;
	while (defaults->searchList[i]) {
		WMAddToPLArray(defaults->searchListArray, defaults->searchList[i]);
		i++;
	}

	if (sharedUserDefaults)
		defaults->next = sharedUserDefaults;
	sharedUserDefaults = defaults;

	registerSaveOnExit();

	return defaults;
}

WMUserDefaults *WMGetDefaultsFromPath(char *path)
{
	WMUserDefaults *defaults;
	WMPropList *domain;
	WMPropList *key;
	struct stat stbuf;
	char *name;
	int i;

	assert(path != NULL);

	if (sharedUserDefaults) {
		defaults = sharedUserDefaults;
		while (defaults) {
			if (defaults->path && strcmp(defaults->path, path) == 0)
				return defaults;
			defaults = defaults->next;
		}
	}

	/* we didn't found the database we are looking for. Go read it. */
	defaults = wmalloc(sizeof(WMUserDefaults));
	memset(defaults, 0, sizeof(WMUserDefaults));

	defaults->defaults = WMCreatePLDictionary(NULL, NULL);

	defaults->searchList = wmalloc(sizeof(WMPropList *) * 2);

	/* the domain we want, go in the first position */
	name = strrchr(path, '/');
	if (!name)
		name = path;
	else
		name++;

	key = WMCreatePLString(name);
	defaults->searchList[0] = key;

	if (stat(path, &stbuf) >= 0)
		defaults->timestamp = stbuf.st_mtime;

	domain = WMReadPropListFromFile(path);

	if (!domain)
		domain = WMCreatePLDictionary(NULL, NULL);

	defaults->path = wstrdup(path);

	defaults->appDomain = domain;

	if (domain)
		WMPutInPLDictionary(defaults->defaults, key, domain);

	/* terminate list */
	defaults->searchList[1] = NULL;

	defaults->searchListArray = WMCreatePLArray(NULL, NULL);

	i = 0;
	while (defaults->searchList[i]) {
		WMAddToPLArray(defaults->searchListArray, defaults->searchList[i]);
		i++;
	}

	if (sharedUserDefaults)
		defaults->next = sharedUserDefaults;
	sharedUserDefaults = defaults;

	registerSaveOnExit();

	return defaults;
}

/* Returns a WMPropList array with all keys in the user defaults database.
 * Free the array with WMReleasePropList() when no longer needed,
 * but do not free the elements of the array! They're just references. */
WMPropList *WMGetUDKeys(WMUserDefaults * database)
{
	return WMGetPLDictionaryKeys(database->appDomain);
}

WMPropList *WMGetUDObjectForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *domainName, *domain;
	WMPropList *object = NULL;
	WMPropList *key = WMCreatePLString(defaultName);
	int i = 0;

	while (database->searchList[i] && !object) {
		domainName = database->searchList[i];
		domain = WMGetFromPLDictionary(database->defaults, domainName);
		if (domain) {
			object = WMGetFromPLDictionary(domain, key);
		}
		i++;
	}
	WMReleasePropList(key);

	return object;
}

void WMSetUDObjectForKey(WMUserDefaults * database, WMPropList * object, char *defaultName)
{
	WMPropList *key = WMCreatePLString(defaultName);

	database->dirty = 1;

	WMPutInPLDictionary(database->appDomain, key, object);
	WMReleasePropList(key);
}

void WMRemoveUDObjectForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *key = WMCreatePLString(defaultName);

	database->dirty = 1;

	WMRemoveFromPLDictionary(database->appDomain, key);

	WMReleasePropList(key);
}

char *WMGetUDStringForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *val;

	val = WMGetUDObjectForKey(database, defaultName);

	if (!val)
		return NULL;

	if (!WMIsPLString(val))
		return NULL;

	return WMGetFromPLString(val);
}

int WMGetUDIntegerForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *val;
	char *str;
	int value;

	val = WMGetUDObjectForKey(database, defaultName);

	if (!val)
		return 0;

	if (!WMIsPLString(val))
		return 0;

	str = WMGetFromPLString(val);
	if (!str)
		return 0;

	if (sscanf(str, "%i", &value) != 1)
		return 0;

	return value;
}

float WMGetUDFloatForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *val;
	char *str;
	float value;

	val = WMGetUDObjectForKey(database, defaultName);

	if (!val || !WMIsPLString(val))
		return 0.0;

	if (!(str = WMGetFromPLString(val)))
		return 0.0;

	if (sscanf(str, "%f", &value) != 1)
		return 0.0;

	return value;
}

Bool WMGetUDBoolForKey(WMUserDefaults * database, char *defaultName)
{
	WMPropList *val;
	int value;
	char *str;

	val = WMGetUDObjectForKey(database, defaultName);

	if (!val)
		return False;

	if (!WMIsPLString(val))
		return False;

	str = WMGetFromPLString(val);
	if (!str)
		return False;

	if (sscanf(str, "%i", &value) == 1 && value != 0)
		return True;

	if (strcasecmp(str, "YES") == 0)
		return True;

	if (strcasecmp(str, "Y") == 0)
		return True;

	return False;
}

void WMSetUDIntegerForKey(WMUserDefaults * database, int value, char *defaultName)
{
	WMPropList *object;
	char buffer[128];

	sprintf(buffer, "%i", value);
	object = WMCreatePLString(buffer);

	WMSetUDObjectForKey(database, object, defaultName);
	WMReleasePropList(object);
}

void WMSetUDStringForKey(WMUserDefaults * database, char *value, char *defaultName)
{
	WMPropList *object;

	object = WMCreatePLString(value);

	WMSetUDObjectForKey(database, object, defaultName);
	WMReleasePropList(object);
}

void WMSetUDFloatForKey(WMUserDefaults * database, float value, char *defaultName)
{
	WMPropList *object;
	char buffer[128];

	sprintf(buffer, "%f", value);
	object = WMCreatePLString(buffer);

	WMSetUDObjectForKey(database, object, defaultName);
	WMReleasePropList(object);
}

void WMSetUDBoolForKey(WMUserDefaults * database, Bool value, char *defaultName)
{
	static WMPropList *yes = NULL, *no = NULL;

	if (!yes) {
		yes = WMCreatePLString("YES");
		no = WMCreatePLString("NO");
	}

	WMSetUDObjectForKey(database, value ? yes : no, defaultName);
}

WMPropList *WMGetUDSearchList(WMUserDefaults * database)
{
	return database->searchListArray;
}

void WMSetUDSearchList(WMUserDefaults * database, WMPropList * list)
{
	int i, c;

	if (database->searchList) {
		i = 0;
		while (database->searchList[i]) {
			WMReleasePropList(database->searchList[i]);
			i++;
		}
		wfree(database->searchList);
	}
	if (database->searchListArray) {
		WMReleasePropList(database->searchListArray);
	}

	c = WMGetPropListItemCount(list);
	database->searchList = wmalloc(sizeof(WMPropList *) * (c + 1));

	for (i = 0; i < c; i++) {
		database->searchList[i] = WMGetFromPLArray(list, i);
	}
	database->searchList[c] = NULL;

	database->searchListArray = WMDeepCopyPropList(list);
}
