// Settings.h

#ifdef WINDOWS
#include <stdafx.h>
#endif

#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>

#include "general.H"
#include "settings.H"

static const char *rcs_id = "@(#) $Id: settings.C 1 2022-02-25 17:20:12Z perette $" ;

#define WHITESPACE " \t\r\n"

static char *NewPath (const char *path, const char *extension = 0) {
	int index;
	char *newpath = (char *) malloc (strlen (path) + strlen (extension) + 2);
	strcpy (newpath, path);
	for (index = strlen (path); index >= 0; index--) {
		if (newpath [index - 1] == '/')
			break;
		if (newpath [index - 1] == '.') {
			strcpy (&newpath [index], extension);
			return (newpath);
		}
	}
	strcat (newpath, ".");
	strcat (newpath, extension);
	return (newpath);
}

static int IsStartOf (const char *section, char *inputbuffer) {
	char *start, *end;
	int status = 0;

	start = inputbuffer + strspn (inputbuffer, WHITESPACE);
	if (*start == '[') {
		if ((end = strchr (++start, ']'))) {
			status = ((int) strlen (section) == (int) (end - start)) &&
				(strncasecmp (section, start, end - start) == 0);
		}
	}
	return (status);
}

// =========================== Settings ==========================

Settings::Settings (void) {
	readMode = 0;
}

Settings::~Settings (void) {
}



// =========================== ReadDefaultSettings ==========================
// This class doesn't read anything from anywhere.
// It just assigns the default value to the result.

ReadDefaultSettings::ReadDefaultSettings (void) {
	readMode = 1;
}

ReadDefaultSettings::~ReadDefaultSettings (void) {
}

int ReadDefaultSettings::Value (const char *name, int *value, int def) {
	*value = def;
	return (1);
}

int ReadDefaultSettings::Value (const char *name, double *value, double def) {
	*value = def;
	return (1);
}

int ReadDefaultSettings::Value (const char *name, char **value, const char *def) {
	if (*value) {
		free (*value);
		*value = NULLP (char);
	}
	*value = strdup (def ? def : "");
	return (value != NULL);
}


int ReadDefaultSettings::Value (const char *name, char *value, int maxlen, const char *def) {
	strncpy (value, def, maxlen);
	*(value + maxlen - 1) = NULLCH;
	int status = (int) strlen (value) < maxlen;
	return (status);
}

/*
int ReadDefaultSettings::Value (const char *name, BOOL *value, BOOL *def) {
	*value = (def != 0);
	return (1);
};
*/






// =========================== WriteFileSettings ==========================
// This class writes settings to a file.

WriteFileSettings::WriteFileSettings (const char *thefile, const char *thesection, int *status) {
	char *file = NewPath (thefile, "ini");
	char *oldFile = NewPath (thefile, "bak");

	// move an existing file to a backup ("old") file
	remove (oldFile);
	rename (file, oldFile);

	*status = 0;
	if ((output = fopen (file, "w"))) {
		*status = 1;
		const char *section;
		if ((section = strrchr (thesection, '\\')))
			thesection = section + 1;
		if ((section = strrchr (thesection, '/')))
			section ++;
		else
			section = thesection;

		// Copy other sections, then write the section header
		FILE *input;
		if ((input = fopen (oldFile, "r"))) {
			int write = 1;
			char inputbuffer [500];

			while (fgets (inputbuffer, sizeof (inputbuffer), input)) {
				// find if this is the start of a section.
				// if so, determine if the section needs to be copied or not.
				char *start;
				start = inputbuffer + strspn (inputbuffer, WHITESPACE);
				if (*start == '[') {
					write = !IsStartOf (section, inputbuffer);
					if (write)
						fputs (start, output);
				} else if (write) {
					fprintf (output, *start ? "    %s" : "\n", start);
				}

				// if the string was not completely copied, copy or drop the rest.
				if (inputbuffer [strlen (inputbuffer) - 1] != '\n') {
					while (fgets (inputbuffer, sizeof (inputbuffer), input)) {
						if (write)
							fputs (inputbuffer, output);
						if (inputbuffer [strlen (inputbuffer) -1] == '\n')
							break;
					}
				}
			} /* while get string from file */
			fclose (input);
		} /* if "input" file opened */
		fprintf (output, "[%s]\n", section);
	}
}

