/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- February 1998
 */

/*
 * Persistent.cpp --- utilities to store persitent values
 */

#ifdef TARGET_OS_MAC
#	include "GUSIInternal.h"
#	include "GUSIFileSpec.h"
#endif

#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
#include <map>

#if defined(HAVE_ERRNO_H) || defined(WIN32)
#	include <errno.h>
#endif

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif

#ifndef WIN32
#	include <ctype.h>
#endif /* !WIN32 */

#ifdef WIN32
#	include "wincvs.h"
#endif

#include "Persistent.h"
#include "AppConsole.h"
#include "CPStr.h"
#include "uconsole.h"
#include "uwidget.h"
#include "FileTraversal.h"
#include "Authen.h"
#include "CvsPrefs.h"
#include "getline.h"
#include "CvsAlert.h"

#ifdef TARGET_OS_MAC
#	include <Resources.h>
#	include <TextUtils.h>
#	include <Folders.h>
#	include "VolsPaths.h"
#	include "MacMisc.h"
#	ifndef Assert_ // for mpw
#		include <assert.h>
#		define Assert_(cond) assert(cond)
#	endif
#endif /* TARGET_OS_MAC */

#if qMacCvsPP
#	include "MacCvsApp.h"
#	include "MacCvsConstant.h"
#endif

#if TARGET_RT_MAC_MACHO
#	include <LCFString.h>
#endif

#if qUnix
#	include "UCvsDialogs.h"
#endif

#ifdef WIN32
#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

#ifndef NEW
#define NEW new
#endif

std::vector<CPersistent::CPersistentEntry> *CPersistent::fAllEntries = 0L;

UIMPLEMENT_DYNAMIC(CPersistent, UObject)
UIMPLEMENT_DYNAMIC(CPersistentInt, CPersistent)
UIMPLEMENT_DYNAMIC(CPersistentBool, CPersistent)
UIMPLEMENT_DYNAMIC(PCStr, CPersistent)
UIMPLEMENT_DYNAMIC(PCPStr, CPersistent)

CPersistent::CPersistent(const char *uniqueName, kClassPersistent pclass) : fTimeStamp(0)
{
	Register(this, uniqueName, pclass);
	TouchTimeStamp();
}

CPersistent::~CPersistent()
{
	UnRegister(this);
}

void CPersistent::Register(CPersistent *value, const char *uniqueName, kClassPersistent pclass)
{
	std::vector<CPersistentEntry>::const_iterator i;
	if(fAllEntries == 0L)
		fAllEntries = NEW std::vector<CPersistentEntry>;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		if(strcmp(uniqueName, (*i).fUniqueName) == 0)
		{
			UAppConsole("Error : duplicate persistent %s !\n", uniqueName);
		}
	}
	CPersistentEntry newentry;
	newentry.fClass = pclass;
	newentry.fUniqueName = uniqueName;
	newentry.fValue = value;
	fAllEntries->push_back(newentry);
}

void CPersistent::UnRegister(CPersistent *value)
{
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		if(value == (*i).fValue)
			break;
	}

	if(i == fAllEntries->end())
	{
		UAppConsole("Error : Unknown persistent value !\n");
		return;
	}

	fAllEntries->erase(i);
	if(fAllEntries->size() == 0)
	{
		delete fAllEntries;
		fAllEntries = 0L;
	}
}

// very simple diff
static int simple_diff(const char *file1, const char *file2)
{
	FILE *f1 = fopen(file1, "rb");
	if(f1 == 0L)
		return -1;

	FILE *f2 = fopen(file2, "rb");
	if(f2 == 0L)
	{
		fclose(f1);
		return 1;
	}

	int res = -1;
	char buf1[8000];
	char buf2[8000];
	size_t num1, num2;
	while(!feof(f1) && !feof(f2) && !ferror(f1) && !ferror(f2))
	{
		num1 = fread(buf1, sizeof(char), 8000, f1);
		num2 = fread(buf2, sizeof(char), 8000, f2);
		if(num1 != num2 || (res = memcmp(buf1, buf2, num1 * sizeof(char))) != 0)
			goto fail;
	}

	if(ferror(f1) || ferror(f2))
		goto fail;

	if(feof(f1) ^ feof(f2))
		goto fail;

	res = 0;
fail:
	fclose(f1);
	fclose(f2);
	return res;
}

