/*	XGPreferences.cpp
 *
 *		Preferences data class. This encapsulates the interface to the
 *	preferences information that I store away.
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XStringUtil.h>
#include <XError.h>
#include <XDataUtil.h>
#include <XPreferences.h>
#include <stdlib.h>
#include <string.h>

#if OPT_XWIN
#include <stdio.h>
#endif

/************************************************************************/
/*																		*/
/*	Find Folder Support (Macintosh)										*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*	FindFolderGlue
 *
 *		Find folder; on System 6 and earlier this uses a different
 *	mechanism
 */

static void FindFolderGlue(short *vRefNum, long *dirID)
{
	extern long _GMacOSVersion;
	SysEnvRec env;
	OSErr err;
	unsigned char name[64];
	CInfoPBRec cinfo;
	long sysDirID;
	HParamBlockRec hrec;
	
	if (_GMacOSVersion >= 0x0700) {
		err = FindFolder(kOnSystemDisk,
			   			 kPreferencesFolderType,
			   			 kDontCreateFolder,
			   			 vRefNum,
			   			 dirID);
		Assert(err == noErr);
	} else {
		err = SysEnvirons(2,&env);
		Assert(err == noErr);
		
		/*
		 *	Fron the WD, get the disk, folder
		 */
		
		err = GetWDInfo(env.sysVRefNum,vRefNum,&sysDirID,NULL);
		Assert(err == noErr);
		
		/*
		 *	Now that I have the system folder, create (or find)
		 *	a folder named 'Preferences'
		 */
		
		pstrcpy(name,"\pPreferences");
		cinfo.dirInfo.ioCompletion = NULL;
		cinfo.dirInfo.ioNamePtr = name;
		cinfo.dirInfo.ioVRefNum = *vRefNum;
		cinfo.dirInfo.ioFDirIndex = 0;
		cinfo.dirInfo.ioDrDirID = *dirID;
		
		err = PBGetCatInfo(&cinfo,false);
		if (err) {
			/*
			 *	Does not exist. Create it.
			 */
			
			pstrcpy(name,"\pPreferences");
			hrec.fileParam.ioCompletion = NULL;
			hrec.fileParam.ioVRefNum = *vRefNum;
			hrec.fileParam.ioNamePtr = name;
			hrec.fileParam.ioDirID = sysDirID;
			err = PBDirCreate(&hrec,false);
			if (err) {
				/*
				 *	Couldn't create?
				 */
				
				*dirID = sysDirID;
			} else {
				/*
				 *	Created directory
				 */
				
				*dirID = hrec.fileParam.ioDirID;
			}
		} else if (cinfo.dirInfo.ioFlAttrib & 0x10) {
			/*
			 *	It's a directory
			 */
			
			*dirID = cinfo.dirInfo.ioDrDirID;
		} else {
			/*
			 *	It's a file???
			 */
			
			*dirID = sysDirID;
		}
	}
}

#endif

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*$	XGPreferences::XGPreferences
 *
 *		Macintosh version of the file open routine. This opens the specified
 *	file name in the "Preferences" system folder, with the file creator
 *	type. (The file's type will be set to 'pref', marking it as a preferences
 *	file on the Macintosh operating system.)
 *
 *		If the file does not exist, this creates an empty preferences
 *	record, and will create a new preference file when this is destroyed
 *	and the contents flushed to disk.
 */

XGPreferences::XGPreferences(const char *file, long type)
{
	fDirty = false;
	fFile = file;
	fType = type;
	ReadData();
}

#endif

#if OPT_XWIN

/*$	XGPreferences::XGPreferences
 *
 *		Unix version of the file open routine. This operates like the
 *	Macintosh counterpart, except that the file is open in the home
 *	directory of the current user. If the file does not exist, this
 *	creates an empty preferences object, and will create a new preferences
 *	file when this is destroyed and the contents flushed to disk.
 */

XGPreferences::XGPreferences(const char *file)
{
	fDirty = false;
	fFile = file;
	ReadData();
}

#endif

#if OPT_WINOS

