/*
** 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"
#include "CvsCommands.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(const char* only)
{
	bool found_one = false;
	std::vector<CPersistentEntry>::iterator i;
#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);
	
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry& entry = *i;
		name = entry.fUniqueName;

		Handle hdl = Get1NamedResource(kCVS_prefID, name);
		if( hdl == 0L || (only && strcmp(only, name) != 0) )
			continue;
		
		HLock(hdl);
		found_one = true;
		entry.fValue->SetData(*hdl, GetHandleSize(hdl));
		entry.fValue->ResetTimeStamp();
		HUnlock(hdl);
	}

	// migrate to the new gOldCvsroots container
	if( gOldCvsroots.GetList().size() == 0 && only == 0L )
	{
		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);
#endif /* TARGET_OS_MAC */
#ifdef WIN32
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++i)
	{
		CPersistentEntry & entry = *i;
		BYTE* ppData;
		UINT pBytes;

		if( only && strcmp(only, entry.fUniqueName) != 0 )
			continue;

		if( !AfxGetApp()->GetProfileBinary(PROFILE_NAME, entry.fUniqueName, &ppData, &pBytes))
		{
			continue;
		}

		found_one = true;
		entry.fValue->SetData(ppData, pBytes);
		entry.fValue->ResetTimeStamp();

		delete ppData;
	}
#endif /* WIN32 */
#ifdef qUnix
	CStr home;
	bool error     = false;
	
	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);
		error = true;
	} else {
		fread(&numEntries, sizeof(int), 1, out);
		if( numEntries < 0 )
		{
			cvs_err("Unexpected # %d in the file '%s'\n", numEntries, (char*)home);
			error = true;
		}
	}
	
	/* something has gone wrong !!!! */
	if( error ) {
		fclose(out);
		return false;
	}
	
	for(i = fAllEntries->begin(); i != fAllEntries->end(); ++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);
		
		if( ferror(out) )
			cvs_err("Unexpected error %d in '%s'\n", errno, (char*)home);
				
		if( feof(out) || ferror(out) ) {
			continue;
		}
		
		CPersistentEntry& entry = *i;
		unsigned int size;
		fread(&size, sizeof(unsigned int), 1, out);
		
		alloc.AdjustSize(size);
		fread((char*)alloc, 1, size, out);
		if( only && strcmp(only, entry.fUniqueName) != 0 )
			continue;
		
		found_one = true;
		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);
#endif /* qUnix */
	
	return found_one;
}

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

/*!
	Format the default settings folder string
	\param path Return the formatted settings folder string
	\return true on success, false otherwise
*/
bool FormatDefaultSettingsLoc(CStr& path)
{
#ifdef WIN32
	CWincvsApp* pApp = (CWincvsApp*)AfxGetApp();
	if( !pApp )
	{
		return false;
	}

	pApp->GetAppPath(path);

#elif defined(qUnix)
	struct stat sb;

	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;
	}

#elif defined(qMacCvsPP)
	path = CMacCvsApp::GetMacCvsPrefsPath();
#endif

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

	path << "Settings";

	return true;
}

/*!
	Get the settings folder (location)
	\param path Return the settings folder
	\return true on success, false otherwise
*/
static bool SettingsGetLoc(UStr& path)
{
	struct stat sb;

	path = "";

	if( gCvsPrefs.SettingsFolder() != 0L )
	{
		path = gCvsPrefs.SettingsFolder();
	}
	else
	{
		if( !FormatDefaultSettingsLoc(path) )
		{
			return false;
		}
	}

	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
		strcmp(value, loader->m_pathToFind) == 0
#else
		stricmp(value, loader->m_pathToFind) == 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