bool CPersistent::SaveAllSettings(const char *settingFileName, const char *forPath)
{
	UStr tmpSettingsFile(settingFileName);
	tmpSettingsFile << "__";
	FILE *fd = fopen(tmpSettingsFile, "w");
	if(fd == 0L)
	{
		cvs_err("Failed to open the setting file '%s' for writing (error %d)",
			(const char *)tmpSettingsFile, errno);
		return false;
	}

	fprintf(fd, "VERSION=%d\n", 1);

	UStr path(forPath);
	if(path.endsWith(kPathDelimiter))
		path[path.length() - 1] = '\0';
	fprintf(fd, "PATH=%s\n", (const char *)path);

	fprintf(fd, "AUTHENTICATION=%s\n",
		(const char *)Authen::kindToToken(gAuthen.kind()));

	fprintf(fd, "CVSROOT=%s\n", (const char *)gCvsPrefs);

	std::vector<CPersistentEntry>::const_iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		const CPersistentEntry & entry = *i;
		if((entry.fClass & kAddSettings) == 0)
			continue;

		if(entry.fValue->IsKindOf(URUNTIME_CLASS(CPersistentBool)))
		{
			CPersistentBool *pbool = (CPersistentBool *)entry.fValue;
			bool state = *pbool;
			fprintf(fd, "%s=%s\n", entry.fUniqueName, state ? "true" : "false");
		}
		else if(entry.fValue->IsKindOf(URUNTIME_CLASS(CPersistentInt)))
		{
			CPersistentInt *pint = (CPersistentInt *)entry.fValue;
			int val = *pint;
			fprintf(fd, "%s=%d\n", entry.fUniqueName, val);
		}
		else if(entry.fValue->IsKindOf(URUNTIME_CLASS(PCStr)))
		{
			PCStr *pstr = (PCStr *)entry.fValue;
			const char *str = *pstr;
			fprintf(fd, "%s=%s\n", entry.fUniqueName, (const char *)(str == 0L ? "" : str));
		}
		else if(entry.fValue->IsKindOf(URUNTIME_CLASS(PCPStr)))
		{
			PCPStr *pstr = (PCPStr *)entry.fValue;
			const char *str = *pstr;
			fprintf(fd, "%s=%s\n", entry.fUniqueName, (const char *)(str == 0L ? "" : str));
		}
		else
		{
			cvs_err("Unknown persistent value '%s'\n", entry.fUniqueName);
		}
	}
	fclose(fd);
	
	if(simple_diff(tmpSettingsFile, settingFileName) == 0)
	{
		if(unlink(tmpSettingsFile) != 0 && errno != ENOENT)
			cvs_err("Could not remove file '%s' (error %d)\n", (const char *)tmpSettingsFile, errno);
	
		return false;
	}

	if(unlink(settingFileName) != 0 && errno != ENOENT)
	{
		cvs_err("Could not remove file '%s' (error %d)\n", (const char *)settingFileName, errno);
		return false;
	}
	
	if(rename(tmpSettingsFile, settingFileName) != 0)
	{
		cvs_err("Could not rename file '%s' (error %d)\n", (const char *)tmpSettingsFile, errno);
		return false;
	}
	
	return true;
}

