/* --- Copyright University of Sussex 1997. All rights reserved. ----------
 * File:			C.win32/extern/src/environment.c
 * Purpose:			Environment Manipulation
 * Author:			Robert John Duncan, Jun 14 1994 (see revisions)
 * Documentation:
 * Related Files:
 */

#include <stdlib.h>
#include "popcore.h"


/***************************************************************************
*                                                                          *
*	Access to the Registry                                                 *
*                                                                          *
***************************************************************************/

	/* Default company string */
static const TCHAR REG_DEFAULT_COMPANY[] = TEXT("Integral Solutions Ltd.");

	/* Default product string */
static const TCHAR REG_DEFAULT_PRODUCT[] = TEXT("Poplog");

	/* Actual company, product and version strings */
static const TCHAR* reg_key_company;
static const TCHAR* reg_key_product;
static const TCHAR* reg_key_version;

	/* Registry keys for Poplog data */
static TCHAR reg_key_path[MAX_PATH];
static TCHAR reg_key_version_path[MAX_PATH];

	/* Abbreviations for standard keys */
#define USER_KEY	HKEY_CURRENT_USER,  reg_key_path
#define VERSION_KEY HKEY_LOCAL_MACHINE, reg_key_version_path
#define GENERIC_KEY HKEY_LOCAL_MACHINE, reg_key_path

	/* Flag for once-only initialisation */
static BOOL registry_init_done = FALSE;

	/* Set up the preferred company, product and version strings */
BOOL pop_init_registry(LPCTSTR company, LPCTSTR product, LPCTSTR version) {
	if (registry_init_done)
		return (company == NULL || lstrcmp(company, reg_key_company) == 0)
			&& (product == NULL || lstrcmp(product, reg_key_product) == 0)
			&& (version == NULL || lstrcmp(version, reg_key_version) == 0);
	if (version == NULL)
		return FALSE;
	if (product == NULL)
		product = REG_DEFAULT_PRODUCT;
	if (company == NULL)
		company = REG_DEFAULT_COMPANY;
	if (lstrlen(company) + lstrlen(product) + lstrlen(version)
			+ sizeof "Software" + 3 > MAX_PATH)
		return FALSE;
	wsprintf(reg_key_path, TEXT("Software\\%s\\%s"), company, product);
	wsprintf(reg_key_version_path, TEXT("%s\\%s"), reg_key_path, version);
	reg_key_company = company;
	reg_key_product = product;
	reg_key_version = version;
	registry_init_done = TRUE;
	return TRUE;
}

	/* Open a registry key */
static HKEY open_key(HKEY root, LPCTSTR path, LPCTSTR key_name, DWORD mode) {
	TCHAR full_path[MAX_PATH];
	HKEY key;
	DWORD status;
	if (lstrlen(path) + lstrlen(key_name) + 2 > MAX_PATH)
		return NULL;
	wsprintf(full_path, TEXT("%s\\%s"), path, key_name);
	status = RegOpenKeyEx(root, full_path, 0, mode, &key);
	SetLastError(status);
	return status == ERROR_SUCCESS ? key : NULL;
}

	/* Open/create a registry key */
static HKEY create_key(HKEY root, LPCTSTR path, LPCTSTR key_name, DWORD mode) {
	TCHAR full_path[MAX_PATH];
	HKEY key;
	DWORD disp;
	DWORD status;
	if (lstrlen(path) + lstrlen(key_name) + 2 > MAX_PATH)
		return NULL;
	wsprintf(full_path, TEXT("%s\\%s"), path, key_name);
	status = RegCreateKeyEx(root, full_path, 0, NULL, REG_OPTION_NON_VOLATILE,
							mode, NULL, &key, &disp);
	SetLastError(status);
	return status == ERROR_SUCCESS ? key : NULL;
}

	/* Close a registry key */
static void close_key(HKEY key) {
	if (key != NULL) RegCloseKey(key);
}

	/* Get a value from the registry */
static BOOL get_value_by_key(
	HKEY key,
	LPCTSTR value_name,
	DWORD* type,
	BYTE data[],
	DWORD* len
) {
	DWORD status, nbytes = *len;
	if (key == NULL)
		return FALSE;
	status = RegQueryValueEx(key, value_name, 0, type, data, &nbytes);
	SetLastError(status);
	if (status == ERROR_SUCCESS) {
		/* only update len if successful (this behaviour is unspecified in
		   the documentation)
		*/
		*len = nbytes;
		return TRUE;
	}
	else
		return FALSE;
}