/*$	XGPreferences::XGPreferences
 *
 *		Windows version of the preferences open routine. On Windows,
 *	this constructs a key from the #company#, #program# and #version#
 *	keys, and accesses the preferences keys from that location in
 *	the system registry.
 *
 *		If a key does not exist, this creates one, and stores all
 *	of the newly created keys in that location.
 *
 *		Normally you would provide the name of your company as #company#,
 *	the name of your program as #program#, and the version of the
 *	preferences data (not necessarly the version of the application)
 *	in the #version# key. So, for example, with BugLink, we would write:
 *
 *		XGPreferences prefs("PandaWave","BugLink","1.0");
 */

XGPreferences::XGPreferences(const char *company, const char *program, const char *version)
{
	char buffer[256];
	DWORD disp;
	
	/*
	 *	Construct the key string:
	 *
	 *		Software\company\program\version
	 *
	 *	For example:
	 *
	 *		Software\PandaWave\WorldCal\1.0
	 */
	
	strcpy(buffer,"Software\\");
	strcat(buffer,company);
	strcat(buffer,"\\");
	strcat(buffer,program);
	if (version) {
		strcat(buffer,"\\");
		strcat(buffer,version);
	}
	
	fKey = (HKEY)INVALID_HANDLE_VALUE;
	if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE,
									  buffer,
									  0,
									  KEY_ALL_ACCESS,
									  &fKey)) {
		/*
		 *	Key doesn't exist. Create it
		 */
		
		if (ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE,
											buffer,
											0,
											"",
											REG_OPTION_NON_VOLATILE,
											KEY_ALL_ACCESS,
											NULL,
											&fKey,
											&disp)) {
			throw XGPostError(KXGPreferenceError);
		}
	}
}

#endif

/*$	XGPreferences::~XGPreferences
 *
 *		Delete preferences data. If the prefernences data was changed
 *	by calling any one of the SetXXX() methods, this overwrites the
 *	preferences data with the new preferences information.
 */

XGPreferences::~XGPreferences()
{
#if OPT_MACOS || OPT_XWIN
	if (fDirty) WriteData();
#endif

#if OPT_WINOS
	if (fKey != INVALID_HANDLE_VALUE) ::RegCloseKey(fKey);
#endif
}

/************************************************************************/
/*																		*/
/*	Get/Set Values														*/
/*																		*/
/************************************************************************/

/*$	XGPreferences::GetBoolean
 *
 *		Get boolean value. This looks up the key and returns either 'true'
 *	or 'false' for the preferences value. If no such key exists, this
 *	returns the default value (which defaults to 'false'.)
 */

bool XGPreferences::GetBoolean(const char *key, bool deflt)
{
	char buffer[256];
	
	GetString(key,buffer);
	if (!buffer[0]) return deflt;
	if (!strcmp(buffer,"true")) return true;
	else return false;
}

/*$	XGPreferences::GetInteger
 *
 *		Get an integer. This looks up the key and returns the value
 *	of that key, as a signed integer. If no such key exists, this
 *	returns the default value, which defaults to 0.
 */

long XGPreferences::GetInteger(const char *key, long deflt)
{
	char buffer[256];
	char *c;
	long l;
	bool flag = false;
	
	GetString(key,buffer);
	if (!buffer[0]) return deflt;
	l = 0;
	for (c = buffer; *c != 0; c++) {
		if (*c == '-') flag = true;
		else if ((*c >= '0') && (*c <= '9')) {
			l = (l * 10) + (*c - '0');
		}
	}
	if (flag) l = -l;
	return l;
}

/*$	XGPreferences::SetBoolean
 *
 *		Set the boolean value. This writes a boolean value out as
 *	the string "true" or "false" to the preferences key.
 */

void XGPreferences::SetBoolean(const char *key, bool value)
{
	SetString(key,value ? "true" : "false");
}

/*$	XGPreferences::SetInteger
 *
 *		Set integer value. This writes the numeric value to the
 *	preferences file as a signed text string.
 */

void XGPreferences::SetInteger(const char *key, long value)
{
	char buffer[256];
	
	::yformat(buffer,"#L",value);
	SetString(key,buffer);
}

/*$	XGPreferences::GetString
 *
 *		Get the string value. This looks up the key and returns
 *	the value of the string associated with this key. If no such
 *	key is provided, this returns the default value, which normally
 *	defaults to NULL.
 */