#ifdef TARGET_OS_MAC
	static OSErr GetPreferencesFile(FSSpec & mMacFileSpec)
	{
		OSErr	theErr;
	#if TARGET_RT_MAC_CFM
		short	theVRef;
		long	theDirID;
		Str255 name = {'\0'};
		
		theErr = ::FindFolder(kOnSystemDisk, kPreferencesFolderType,
						kDontCreateFolder, &theVRef, &theDirID);
		
		if (theErr == noErr)
		{
			GUSIFileSpec spec(theVRef, theDirID, name);
			theErr = spec.Error();
			if (theErr == noErr)
			{
				spec += kCVS_prefName;
				theErr = spec.Error();
				if (theErr == noErr || theErr == fnfErr)
				{
					mMacFileSpec = spec;
				}
			}
		}
	#else
		FSRef ref, newref;
		theErr = ::FSFindFolder(kOnAppropriateDisk, kPreferencesFolderType,
						kDontCreateFolder, &ref);
		
		if (theErr == noErr)
		{
			LCFString str(kCVS_prefName);
			UniChar buffer[PATH_MAX];
			CFRange range = {0, str.GetLength()};
			::CFStringGetCharacters(str, range, buffer);
			
			theErr = ::FSMakeFSRefUnicode(&ref, str.GetLength(), buffer, kTextEncodingDefaultFormat, &newref);
			
			if (theErr == noErr)
			{
				return ::FSGetCatalogInfo(&newref, kFSCatInfoNone, NULL, NULL, &mMacFileSpec, NULL);
			}
			else if(theErr == fnfErr)
			{
				return ::FSCreateFileUnicode(&ref, str.GetLength(), buffer, kFSCatInfoNone,
					NULL, NULL, &mMacFileSpec);
			}
		}
	#endif

		return theErr;

	}
#endif // TARGET_OS_MAC

void CPersistent::SaveAll(void)
{
#ifdef TARGET_OS_MAC
	short FRef, oldRef;
	OSErr err;
	FSSpec thePrefFile;
	
	err = GetPreferencesFile(thePrefFile);

	if(err != noErr)
	{
		cvs_err("Error while searching preference folder (error %d)!\n", err);
		return;
	}

	oldRef = CurResFile();

	FRef = FSpOpenResFile(&thePrefFile, fsRdWrPerm);
	if(FRef < 0)
	{
		::FSpCreateResFile (&thePrefFile, 'mCVS', 'pref', smRoman);
		FRef = FSpOpenResFile(&thePrefFile, fsRdWrPerm);
		if(FRef < 0)
		{
			cvs_err("Error while saving preferences (error %d)!\n", ResError());
			return;
		}
	}

	UseResFile (FRef);
	CPStr name;

	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		int size = entry.fValue->SizeOf();
		if(size <= 0)
			continue;
		
		//short id = Unique1ID(kCVS_prefID);
		name = entry.fUniqueName;

		Handle removing = Get1NamedResource(kCVS_prefID, name);

		if (removing != nil)
		{
			RemoveResource(removing);
			Assert_(ResError() == noErr);
			DisposeHandle(removing);
		}

		short theID;
		do
		{
			theID = Unique1ID(kCVS_prefID);
		}
		while (theID <= 127);

		Handle hdl = NewHandle(size);
		Assert_(hdl != 0L);
		HLock(hdl);
		BlockMove(entry.fValue->GetData(), *hdl, size);
		HUnlock(hdl);
		AddResource(hdl, kCVS_prefID, theID, name);
		Assert_(ResError() == noErr);
		UpdateResFile(FRef);
		Assert_(ResError() == noErr);
		ReleaseResource(hdl);
		Assert_(ResError() == noErr);
	}

	UseResFile(oldRef);
	Assert_(ResError() == noErr);
	CloseResFile (FRef);
	Assert_(ResError() == noErr);
#endif /* TARGET_OS_MAC */
#ifdef WIN32
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		unsigned int size = entry.fValue->SizeOf();
		if(size == 0)
			continue;
		
		if(!AfxGetApp()->WriteProfileBinary(PROFILE_NAME, entry.fUniqueName,
			(BYTE *)entry.fValue->GetData(), size))
		{
			cvs_err("Error while saving preferences !\n");
			break;
		}
	}
#endif /* WIN32 */
#ifdef qUnix
	CStr home;
	home = gCvsPrefs.Home();
	if(!home.endsWith(kPathDelimiter))
		home << kPathDelimiter;
	home << ".gcvsrc";
	FILE *out = fopen(home, "wb");
	if(out == 0L)
	{
		cvs_err("Unable to open '%s' for writing (error %d)", (char *)home, errno);
		return;
	}
	int version = 1;
	fwrite(&version, sizeof(int), 1, out);
	int numEntries = fAllEntries->size();
	fwrite(&numEntries, sizeof(int), 1, out);
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		unsigned int size = entry.fValue->SizeOf();

		fwrite(entry.fUniqueName, sizeof(char), strlen(entry.fUniqueName) + 1, out);
		fwrite(&size, sizeof(unsigned int), 1, out);
		fwrite(entry.fValue->GetData(), 1, size, out);
	}

	if(ferror(out))
		cvs_err("Error while writing '%s' (error %d)", (char *)home, errno);
	fclose(out);