static BOOL get_value_by_name(
	HKEY root,
	LPCTSTR path,
	LPCTSTR key_name,
	LPCTSTR value_name,
	DWORD* type,
	BYTE data[],
	DWORD* len
) {
	HKEY key = open_key(root, path, key_name, KEY_READ);
	BOOL status = get_value_by_key(key, value_name, type, data, len);
	close_key(key);
	return status;
}

BOOL pop_get_registry_value(
	LPCTSTR key_name,
	LPCTSTR value_name,
	DWORD flags,
	DWORD* type,
	BYTE data[],
	DWORD* len
) {
	if (!registry_init_done) return FALSE;
	switch (flags) {
	case 0:
		if (get_value_by_name(USER_KEY, key_name, value_name, type, data, len))
			return TRUE;
	case 1:
		if (get_value_by_name(VERSION_KEY, key_name, value_name, type, data, len))
			return TRUE;
	case 2:
		if (get_value_by_name(GENERIC_KEY, key_name, value_name, type, data, len))
			return TRUE;
	default:
		return FALSE;
	}
}

	/* Change a value in the registry */
static BOOL set_value_by_key(
	HKEY key,
	LPCTSTR value_name,
	DWORD type,
	const BYTE data[],
	DWORD len
) {
	DWORD status;
	if (key == NULL)
		return FALSE;
	status = RegSetValueEx(key, value_name, 0, type, data, len);
	SetLastError(status);
	return status == ERROR_SUCCESS;
}

static BOOL set_value_by_name(
	HKEY root,
	LPCTSTR path,
	LPCTSTR key_name,
	LPCTSTR value_name,
	DWORD type,
	const BYTE data[],
	DWORD len
) {
	HKEY key = create_key(root, path, key_name, KEY_WRITE);
	BOOL status = set_value_by_key(key, value_name, type, data, len);
	close_key(key);
	return status;
}

BOOL pop_set_registry_value(
	LPCTSTR key_name,
	LPCTSTR value_name,
	DWORD flags,
	DWORD type,
	const BYTE data[],
	DWORD len
) {
	if (!registry_init_done) return FALSE;
	switch (flags) {
		case 0:
			return set_value_by_name(USER_KEY, key_name, value_name, type, data, len);
		case 1:
			return set_value_by_name(VERSION_KEY, key_name, value_name, type, data, len);
		case 2:
			return set_value_by_name(GENERIC_KEY, key_name, value_name, type, data, len);
		default:
			return FALSE;
	}
}

	/* Delete a value from the registry */
static BOOL delete_value_by_name(
	HKEY root,
	LPCTSTR path,
	LPCTSTR key_name,
	LPCTSTR value_name
) {
	HKEY key;
	DWORD status;

	key = open_key(root, path, key_name, KEY_WRITE);
	if (key == NULL)
		/* OK if it doesn't exist */
		return GetLastError() == ERROR_FILE_NOT_FOUND;
	status = RegDeleteValue(key, value_name);
	close_key(key);
	SetLastError(status);
	return status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND;
}

BOOL pop_delete_registry_value(
	LPCTSTR key_name,
	LPCTSTR value_name,
	DWORD flags
) {
	if (!registry_init_done) return FALSE;
	switch (flags) {
		case 2:
			if (!delete_value_by_name(GENERIC_KEY, key_name, value_name))
				return FALSE;
		case 1:
			if (!delete_value_by_name(VERSION_KEY, key_name, value_name))
				return FALSE;
		case 0:
			if (delete_value_by_name(USER_KEY, key_name, value_name))
				return TRUE;
		default:
			return FALSE;
	}
}

/***************************************************************************
*                                                                          *
*	Access to the Environment                                              *
*                                                                          *
***************************************************************************/

DWORD pop_get_environment_variable(LPCTSTR var, LPTSTR value, DWORD nchars) {
	return GetEnvironmentVariable(var, value, nchars);
}

BOOL pop_set_environment_variable(LPCTSTR var, LPCTSTR value) {
	return SetEnvironmentVariable(var, value);
}