void XGPreferences::GetString(const char *key, char *string, const char *deflt)
{
#if OPT_MACOS || OPT_XWIN
	long i,len;
	char buffer[256];
	
	len = fDataKey.GetNumStrings();
	for (i = 0; i < len; i++) {
		fDataKey.GetString(i,buffer);
		if (!strcmp(key,buffer)) {
			fDataValue.GetString(i,string);
			return;
		}
	}
	string[0] = 0;
	if (deflt)
		strcpy(string, deflt);
#endif

#if OPT_WINOS
	unsigned long len;
	unsigned long type;
	long ret;
	
	string[0] = 0;
	len = 256;
	ret = ::RegQueryValueEx(fKey,
							key,
							NULL,
							&type,
							(unsigned char *)string,
							&len);
	if (ret) {
		string[0] = 0;
		if (deflt)
			strcpy(string, deflt);
	}
#endif
}

/*$	XGPreferences::SetString
 *
 *		Set the string value. This writes this string out for this
 *	key.`
 */

void XGPreferences::SetString(const char *key, const char *string)
{
#if OPT_MACOS || OPT_XWIN
	long i,len;
	char buffer[256];
		
	fDirty = true;
	len = fDataKey.GetNumStrings();
	for (i = 0; i < len; i++) {
		fDataKey.GetString(i,buffer);
		if (!strcmp(key,buffer)) {
			fDataValue.SetString(i,string);
			return;
		}
	}
	
	fDataKey.Append(key);
	fDataValue.Append(string);
#endif

#if OPT_WINOS
	::RegSetValueEx(fKey,key,0,REG_SZ,(unsigned char *)string,1+strlen(string));
#endif
}

/*$	XGPreferences::DeleteValue
 *
 *		Delete this key. If the key is found associated with this
 *	preferences key, the key and it's value are both removed. This
 *	does nothing if the key does not exist.
 */

void XGPreferences::DeleteValue(const char *key)
{
#if OPT_MACOS || OPT_XWIN
	long i,len;
	char buffer[256];
	
	len = fDataKey.GetNumStrings();
	for (i = 0; i < len; i++) {
		fDataKey.GetString(i,buffer);
		if (!strcmp(key,buffer)) {
			fDirty = true;
			fDataKey.Delete(i);
			fDataValue.Delete(i);
			return;
		}
	}
#endif

#if OPT_WINOS
	::RegDeleteValue(fKey,key);
#endif
}

/************************************************************************/
/*																		*/
/*	Read/Write Data														*/
/*																		*/
/************************************************************************/

/*	XGPreferences::ReadData
 *
 *		Read the data from the preferences file. For Mac and Unix only
 */

#if OPT_XWIN

void XGPreferences::ReadData()
{
	FILE *f;
	char buffer[256];
	char *c,*d;
	
	c = getenv("HOME");						// home directory
	if (c == NULL) buffer[0] = 0;
	else strcpy(buffer,c);
	strcat(buffer,"/.");
	strcat(buffer,fFile.Get());
	f = fopen(buffer,"r");
	if (f == NULL) return;
	
	/*
	 *	Read the data
	 */
	
	fDataKey.DeleteAll();
	fDataValue.DeleteAll();
	while (NULL != fgets(buffer,255,f)) {
		d = NULL;
		for (c = buffer; *c != 0; c++) {
			if (*c == '=') {
				*c = 0;
				d = c+1;
			} else if (*c == '\n') {
				*c = 0;
				break;
			}
		}
		if (d) {
			fDataKey.Append(buffer);
			fDataValue.Append(d);
		}
	}
	
	/*
	 *	Done.
	 */
	
	fclose(f);
}

#endif

#if OPT_MACOS