#endif /* qUnix */

	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		entry.fValue->ResetTimeStamp();
	}
}

bool CPersistent::LoadAll(void)
{
#ifdef TARGET_OS_MAC
	FSSpec thePrefFile;
	short FRef, oldRef;
	OSErr err;
	
	err = GetPreferencesFile(thePrefFile);
	
	if(err != noErr)
		return false;

	oldRef = CurResFile();
	FRef = FSpOpenResFile(&thePrefFile, fsRdPerm);
	if(FRef < 0)
		return false;

	CPStr name;
	UseResFile(FRef);
	
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		name = entry.fUniqueName;

		Handle hdl = Get1NamedResource(kCVS_prefID, name);
		if(hdl == 0L)
			continue;
		
		HLock(hdl);
		entry.fValue->SetData(*hdl, GetHandleSize(hdl));
		entry.fValue->ResetTimeStamp();
		HUnlock(hdl);
	}

	// migrate to the new gOldCvsroots container
	if(gOldCvsroots.GetList().size() == 0)
	{
		for(int nIndex = 0; nIndex <= NUM_CVSROOT; nIndex++)
		{
			char tmp[20];
			sprintf(tmp, "P_Cvsroot%d", nIndex);

			CPStr pstr(tmp);
			Handle hdl = Get1NamedResource(kCVS_prefID, pstr);
			if(hdl == 0L)
				continue;

			CStr root((char *)*hdl);
			if( !root.empty() )
				gOldCvsroots.Insert(root);
		}
	}

	CloseResFile (FRef);
	UseResFile(oldRef);
	return true;
#endif /* TARGET_OS_MAC */
#ifdef WIN32
	bool found_one = false;
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		BYTE* ppData;
		UINT pBytes;

		if(!AfxGetApp()->GetProfileBinary(PROFILE_NAME, entry.fUniqueName,
			&ppData, &pBytes))
		{
			continue;
		}
		found_one = true;
		entry.fValue->SetData(ppData, pBytes);
		entry.fValue->ResetTimeStamp();
		delete ppData;
	}
	return found_one;
#endif /* WIN32 */
#ifdef qUnix
	CStr home;
	home = gCvsPrefs.Home();
	if(!home.endsWith(kPathDelimiter))
		home << kPathDelimiter;
	home << ".gcvsrc";
	FILE *out = fopen(home, "rb");
	if(out == 0L)
		return false;

	int version;
	int numEntries;
	CStaticAllocT<char> alloc;

	fread(&version, sizeof(int), 1, out);
	if(version != 1)
	{
		cvs_err("Unexpected version %d in the file '%s'\n", version, (char *)home);
		goto close;
	}
	fread(&numEntries, sizeof(int), 1, out);
	if(numEntries < 0)
	{
		cvs_err("Unexpected # %d in the file '%s'\n", numEntries, (char *)home);
		goto close;
	}
	for(int i = 0; i < numEntries; i++)
	{
		CStr name;
		while(!feof(out) && !ferror(out))
		{
			int c = getc(out);
			name << (char)c;
			if(c == '\0')
				break;
		}
		if(feof(out))
		{
			cvs_err("Unexpected end of file in '%s'\n", (char *)home);
			goto close;
		}
		if(ferror(out))
		{
			cvs_err("Unexpected error %d in '%s'\n", errno, (char *)home);
			goto close;
		}
		CPersistentEntry * entry = CPersistent::Find(name);
		if(entry == 0L)
		{
			cvs_err("Unexpected token '%s' in '%s'\n", (char *)name, (char *)home);
		}

		unsigned int size;
		fread(&size, sizeof(unsigned int), 1, out);
		
		alloc.AdjustSize(size);
		fread((char *)alloc, 1, size, out);
		if(entry != 0L)
		{
			entry->fValue->SetData((char *)alloc, size);
			entry->fValue->ResetTimeStamp();
		}
	}

	if(ferror(out))
		cvs_err("Error while reading '%s' (error %d)", (char *)home, errno);
	fclose(out);
	return true;