DWORD pop_expand_environment_strings(LPCTSTR string, LPTSTR value, DWORD nchars)
{
	nchars = ExpandEnvironmentStrings(string, value, nchars);
#ifndef UNICODE
	/*	Ghastly bug in ExpandEnvironmentStringsA in NT 3.5x/4.0 -- it
		returns the number of characters * 2, probably because this is the
		number of bytes converted by the underlying UNICODE function.
	*/
	if (pop_os_version_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
		if (pop_os_version_info.dwMajorVersion == 3
		||	pop_os_version_info.dwMajorVersion == 4)
			nchars /= 2;
#endif
	return nchars;
}


/***************************************************************************
*                                                                          *
*	Environment Initialisation                                             *
*                                                                          *
***************************************************************************/

	/* Variables which must be set, with their default values */
static const TCHAR *environment_defaults[] = {
	TEXT("usepop"),			NULL,	/* no default */
	TEXT("popsys"),			TEXT("%usepop%\\pop\\pop"),
	TEXT("popsrc"),			TEXT("%usepop%\\pop\\src"),
	TEXT("popexternlib"),	TEXT("%usepop%\\pop\\extern\\lib"),
	TEXT("popautolib"),		TEXT("%usepop%\\pop\\lib\\auto"),
	TEXT("popliblib"),		TEXT("%usepop%\\pop\\lib\\lib"),
	TEXT("popvedlib"),		TEXT("%usepop%\\pop\\lib\\ved"),
	TEXT("popobjlib"),		TEXT("%usepop%\\pop\\obj"),
	TEXT("poplocal"),		TEXT("%usepop%\\pop\\local"),
	TEXT("poplocalauto"),	TEXT("%poplocal%\\local\\auto"),
	TEXT("poplocalbin"),	TEXT("%poplocal%\\local\\bin"),
	TEXT("popcontrib"),		TEXT("%usepop%\\pop\\contrib"),
	TEXT("poplib"),			TEXT("%HOMEDRIVE%%HOMEPATH%"),
	TEXT("popsavelib"),		TEXT("%popsys%"),
	TEXT("popsavepath"),	TEXT(";%poplib%;%poplocalbin%;%popsavelib%"),
	TEXT("popcomppath"),	TEXT(";%poplib%;%poplocalauto%;%popautolib%;%popliblib%"),
	NULL,	/* terminator */
};

	/* Where to find environment entries in the registry */
static const TCHAR ENVIRONMENT_KEY[] = TEXT("Environment");

	/* Initialise the environment from the registry or the supplied defaults.
	   Registry entries may be under any of three possible keys.
	*/
void pop_init_environment(const TCHAR** env) {
	HKEY key1, key2, key3;
	if (registry_init_done) {
		key1 = open_key(USER_KEY, ENVIRONMENT_KEY, KEY_READ);
		key2 = open_key(VERSION_KEY, ENVIRONMENT_KEY, KEY_READ);
		key3 = open_key(GENERIC_KEY, ENVIRONMENT_KEY, KEY_READ);
	}
	else
		key1 = key2 = key3 = NULL;
	if (env == NULL)
		/* use standard defaults */
		env = environment_defaults;
	for (; *env != NULL; env += 2) {
		if (GetEnvironmentVariable(env[0], NULL, 0) == 0) {
			TCHAR value[MAX_PATH];
			BYTE data[MAX_PATH*sizeof(TCHAR)];
			DWORD type, nbytes = sizeof data;
			if (get_value_by_key(key1, env[0], &type, data, &nbytes)
			||	get_value_by_key(key2, env[0], &type, data, &nbytes)
			||	get_value_by_key(key3, env[0], &type, data, &nbytes)) {
				if (type == REG_SZ)
					SetEnvironmentVariable(env[0], (TCHAR*)data);
				else if (type == REG_EXPAND_SZ) {
					DWORD n = pop_expand_environment_strings((TCHAR*)data, value, MAX_PATH);
					if (n > 0 && n <= MAX_PATH)
						SetEnvironmentVariable(env[0], value);
				}
			}
			else if (env[1] != NULL) {
				DWORD n = pop_expand_environment_strings(env[1], value, MAX_PATH);
				if (n > 0 && n <= MAX_PATH)
					SetEnvironmentVariable(env[0], value);
			}
		}
	}
	close_key(key3);
	close_key(key2);
	close_key(key1);
}


/* --- Revision History ---------------------------------------------------
--- Robert Duncan, Jan 24 1997
		Corrections for UNICODE compilation
--- Robert Duncan, Oct 31 1996
		ExpandEnvironmentStrings bug still present in NT 4.0
--- Robert Duncan, Jul 24 1996
		Changed the organisation of data in the registry: now has three
		levels for user-specific, version-specific and general preferences.
		Added support for access to the registry from Poplog.
--- Robert Duncan, Jun  4 1996
		pop_expand_environment_strings now uses global version info struct
--- Robert Duncan, May 14 1996
		Amended last fix to be NT 3.x only, i.e. not Windows 95
--- Robert John Duncan, Jan  8 1996
		Temporary (?) patch to pop_expand_environment_strings for horrible
		bug in NT 3.5
 */