WriteFileSettings::~WriteFileSettings (void) {
	if (output) {
		fclose (output);
	}
}

int WriteFileSettings::Value (const char *name, int *value, int def) {
	int status = 0;
	if (output) {
		status = (fprintf (output, "    %-20s = %d\n", name, *value) > 0);
	}
	return (status);
}

int WriteFileSettings::Value (const char *name, double *value, double def) {
	int status = 0;
	if (output) {
		status = (fprintf (output, "    %-20s = %e\n", name, *value) > 0);
	}
	return (status);
}

int WriteFileSettings::Value (const char *name, char *value, int maxlen, const char *def) {
	int status = 0;
	if (output) {
		status = (fprintf (output, "    %-20s = %s\n", name, value) > 0);
	}
	return (status);
}

int WriteFileSettings::Value (const char *name, char **value, const char *def) {
	int status = 0;
	if (*value != NULLP (char) && output) {
		status = (fprintf (output, "    %-20s = %s\n", name, *value) > 0);
	}
	return (status);
}

/*
int WriteFileSettings::Value (const char *name, BOOL *value, BOOL *def) {
	int status = 0;
	if (output) {
		status = (fprintf (output, "    %-20s = %d\n", name, *value != 0) > 0);
	}
	return (status);
};
*/






// =========================== ReadFileSettings ==========================
// This class reads values from the indicated section of an initialization file.
// ifNotFound and useDefaults is indicated, then it uses the default value.
// otherwise, the value is unchanged.

ReadFileSettings::ReadFileSettings (const char *thefile, const char *thesection, int *status, int useDefaults)
				 :ReadDefaultSettings () {
	char *file = NewPath (thefile, "ini");
	usingDefaults = useDefaults;
	if (!(input = fopen (file, "r"))) {
		// if we can't open the file, it is still okay if we can use defaults.
		*status = useDefaults;
	} else {
		// strip off any preceeding hiearchy to the section & just use the last bit.
		const char *section;
		if ((section = strrchr (thesection, '\\')))
			thesection = section + 1;
		if ((section = strrchr (thesection, '/')))
			section ++;
		else
			section = thesection;

		// search for start of appropriate section && set startOfSection.
		int startSection = 0;
		while (!startSection && fgets (inputbuffer, sizeof (inputbuffer), input)) {
			startSection = IsStartOf (section, inputbuffer);
			if (inputbuffer [strlen (inputbuffer) - 1] != '\n') {
				// report/log buffer overflow
				while (fgets (inputbuffer, sizeof (inputbuffer), input)) {
					if (inputbuffer [strlen (inputbuffer) -1] == '\n')
						break;
				}
			}
		}
		*status = (startOfSection = ftell (input)) >= 0;
	}
}

ReadFileSettings::~ReadFileSettings (void) {
	if (input) {
		fclose (input);
	}
}

int ReadFileSettings::Value (const char *name, int *value, int def) {
	char *setting, *end;
	int status = 0, temp;
	if ((setting = GetSetting (name))) {
		temp = strtol (setting, &end, 0 /* base determined by string */);
		status = !end || !*end;
	}
	if (status) {
		*value = temp;
	} else if (usingDefaults) {
		status = ReadDefaultSettings::Value (name, value, def);
	}
	return (status);
}

int ReadFileSettings::Value (const char *name, double *value, double def) {
	char *setting, *end;
	int status = 0;
	double temp;
	if ((setting = GetSetting (name))) {
		temp = strtod (setting, &end);
		status = !end || !*end;
	}
	if (status) {
		*value = temp;
	} else if (usingDefaults) {
		status = ReadDefaultSettings::Value (name, value, def);
	}
	return (status);
}