close:
	fclose(out);
#endif /* qUnix */
	return false;
}

CPersistent::CPersistentEntry * CPersistent::Find(const char *uniqueName)
{
	std::vector<CPersistentEntry>::iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		if(!strcmp(uniqueName, entry.fUniqueName))
			return &entry;
	}
	return 0L;
}

bool CPersistent::NeedSave(bool onlySettings)
{
	std::vector<CPersistentEntry>::const_iterator i;
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		const CPersistentEntry & entry = *i;
		if(onlySettings && (entry.fClass & kAddSettings) == 0)
			continue;

		if(entry.fValue->fTimeStamp != 0)
			return true;
	}
	return false;
}

#if !qMPWCvs
//
// Settings management
//

#define SETTINGS_FOLDER "Settings"

static bool SettingsGetLoc(CStr & path)
{
	struct stat sb;

	path = "";
#ifdef WIN32
	CWincvsApp* pApp = (CWincvsApp *)AfxGetApp();
	pApp->GetAppPath(path);
	if(!path.empty() && !path.endsWith(kPathDelimiter))
	{
		path << kPathDelimiter;
	}
	path << SETTINGS_FOLDER;
#elif defined(qUnix)
	path = gCvsPrefs.Home();
	if(!path.endsWith(kPathDelimiter))
		path << kPathDelimiter;
	path << ".gcvs";

	if(stat(path, &sb) != -1)
	{
		if(!S_ISDIR(sb.st_mode))
		{
			cvs_err("The file '%s' already exists and is not a directory", (char *)path, errno);
			return false;
		}
	}
	else if(mkdir(path, 0750) != 0)
	{
		cvs_err("Unable to create the directory '%s' (error %d)", (char *)path, errno);
		return false;
	}

	if(!path.endsWith(kPathDelimiter))
		path << kPathDelimiter;
	path << SETTINGS_FOLDER;
#else
#	ifdef qMacCvsPP
	path = CMacCvsApp::GetAppPath();
#	endif /* qMacCvsPP */

	if(!path.empty() && !path.endsWith(kPathDelimiter))
	{
		path << kPathDelimiter;
	}
	path << SETTINGS_FOLDER;
#endif

	if(stat(path, &sb) != -1 && S_ISDIR(sb.st_mode))
		return true;

#if qUnix || TARGET_RT_MAC_MACHO
	if(mkdir(path, 0750) != 0)
#else
	if(mkdir(path) != 0)
#endif
	{
		cvs_err("Failed to create the setting folder '%s' (error %d)",
			(const char *)path, errno);
		return false;
	}
	return true;
}

class USaveSettings : public UWidget
{
public:
	UDECLARE_DYNAMIC(USaveSettings)
public:
	USaveSettings(const char *path, const char *oldname);
	virtual ~USaveSettings();

	virtual void DoDataExchange(bool fill);

	enum
	{
#if qUnix
		kOK = EV_COMMAND_START,
		kCancel,
		kSettingName,
#else
		kSettingName = EV_COMMAND_START,
#endif
		kCopyPath,
		kCopyFrom,
		kCopyList
	};

	UStr m_settingName;
	UStr m_copyFromPath;

protected:
	UStr m_path;
	UStr m_oldname;
	std::map<UStr, UStr> m_list;