void XGPreferences::ReadData()
{
	short refNum;
	short vRefNum;
	long dirID;
	OSErr err;
	Handle h;
	long len;
	long pos;
	unsigned char *data;
	char buf1[256];
	char buf2[256];
	
	/*
	 *	Get the preferences folder. If we're under 6 or earlier,
	 *	get tricky.
	 */
	
	FindFolderGlue(&vRefNum,&dirID);
	
	/*
	 *	Now open this preferences file. If it doesn't exist, bail
	 */
	
	err = HOpen(vRefNum,
				dirID,
				cvtc2p((unsigned char *)buf1,fFile.Get()),
				fsRdPerm,
				&refNum);
	if (err) return;
	
	/*
	 *	We have the file. Read the file into a handle for processing
	 */
	
	err = GetEOF(refNum,&len);
	if (err || (len == 0)) {
		FSClose(refNum);
		return;
	}
	h = NewHandle(len);
	if (h == NULL) {
		FSClose(refNum);
		return;
	}
	HLock(h);
	err = FSRead(refNum,&len,*h);
	FSClose(refNum);
	if (err) {
		DisposeHandle(h);
		return;
	}
	
	/*
	 *	And process the handle
	 */
	
	fDataKey.DeleteAll();
	fDataValue.DeleteAll();
	
	data = (unsigned char *)*h;
	pos = 0;
	while (pos < len) {
		cvtp2c(buf1,data+pos);
		pos += data[pos] + 1;
		if (pos >= len) break;			// EOF too soon?
		cvtp2c(buf2,data+pos);
		pos += data[pos] + 1;
		
		/*
		 *	Insert new token
		 */
		
		fDataKey.Append(buf1);
		fDataValue.Append(buf2);
	}
	
	/*
	 *	Done. Release data from handle
	 */
	
	HUnlock(h);
	DisposeHandle(h);
}

#endif

/*	XGPreferences::WriteData
 *
 *		Write the data to the preferences file. For Mac and Unix only
 */

#if OPT_XWIN

void XGPreferences::WriteData(void)
{
	FILE *f;
	char *c;
	char buffer[256];
	char bufval[256];
	long len,off;
	
	c = getenv("HOME");						// home directory
	if (c == NULL) buffer[0] = 0;
	else strcpy(buffer,c);
	strcat(buffer,"/.");
	strcat(buffer,fFile.Get());
	f = fopen(buffer,"w");
	if (f == NULL) return;
	
	/*
	 *	Write the data. This is relatively easy
	 */
	
	len = fDataKey.GetNumStrings();
	for (off = 0; off < len; off++) {
		fDataKey.GetString(off,buffer);
		fDataValue.GetString(off,bufval);
		
		fprintf(f,"%s=%s\n",buffer,bufval);
	}
	
	/*
	 *	Done.
	 */
	
	fclose(f);
}

#endif

#if OPT_MACOS

void XGPreferences::WriteData(void)
{
	short refNum;
	short vRefNum;
	long dirID;
	OSErr err;
	Handle h;
	long len;
	char buf1[256];
	char buf2[256];
	long off;
	unsigned char *p;
	
	/*
	 *	Build the data handle from my data
	 */
	
	h = NewHandle(0);
	len = fDataKey.GetNumStrings();
	for (off = 0; off < len; off++) {
		fDataKey.GetString(off,buf2);
		p = cvtc2p((unsigned char *)buf1,buf2);
		PtrAndHand(p,h,1+*p);
		fDataValue.GetString(off,buf2);
		p = cvtc2p((unsigned char *)buf1,buf2);
		PtrAndHand(p,h,1+*p);
	}
	
	/*
	 *	Get the preferences folder. this *will* work under system 6,
	 *	as the RTL provides this as glue
	 */
	
	FindFolderGlue(&vRefNum,&dirID);
	
	/*
	 *	Now open this preferences file. If it doesn't exist, create
	 */
	
	err = HOpen(vRefNum,
				dirID,
				cvtc2p((unsigned char *)buf1,fFile.Get()),
				fsWrPerm,
				&refNum);
	if (err) {
		if (err == fnfErr) {
			/*
			 *	File was not found. Create it instead.
			 */
			
			err = HCreate(vRefNum,
						  dirID,
						  (unsigned char *)buf1,
						  fType,
						  'pref');
			if (err) return;
			
			err = HOpen(vRefNum,
						dirID,
						(unsigned char *)buf1,
						fsWrPerm,
						&refNum);
			if (err) return;
		} else return;
	} else {
		/*
		 *	File found. Clear the file's contents by setting the
		 *	EOF to zero
		 */
		
		err = SetEOF(refNum,0L);
		if (err) return;
	}
	
	/*
	 *	We have the file. Write the handle and return
	 */
	
	len = GetHandleSize(h);
	HLock(h);
	err = FSWrite(refNum,&len,*h);
	FSClose(refNum);
	HUnlock(h);
	DisposeHandle(h);
}

#endif