int ReadFileSettings::Value (const char *name, char *value, int maxlen, const char *def) {
	char *setting;
	int status = 0;
	if ((setting = GetSetting (name))) {
		strncpy (value, setting, maxlen);
		*(value + maxlen - 1) = NULLCH;
		status = (int) strlen (setting) < maxlen;
	} else if (usingDefaults) {
		status = ReadDefaultSettings::Value (name, value, maxlen, def);
	}
	return (status);
}

int ReadFileSettings::Value (const char *name, char **value, const char *def) {
	char *setting;
	int status = 0;
	if (*value) {
		free (*value);
		*value = NULLP (char);
	}
	if ((setting = GetSetting (name))) {
		*value = strdup (setting);
		status = (*value != NULLP (char));
	} else if (usingDefaults) {
		status = ReadDefaultSettings::Value (name, value, def);
	}
	return (status);
}

/*
int ReadFileSettings::Value (const char *name, BOOL *value, BOOL *def) {
	char *setting, *end;
	int status = 0, temp;
	if (setting = GetSetting (name)) {
		temp = strtol (setting, &end, 0 ); // 0 is base determined by string 
		status = (!end || !*end) && temp >= 0 && temp <= 1;
	}
	if (status)
		*value = temp;
	else if (usingDefaults)
		status = ReadDefaultSettings::Value (name, value, def);
	return (status);
};
*/


char *ReadFileSettings::GetSetting (const char *find) {
	char *name, *value;

	if (!input)
		return (NULLP (char));
	fseek(input, startOfSection, SEEK_SET);

	while (fgets (inputbuffer, sizeof (inputbuffer), input)) {
		if (inputbuffer [strlen (inputbuffer) - 1] != '\n') {
			// report/log buffer overflow
			while (fgets (inputbuffer, sizeof (inputbuffer), input)) {
				if (inputbuffer [strlen (inputbuffer) -1] == '\n')
					break;
			}
		} else {
			inputbuffer [strlen (inputbuffer) - 1] = NULLCH;
			name = inputbuffer + strspn (inputbuffer, WHITESPACE);
			if (*name == '[') // sections not supported yet.
			 	break;
			value = name + strcspn (name, WHITESPACE "=");
			if (isspace (*value)) {
				*(value++) = NULLCH;
				value += strcspn (value, WHITESPACE "=");
			}
			if (*name && *value == '=') {
				*(value++) = NULLCH;
				if (strcasecmp (name, find) == 0) {
					value += strspn (value, WHITESPACE);
					return (value);
				}
			}
		}
	}
	return (NULLP (char));
}






#ifdef WINDOWS

// =========================== WriteRegistrySettings ==========================
// This class writes settings to the registry.

WriteRegistrySettings::WriteRegistrySettings (const char *hiearchy, const char *file, int *status) {
	DWORD					disposition;
	SECURITY_ATTRIBUTES		sAt;

	sAt.nLength = sizeof(SECURITY_ATTRIBUTES);
	sAt.lpSecurityDescriptor = NULL;
	sAt.bInheritHandle = FALSE;

	*status = 0;
	if (RegCreateKeyEx (HKEY_LOCAL_MACHINE, hiearchy, 0, NULL, REG_OPTION_NON_VOLATILE,
						KEY_ALL_ACCESS, &sAt, &hKey, &disposition) == ERROR_SUCCESS) {
		*status = 1;
	}
}

WriteRegistrySettings::~WriteRegistrySettings (void) {
	if (hKey) {
		RegCloseKey(hKey);
	}
}

int WriteRegistrySettings::Value (const char *name, int *value, int def) {
	return (RegSetValueEx(hKey, name, 0, REG_DWORD, (unsigned char *) value, sizeof (*value)) == ERROR_SUCCESS);
}

int WriteRegistrySettings::Value (const char *name, double *value, double def) {
	char buffer [200];
	sprintf (buffer, "%e", *value);
	return (Value (name, buffer, 0));
}