	ev_msg int OnCopyFrom(void);
#if qUnix
	ev_msg int OnOK(void);
	ev_msg int OnCancel(void);
#endif

	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(USaveSettings, UWidget)

UBEGIN_MESSAGE_MAP(USaveSettings, UWidget)
	ON_UCOMMAND(USaveSettings::kCopyFrom, USaveSettings::OnCopyFrom)
#if qUnix
	ON_UCOMMAND(USaveSettings::kOK, USaveSettings::OnOK)
	ON_UCOMMAND(USaveSettings::kCancel, USaveSettings::OnCancel)
#endif
UEND_MESSAGE_MAP()

#if qUnix
int USaveSettings::OnOK(void)
{
	EndModal(true);
	return 0;
}

int USaveSettings::OnCancel(void)
{
	EndModal(false);
	return 0;
}
#endif

USaveSettings::USaveSettings(const char *path, const char *oldname) :
	UWidget(::UEventGetWidID()), m_path(path), m_oldname(oldname)
{
}

USaveSettings::~USaveSettings()
{
}

static bool GetAllPersistentSettings(std::map<UStr, UStr> & res);

void USaveSettings::DoDataExchange(bool fill)
{
	if(fill)
	{
		UEventSendMessage(GetWidID(), EV_SETTEXT, kCopyPath,
			(void *)(const char *)m_path);

		UStr defname;
		if(m_oldname.empty())
		{
			UStr uppath;
			SplitPath(m_path, uppath, defname);
		}
		else
			defname = m_oldname;

		UEventSendMessage(GetWidID(), EV_SETTEXT, kSettingName,
			(void *)(const char *)defname);

		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kCopyList, 0), 0L);

		UEventSendMessage(GetWidID(), EV_COMBO_RESETALL, kCopyList, 0L);

		if(GetAllPersistentSettings(m_list))
		{
			std::map<UStr, UStr>::const_iterator i;
			for(i = m_list.begin(); i != m_list.end(); ++i)
			{
				UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kCopyList,
					(void *)(const char *)(*i).first);
			}
		}
	}
	else
	{
		UEventSendMessage(GetWidID(), EV_GETTEXT, kSettingName,
			&m_settingName);
		int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCopyFrom, 0L);
		if(state)
		{
			UStr what;
			UEventSendMessage(GetWidID(), EV_COMBO_GETSEL, kCopyList, &what);

			std::map<UStr, UStr>::const_iterator i;
			i = m_list.find(what);
			if(i != m_list.end())
			{
				m_copyFromPath = (*i).second;
			}
		}
	}
}

int USaveSettings::OnCopyFrom(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCopyFrom, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kCopyList, state), 0L);
	return 1;
}

static bool loadSettingsInternal(const char *fullname,
	bool (*func)(const char *token, const char *value, void *data), void *data)
{
	bool res = false;
	int line_length;
	char *line = 0L;
	size_t line_chars_allocated = 0;
	FILE *fp = fopen(fullname, "r");

	if(fp == 0L)
	{
		cvs_err("Failed to open the setting file '%s' for reading (error %d)",
			fullname, errno);
		goto fail;
	}

	while ((line_length = getline (&line, &line_chars_allocated, fp)) > 0)
	{
		// read each token=value
		char *tmp = 0L;

		if((tmp = strchr(line, '\n')) != 0L)
			*tmp = '\0';

		tmp = line;
		while(isspace(*tmp) && *tmp != '\0')
			tmp++;
		if(*tmp == '\0' || *tmp == '#')
			continue;

		UStr token;
		UStr value;
		const char *tmp2 = strchr(tmp, '=');
		if(tmp2 == 0L)
			continue;

		token.set(tmp, tmp2 - tmp);
		value = tmp2 + 1;

		// check version
		if(strcmp(token, "VERSION") == 0)
		{
			if(atoi(value) != 1)
			{
				cvs_err("Newer format than expected in the setting file '%s'\n", fullname);
				goto fail;
			}
			continue;
		}

		if(func(token, value, data))
			break;
	}

	if (line_length < 0 && !feof (fp))
	{
		cvs_err("Cannot read file (error %d)\n", errno);
		goto fail;
	}

	res = true;
fail:
	if(line != 0L)
		free(line);
	if(fp!= 0L)
		fclose(fp);
	return res;
}

static void getSettingName(const char *fromfile, UStr & res)
{
	CStr base, ext, uppath, filename;
	SplitPath(fromfile, uppath, filename);
	GetExtension(filename, base, ext);
	if(!base.empty() && stricmp(ext, "txt") == 0)
		res = base;
	else
		res = filename;
}

class CFindSettings : public TraversalReport
{
public:
	CFindSettings(const char *findPath) : m_pathToFind(findPath), m_list(0L)
	{
		if(m_pathToFind.endsWith(kPathDelimiter))
			m_pathToFind[m_pathToFind.length() - 1] = '\0';
	}

	CFindSettings(std::map<UStr, UStr> & list) : m_list(&list)
	{
	}

	virtual ~CFindSettings() {}

	virtual kTraversal OnError(const char *err, int errcode)
	{
		cvs_err(err);
		cvs_err("\n");
		return kTraversalError;
	}

	virtual kTraversal OnFile(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const UFSSpec * macspec);

	static bool searchPath(const char *token, const char *value, void *data);
	static bool searchAllPath(const char *token, const char *value, void *data);

	UStr m_pathToFind;
	UStr m_settingsFile;
	bool m_foundIt;
	UStr m_topfolder;
	std::map<UStr, UStr> *m_list;
};

bool CFindSettings::searchPath(const char *token, const char *value, void *data)
{
	CFindSettings *loader = (CFindSettings *)data;
	// if we give a sub-folder to look for, it will return the
	// top folder's settings
	if(strcmp(token, "PATH") == 0 &&
#if qUnix
		strncmp(value, loader->m_pathToFind, strlen(value)) == 0
#else
		strnicmp(value, loader->m_pathToFind, strlen(value)) == 0
#endif
		)
	{
		loader->m_foundIt = true;
		loader->m_topfolder = value;
		return true;
	}
	return false;
}

bool CFindSettings::searchAllPath(const char *token, const char *value, void *data)
{
	CFindSettings *loader = (CFindSettings *)data;
	if(strcmp(token, "PATH") == 0)
	{
		getSettingName(loader->m_settingsFile, loader->m_settingsFile);
		std::map<UStr, UStr>::value_type v(loader->m_settingsFile, UStr(value));
		loader->m_list->insert(v);
		return true;
	}
	return false;
}

kTraversal CFindSettings::OnFile(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const UFSSpec * macspec)
{
	m_foundIt = false;
	if(m_list == 0L)
	{
		loadSettingsInternal(fullname, searchPath, this);
	}
	else
	{
		m_settingsFile = fullname;
		loadSettingsInternal(fullname, searchAllPath, this);
	}

	if(m_foundIt)
		m_settingsFile = fullname;
	
	return m_foundIt ? kStopTraversal : kContinueTraversal;
}

static bool GetAllPersistentSettings(std::map<UStr, UStr> & res)
{
	UStr settingsDir;
	if(!SettingsGetLoc(settingsDir))
		return false;

	CFindSettings finder(res);
	FileTraverse(settingsDir, finder);

	return !res.empty();
}

bool HasPersistentSettings(const char *path)
{
	UStr settingsDir;
	if(!SettingsGetLoc(settingsDir))
		return false;

	CFindSettings finder(path);
	FileTraverse(settingsDir, finder);

	return !finder.m_settingsFile.empty();
}

static bool _loadPersistentSettings(const char *token, const char *value, void *data)
{
	if(strcmp(token, "CVSROOT") == 0)
	{
		gCvsPrefs = value;
	}
	else if(strcmp(token, "AUTHENTICATION") == 0)
	{
		gAuthen.setkind(Authen::tokenToKind(value));
	}
	else if(strcmp(token, "PATH") == 0)
	{
	}
	else
	{
		CPersistent::CPersistentEntry *entry = CPersistent::Find(token);
		if(entry == 0L)
		{
			//cvs_err("Persistent value '%s' is not registred\n", token);
		}
		else
		{
			if(entry->fValue->IsKindOf(URUNTIME_CLASS(CPersistentBool)))
			{
				CPersistentBool *pbool = (CPersistentBool *)entry->fValue;
				if(strcmp(value, "true") == 0)
					*pbool = true;
				else if(strcmp(value, "false") == 0)
					*pbool = false;
				else
					cvs_err("Invalid value '%s' for persistent '%s'\n",
						(const char *)value, entry->fUniqueName);
			}
			else if(entry->fValue->IsKindOf(URUNTIME_CLASS(CPersistentInt)))
			{
				CPersistentInt *pint = (CPersistentInt *)entry->fValue;
				int aint;
				int check = sscanf(value, "%d", &aint);
				if(check == 1)
					*pint = aint;
				else
					cvs_err("Invalid value '%s' for persistent '%s'\n",
						(const char *)value, entry->fUniqueName);
			}
			else if(entry->fValue->IsKindOf(URUNTIME_CLASS(PCStr)))
			{
				PCStr *pstr = (PCStr *)entry->fValue;
				*pstr = value;
			}
			else if(entry->fValue->IsKindOf(URUNTIME_CLASS(PCPStr)))
			{
				PCPStr *pstr = (PCPStr *)entry->fValue;
				*pstr = value;
			}
			else
			{
				cvs_err("Unknown persistent value '%s'\n", entry->fUniqueName);
			}
		}
	}

	return false;
}