int WriteRegistrySettings::Value (const char *name, char *value, int maxlen, const char *def) {
	return (RegSetValueEx(hKey, name, 0, REG_SZ, (unsigned char *) value, strlen (value) + 1) == ERROR_SUCCESS);
}

/*
int WriteRegistrySettings::Value (const char *name, BOOL *value, BOOL *def) {
	int save = (value != 0);
	return (RegSetValueEx(hKey, name, 0, REG_DWORD, (unsigned char *) &save, sizeof (save)) == ERROR_SUCCESS);
};
*/





// =========================== ReadRegistrySettings ==========================
// This class reads values from the registry.
// ifNotFound and a file was specified, then it reads from the file.
// ifNotFound and useDefaults, then it uses the default value.
// otherwise, the value is unchanged.


ReadRegistrySettings::ReadRegistrySettings (const char *hiearchy, const char *file, int *status, int useDefaults)
					 :ReadFileSettings (file, hiearchy, status, useDefaults) {
	DWORD					disposition;
	SECURITY_ATTRIBUTES		sAt;

	sAt.nLength = sizeof(SECURITY_ATTRIBUTES);
	sAt.lpSecurityDescriptor = NULL;
	sAt.bInheritHandle = FALSE;

	*status = 0;
	if (RegCreateKeyEx (HKEY_LOCAL_MACHINE, hiearchy, 0, NULL, REG_OPTION_NON_VOLATILE,
						KEY_ALL_ACCESS, &sAt, &hKey, &disposition) == ERROR_SUCCESS) {
		*status = 1;
	}
}


ReadRegistrySettings::~ReadRegistrySettings (void) {
	if (hKey) {
		RegCloseKey(hKey);
	}
}

int ReadRegistrySettings::Value (const char *name, int *value, int def) {
	int status;
	DWORD type, data;
	unsigned long len = sizeof (data);

	status = RegQueryValueEx (hKey, name, 0, &type, (unsigned char *) &data, &len) == ERROR_SUCCESS;
	if (status = status && (type == REG_DWORD) && (len == sizeof (data))) {
		*value = data;
	} else {
		status = ReadFileSettings::Value (name, value, def);
	}
	return (status);
}

int ReadRegistrySettings::Value (const char *name, double *value, double def) {
	int status;
	DWORD type;
	char data[255];
	unsigned long len = sizeof (data);

	status = RegQueryValueEx (hKey, name, 0, &type, (unsigned char *) data, &len) == ERROR_SUCCESS;
	if (status = status && (type == REG_SZ)) {
		double result;
		char *end;
		result = strtod (data, &end);
		if (end && *end) {
			status = 0;
		} else {
			*value = result;
		}
	}
	if (!status) {
		status = ReadFileSettings::Value (name, value, def);
	}
	return (status);
}

int ReadRegistrySettings::Value (const char *name, char *value, int maxlen, const char *def) {
	int status;
	DWORD type;
	char data[255];
	unsigned long len = sizeof (data);

	status = RegQueryValueEx (hKey, name, 0, &type, (unsigned char *) data, &len) == ERROR_SUCCESS;
	if (status = status && (type == REG_SZ)) {
		strncpy (value, data, maxlen);
		*(value + maxlen - 1) = NULL;
	} else {
		status = ReadFileSettings::Value (name, value, maxlen, def);
	}
	return (status);
}

/*
int ReadRegistrySettings::Value (const char *name, BOOL *value, BOOL *def) {
	int status;
	DWORD type, data;
	unsigned long len = sizeof (data);

	status = RegQueryValueEx (hKey, name, 0, &type, (unsigned char *) &data, &len) == ERROR_SUCCESS;
	if (status = status && (type == REG_DWORD) && (len == sizeof (data))) {
		*value = data != 0;
	} else {
		status = ReadFileSettings::Value (name, value, def);
	}
	return (status);
};


*/
#endif