void LoadPersistentSettings(const char *path, bool quiet)
{
	UStr settingsDir;
	if(!SettingsGetLoc(settingsDir))
		return;

	CFindSettings finder(path);
	FileTraverse(settingsDir, finder);

	if(finder.m_settingsFile.empty())
		return;

	if(loadSettingsInternal(finder.m_settingsFile, _loadPersistentSettings, 0L) && !quiet)
	{
		CColorConsole out;
		out << kBold << "Loaded settings for " << kNormal << kUnderline << path << kNormal << kBold << " successfully..." << kNormal << kNL;
	}
}

static void SaveAsPersistentSettings(const char *name, const char *path)
{
	UStr settingFileName;
	if(!SettingsGetLoc(settingFileName))
		return;

	if(!settingFileName.empty() && !settingFileName.endsWith(kPathDelimiter))
		settingFileName << kPathDelimiter;
	settingFileName << name;
	settingFileName << ".txt";

	if(CPersistent::SaveAllSettings(settingFileName, path))
	{
		CColorConsole out;
		out << kBold << "Saved settings for " << kNormal << kUnderline << path << kNormal << kBold << " successfully..." << kNormal << kNL;
	}
}

void AskCreatePersistentSettings(const char *path)
{
	UStr settingsDir;
	if(!SettingsGetLoc(settingsDir))
		return;

	int id = 0;
#ifdef WIN32
	id = IDD_SAVESETTINGS;
#endif
#ifdef qMacCvsPP
	id = dlg_SaveSettings;
#endif
	CFindSettings finder(path);
	FileTraverse(settingsDir, finder);

	UStr oldName;

	if(!finder.m_settingsFile.empty())
	{
		getSettingName(finder.m_settingsFile, oldName);
	}

	USaveSettings *dlg = NEW USaveSettings(path, oldName);

#if qUnix
	void *wid;
#	if qGTK
	wid = UCreate_SettingsDlg();
#	endif

	UEventSendMessage(dlg->GetWidID(), EV_INIT_WIDGET, kUMainWidget, wid);	
#endif

	if(dlg->DoModal(id))
	{
		UStr name = dlg->m_settingName;
		UStr fileToDelete;

		if(!name.empty())
		{
			if(!oldName.empty())
			{
				if(stricmp(oldName, name) != 0)
				{
					UStr prompt("The setting '");
					prompt << oldName;
					prompt << "' already points on the same folder. ";
					prompt << "Press 'Delete' if you want to overide this setting ";
					prompt << "(two settings cannot point on the same location).";
					if(CvsAlert(prompt, "Cancel", "Delete"))
					{
						delete dlg;
						return;
					}
					fileToDelete = finder.m_settingsFile;
				}
			}

			if(!dlg->m_copyFromPath.empty())
				LoadPersistentSettings(dlg->m_copyFromPath);
			SaveAsPersistentSettings(name, path);

			if(!fileToDelete.empty())
			{
				if(remove(fileToDelete) != 0)
					cvs_err("Error while deleting '%s' (error %d)\n", (const char *)fileToDelete, errno);
			}
		}
	}
	delete dlg;
}

void SavePersistentSettings(const char *path)
{
	UStr settingsDir;
	if(!SettingsGetLoc(settingsDir))
		return;

	CFindSettings finder(path);
	FileTraverse(settingsDir, finder);

	if(finder.m_settingsFile.empty())
		return;

	if(CPersistent::SaveAllSettings(finder.m_settingsFile, finder.m_topfolder))
	{
		CColorConsole out;
		out << kBold << "Saved settings for " << kNormal << kUnderline << path << kNormal << kBold << " successfully..." << kNormal << kNL;
	}
}
#endif // !qMPWCvs
