/*
** 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> --- December 1997
 */

/*
 * CvsPrefs.cpp --- class to handle CVS settings
 */

#include "stdafx.h"

#include <string.h>
#include <stdlib.h>
#include "CvsPrefs.h"
#include "Authen.h"
#include "AppConsole.h"
#include "CvsArgs.h"
#include "MultiString.h"
#include "FileTraversal.h"

#ifdef macintosh
#	define PCVS "\pCVSROOT"
#	include <Resources.h>
#	include <Folders.h>
#	include <Errors.h>
#	include <TextUtils.h>
#	include "VolsPaths.h"
#	include "MacMisc.h"
#	include "AppGlue.h"
#endif /* macintosh */

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

#ifdef HAVE_PWD_H
#	include <pwd.h>
#endif

#ifdef WIN32
#	include <process.h>
#	include "WinCvsBrowser.h"
#	include "wincvs.h"
#	include "TextBinary.h"
#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

#ifdef qMacCvsPP
#	include "GUSIInternal.h"
#	include "GUSIFileSpec.h"
#	include "MacCvsApp.h"
#	include <UProcess.h>
#endif

#if qUnix
#	include "UCvsApp.h"
#	include "UCvsFolders.h"
#endif

CvsPrefs gCvsPrefs;

static CPersistentBool gCheckoutRO("P_CheckoutRO", true, kAddSettings);
static CPersistentBool gZ9Option("P_Z9Option", false, kAddSettings);
static CPersistentBool gPruneOption("P_PruneOption", true, kAddSettings);
static CPersistentBool gQuietOption("P_QuietOption", false);
static CPersistentInt gIsoConvert("P_NIsoConvert", ISO8559_none, kAddSettings);
static CPersistentBool gICTextOption("P_ICTextOption", false, kAddSettings);
static CPersistentBool gAddBinControl("P_AddBinControl", true);
static CPersistentBool gUseViewerAlways("P_UseViewerAlways", false, kAddSettings);
static CPersistentInt gMacBinEncoding("P_NMacBinEncoding", MAC_HQX, kAddSettings);
static CPersistentInt gUseShell("P_UseShell", SHELL_TCL);

static PCPStr gCvsroot("P_Cvsroot");
static PCPStr gCvsroot1("P_Cvsroot1");
static PCPStr gCvsroot2("P_Cvsroot2");
static PCPStr gCvsroot3("P_Cvsroot3");
static PCPStr gCvsroot4("P_Cvsroot4");
static PCPStr gCvsroot5("P_Cvsroot5");
static PCPStr gCvsroot6("P_Cvsroot6");
static PCPStr gCvsroot7("P_Cvsroot7");
static PCPStr gCvsroot8("P_Cvsroot8");
static PCPStr gCvsroot9("P_Cvsroot9");
static PCPStr gCvsroot10("P_Cvsroot10");
static PCPStr gCvsroot11("P_Cvsroot11");
static PCPStr gCvsroot12("P_Cvsroot12");
static PCPStr gCvsroot13("P_Cvsroot13");
static PCPStr gCvsroot14("P_Cvsroot14");
static PCPStr gCvsroot15("P_Cvsroot15");
static PCPStr gCvsroot16("P_Cvsroot16");
static PCPStr gCvsroot17("P_Cvsroot17");
static PCPStr gCvsroot18("P_Cvsroot18");
static PCPStr gCvsroot19("P_Cvsroot19");
static PCPStr gCvsroot20("P_Cvsroot20");

static CPersistentInt gKserverPort("P_KserverPort", -1, kAddSettings);
static CPersistentInt gPserverPort("P_PserverPort", -1, kAddSettings);
static CPersistentInt gRhostPort("P_RhostPort", -1, kAddSettings);
static PCPStr gServerName("P_ServerName", 0L, kAddSettings);
static PCStr gRshName("P_RshName", 0L, kAddSettings);
#ifndef qUnix
static PCStr gHome("P_Home", 0L, kAddSettings);
#endif /* !qUnix */
#ifdef qUnix
static PCStr gViewer("P_Viewer", "emacs"); // not vi ;-)
#else
static PCStr gViewer("P_Viewer", "Notepad");
#endif
static PCStr gHelper("P_Helper", "netscape");
static PCStr gBrowser("P_Browser", "gmc");
#ifdef qUnix
static PCStr gExtdiff("P_Extdiff", "emacs");
#else
static PCStr gExtdiff("P_Extdiff", "");
#endif
static CPersistentBool gDirtySupport("P_DirtySupport", true, kAddSettings);
static CPersistentBool gAlwaysUseCvsroot("P_AlwaysUseCvsroot", false, kAddSettings);
static CPersistentInt gLogoutTimeOut("P_LogoutTimeOut", 0);
static PCStr gProxyHost("P_ProxyHost", "www.mydomain", kAddSettings);
static CPersistentInt gProxyPort("P_ProxyPort", 8888, kAddSettings);
static CPersistentBool gUseProxy("P_UseProxy", false, kAddSettings);
static CPersistentBool gUnixLF("P_UnixLF", false, kAddSettings);
static CPersistentInt gZLevel("P_ZLevel", 9, kAddSettings);
static CPersistentInt gCvsVersion("P_CvsVersion", 0, kAddSettings);
static CPersistentBool gCvsConsole("P_CvsConsole", false, kAddSettings);
static CPersistentInt gWordWrapLogMsg("P_WordWrapLogMsg", 0, kAddSettings);
static PCStr gLastWorkingDir("P_LastWorkingDir", 0L, kAddSettings);

static CPersistentBool gHideCommandDlgUpdate("P_HideCommandDlgUpdate", false);
static CPersistentBool gHideCommandDlgDiff("P_HideCommandDlgDiff", false);
static CPersistentBool gHideCommandDlgLog("P_HideCommandDlgLog", false);
static CPersistentBool gHideCommandDlgGraph("P_HideCommandDlgGraph", false);
static CPersistentBool gHideCommandDlgAnnotate("P_HideCommandDlgAnnotate", false);
static CPersistentBool gHideCommandDlgUseShift("P_HideCommandDlgUseShift", true);

bool CvsPrefs::CheckoutRO(void) const {return gCheckoutRO;}
bool CvsPrefs::Z9Option(void) const
{
#ifdef macintosh
	// because of the problem with LSH...
	return true;
#endif
	return gZ9Option;
}
bool CvsPrefs::PruneOption(void) const {return gPruneOption;}
bool CvsPrefs::QuietOption(void) const {return gQuietOption;}
ISO8559 CvsPrefs::IsoConvert(void) const {return (ISO8559)(int)gIsoConvert;}
bool CvsPrefs::ICTextOption(void) const {return gICTextOption;}
bool CvsPrefs::AddControl(void) const {return gAddBinControl;}
bool CvsPrefs::UseViewerAlways(void) const {return gUseViewerAlways;}
MACENCODING CvsPrefs::MacBinEncoding(void) const {return (MACENCODING)(int)gMacBinEncoding;}
int CvsPrefs::KserverPort(void) const {return gKserverPort;}
int CvsPrefs::PserverPort(void) const {return gPserverPort;}
int CvsPrefs::RhostPort(void) const {return gRhostPort;}

static CKeyString gWhichCvs(10, "P_WhichCvs");

const char * CvsPrefs::ServerName(void) const
{
	return gServerName.empty() ? 0L : (const char *)gServerName;
}
const char * CvsPrefs::RshName(void) const
{
	return gRshName.empty() ? 0L : (const char *)gRshName;
}
const char * CvsPrefs::Home(void) const
{
#ifndef qUnix
#	ifdef WIN32
	// Windows 98 doesn't like C:\/.cvspass
	if(!gHome.empty() && gHome.endsWith(kPathDelimiter))
	{
		gHome[gHome.length() - 1] = '\0';
	}
#	endif
	return gHome.empty() ? 0L : (const char *)gHome;
#else /* qUnix */
	static CStr home;
	if (!home.empty())
		return home;

	char *env = getenv ("HOME");
	struct passwd *pw;
	if (env)
		home = env;
	else if ((pw = (struct passwd *) getpwuid (getuid ()))
			 && pw->pw_dir)
		home = pw->pw_dir;
	else
		return 0L;

	return home;
#endif /* qUnix */
}
const char * CvsPrefs::Viewer(void) const
{
	return gViewer.empty() ? 0L : (const char *)gViewer;
}
const char * CvsPrefs::Helper(void) const
{
	return gHelper.empty() ? 0L : (const char *)gHelper;
}
const char * CvsPrefs::Browser(void) const
{
	return gBrowser.empty() ? 0L : (const char *)gBrowser;
}
const char * CvsPrefs::ExtDiff(void) const
{
	return gExtdiff.empty() ? 0L : (const char *)gExtdiff;
}
bool CvsPrefs::DirtySupport(void) const {return gDirtySupport;}
int CvsPrefs::LogoutTimeOut(void) const {return gLogoutTimeOut;}
bool CvsPrefs::AlwaysUseCvsroot(void) const {return gAlwaysUseCvsroot;}
const char *CvsPrefs::ProxyHost(void) const {return gProxyHost;}
int CvsPrefs::ProxyPort(void) const {return gProxyPort;}
bool CvsPrefs::UseProxy(void) const {return gUseProxy;}
bool CvsPrefs::UnixLF(void) const {return gUnixLF;}
int CvsPrefs::ZLevel(void) const {return gZLevel;}
int CvsPrefs::CvsVersion(void) const {return gCvsVersion;}
bool CvsPrefs::CvsConsole(void) const {return gCvsConsole;}
int CvsPrefs::WordWrapLogMsg(void) const {return gWordWrapLogMsg;}
SHELL_KIND CvsPrefs::GetUseShell(void) const {return (SHELL_KIND)(int)gUseShell;}

/* Return the current working dir (last working dir).   */
/* This directory is automatically selected when WinCVS */
/* is opened the next time. */
const char * CvsPrefs::LastWorkingDir(void) const
{
	return gLastWorkingDir.empty() ? 0L : (const char *)gLastWorkingDir;
}

bool CvsPrefs::HideCommandDlgUpdate(void) const {return gHideCommandDlgUpdate;}
bool CvsPrefs::HideCommandDlgDiff(void) const {return gHideCommandDlgDiff;}
bool CvsPrefs::HideCommandDlgLog(void) const {return gHideCommandDlgLog;}
bool CvsPrefs::HideCommandDlgGraph(void) const {return gHideCommandDlgGraph;}
bool CvsPrefs::HideCommandDlgAnnotate(void) const {return gHideCommandDlgAnnotate;}
bool CvsPrefs::HideCommandDlgUseShift(void) const {return gHideCommandDlgUseShift;}

void CvsPrefs::SetCheckoutRO(bool newState) {gCheckoutRO = newState;}
void CvsPrefs::SetZ9Option(bool newState) {gZ9Option = newState;}
void CvsPrefs::SetPruneOption(bool newState) {gPruneOption = newState;}
void CvsPrefs::SetQuietOption(bool newState) {gQuietOption = newState;}
void CvsPrefs::SetIsoConvert(ISO8559 newState) {gIsoConvert = (int)newState;}
void CvsPrefs::SetICTextOption(bool newState) {gICTextOption = newState;}
void CvsPrefs::SetAddControl(bool newState) {gAddBinControl = newState;}
void CvsPrefs::SetUseViewerAlways(bool newState) {gUseViewerAlways = newState;}
void CvsPrefs::SetMacBinEncoding(MACENCODING newState) {gMacBinEncoding = (int)newState;}
void CvsPrefs::SetKserverPort(int newState) {gKserverPort = newState;}
void CvsPrefs::SetPserverPort(int newState) {gPserverPort = newState;}
void CvsPrefs::SetRhostPort(int newState) {gRhostPort = newState;}
void CvsPrefs::SetServerName(const char *newservername) {gServerName = newservername;}
void CvsPrefs::SetRshName(const char *newrshname) {gRshName = newrshname;}
void CvsPrefs::SetHome(const char *newhome)
{
#ifndef qUnix
	gHome = newhome;
#	ifdef WIN32
	// Windows 98 doesn't like C:\/.cvspass
	if(!gHome.empty() && gHome.endsWith(kPathDelimiter))
	{
		gHome[gHome.length() - 1] = '\0';
	}
#	endif
#endif /* !qUnix */
}
void CvsPrefs::SetViewer(const char *newviewer) {gViewer = newviewer;}
void CvsPrefs::SetBrowser(const char *newviewer) {gBrowser = newviewer;}
void CvsPrefs::SetHelper(const char *newviewer) {gHelper = newviewer;}
void CvsPrefs::SetExtDiff(const char *newdiff) {gExtdiff = newdiff;}
void CvsPrefs::SetDirtySupport(bool newState) {gDirtySupport = newState;}
void CvsPrefs::SetLogoutTimeOut(int newTimeout) {gLogoutTimeOut = newTimeout;}
void CvsPrefs::SetAlwaysUseCvsroot(bool newState) {gAlwaysUseCvsroot = newState;}
void CvsPrefs::SetUseProxy(bool useit) {gUseProxy = useit;}
void CvsPrefs::SetUnixLF(bool useit) {gUnixLF = useit;}
void CvsPrefs::SetZLevel(int level) {gZLevel = level;}
void CvsPrefs::SetProxyHost(const char *newhost) {gProxyHost = newhost;}
void CvsPrefs::SetProxyPort(int newport) {gProxyPort = newport;}
void CvsPrefs::SetCvsVersion(int vers) {gCvsVersion = vers;}
void CvsPrefs::SetCvsConsole(bool console) {gCvsConsole = console;}
void CvsPrefs::SetWordWrapLogMsg(int wordwrap) {gWordWrapLogMsg = wordwrap;}
void CvsPrefs::SetLastWorkingDir(const char *workdir) {gLastWorkingDir = workdir;}
void CvsPrefs::SetUseShell(SHELL_KIND shell) {gUseShell = shell;}

void CvsPrefs::SetHideCommandDlgUpdate(bool newState) {gHideCommandDlgUpdate = newState;}
void CvsPrefs::SetHideCommandDlgDiff(bool newState) {gHideCommandDlgDiff = newState;}
void CvsPrefs::SetHideCommandDlgLog(bool newState) {gHideCommandDlgLog = newState;}
void CvsPrefs::SetHideCommandDlgGraph(bool newState) {gHideCommandDlgGraph = newState;}
void CvsPrefs::SetHideCommandDlgAnnotate(bool newState) {gHideCommandDlgAnnotate = newState;}
void CvsPrefs::SetHideCommandDlgUseShift(bool newState) {gHideCommandDlgUseShift = newState;}

bool CvsPrefs::empty(void) const
{
	return gCvsroot.empty();
}

int CvsPrefs::length(void) const
{
	return gCvsroot.length();
}

CvsPrefs::operator const char *() const
{
	return (const char *)gCvsroot;
}

CvsPrefs::operator const unsigned char *() const
{
	return (const unsigned char *)gCvsroot;
}

const char *CvsPrefs::operator=(const char *newstr)
{
	return gCvsroot = newstr;
}

const unsigned char *CvsPrefs::operator=(const unsigned char *newstr)
{
	return gCvsroot = newstr;
}

CPStr & CvsPrefs::get_cvsroot_list(int entry)
{
	switch(entry)
	{
	case 0:
		return gCvsroot1;
		break;
	case 1:
		return gCvsroot2;
		break;
	case 2:
		return gCvsroot3;
		break;
	case 3:
		return gCvsroot4;
		break;
	case 4:
		return gCvsroot5;
		break;
		break;
	case 5:
		return gCvsroot6;
		break;
	case 6:
		return gCvsroot7;
		break;
	case 7:
		return gCvsroot8;
		break;
	case 8:
		return gCvsroot9;
		break;
	case 9:
		return gCvsroot10;
		break;
	case 10:
		return gCvsroot11;
		break;
	case 11:
		return gCvsroot12;
		break;
	case 12:
		return gCvsroot13;
		break;
	case 13:
		return gCvsroot14;
		break;
	case 14:
		return gCvsroot15;
		break;
	case 15:
		return gCvsroot16;
		break;
	case 16:
		return gCvsroot17;
		break;
	case 17:
		return gCvsroot18;
		break;
	case 18:
		return gCvsroot19;
		break;
	case 19:
		return gCvsroot20;
		break;
	}
	return gCvsroot1;
}

bool CvsPrefs::has_cvsroot_in_list(const char *cvsroot, int & entry)
{
	for(int i = 0; i < NUM_CVSROOT; i++)
	{
		CPStr & str = get_cvsroot_list(i);
		if(str.empty())
			continue;
		if(strcmp(str, cvsroot) == 0)
		{
			entry = i;
			return true;
		}
	}
	return false;
}

void CvsPrefs::insert_cvsroot_in_list(const char *cvsroot)
{
	int entry;
	
	if(has_cvsroot_in_list(cvsroot, entry))
	{
		// move to the first entry
		CPStr tmp;
		tmp = get_cvsroot_list(0);
		get_cvsroot_list(0) = get_cvsroot_list(entry);
		get_cvsroot_list(entry) = tmp;
	}
	else
	{
		// push back the entry
		for(int i = (NUM_CVSROOT - 1); i >= 1; i--)
		{
			get_cvsroot_list(i) = get_cvsroot_list(i - 1);
		}
		get_cvsroot_list(0) = cvsroot;
	}
}

void CvsPrefs::flush_list(void)
{
	for(int i = 0; i < NUM_CVSROOT; i++)
	{
		get_cvsroot_list(i) = "";
	}
}

void CvsPrefs::save(void)
{
	if(empty())
		return;
	
	bool needSave = CPersistent::NeedSave();
	bool needSaveSettings = CPersistent::NeedSave(true);
	if(!needSave)
		return;

	// update the CVSROOT list
	insert_cvsroot_in_list((const char *)*this);
	
	CPersistent::SaveAll();

	if(!needSaveSettings)
		return;

#ifdef WIN32
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	SavePersistentSettings(app->GetBrowserView()->GetRoot());
#endif
#ifdef qMacCvsPP
	CMacCvsApp::SavePersistentSettings();
#endif /* qMacCvsPP */
#if qUnix
	UCvsFolders *browser = UCvsApp::gApp->GetBrowserView();
	if(browser != 0L && browser->GetRoot() != 0L)
		SavePersistentSettings(browser->GetRoot());
#endif
}

void CvsPrefs::load(void)
{
	// flush the CVSROOT list, it will be filled later
	flush_list();
	
	bool loaded = CPersistent::LoadAll();

	if( loaded )
		return;

	// backward compatibility
#ifdef WIN32
	UINT value;

	CString cstr = AfxGetApp()->GetProfileString(PROFILE_NAME, "CVSROOT");
	if(!cstr.IsEmpty())
		*this = (const char *)cstr;

	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "CheckoutRO", -1)) != -1)
		SetCheckoutRO(value ? 1 : 0);
	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "Z9Option", -1)) != -1)
		SetZ9Option(value ? 1 : 0);
	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "QuietOption", -1)) != -1)
		SetQuietOption(value ? 1 : 0);
	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "PruneOption", -1)) != -1)
		SetPruneOption(value ? 1 : 0);
	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "Authentication", -1)) != -1)
		gAuthen.setkind((AuthenKind)value);
	if((value = AfxGetApp()->GetProfileInt(PROFILE_NAME, "AddControl", -1)) != -1)
		SetAddControl(value ? 1 : 0);

	char tmp[20];
	for(int i = 0; i < NUM_CVSROOT; i++)
	{
		sprintf(tmp, "CVSROOTLIST%d", i);
		cstr = AfxGetApp()->GetProfileString(PROFILE_NAME, tmp);
		if(!cstr.IsEmpty())
			get_cvsroot_list(i) = (const char *)cstr;
	}
#endif /* WIN32 */

#if defined(macintosh) && !TARGET_API_MAC_CARBON
	FSSpec theFolder;
	short FRef;
	Handle hdl;
	OSErr err;
	Str255 thePath;
	
	err = MacGetPrefsFolder(theFolder, thePath);
	if(err != noErr)
		return;

	err = HSetVol(NULL, theFolder.vRefNum, theFolder.parID);
	FRef = OpenResFile(PCVS);
	if(FRef < 0)
		return;

	hdl = GetResource('CVS ', 128);
	if(hdl != NULL)
	{
		Str255 str;
		HLock(hdl);
		BlockMove(*hdl, str, (*hdl)[0] + 1);
		str[str[0] + 1] = '\0';
		*this = (const char *)str + 1;
		HUnlock(hdl);
	}
	hdl = GetResource('CHRO', 128);
	if(hdl != NULL)
	{
		SetCheckoutRO(**hdl);
	}
	hdl = GetResource('Z9OP', 128);
	if(hdl != NULL)
	{
		SetZ9Option(**hdl);
	}
	hdl = GetResource('QUIE', 128);
	if(hdl != NULL)
	{
		SetQuietOption(**hdl);
	}
	hdl = GetResource('POPT', 128);
	if(hdl != NULL)
	{
		SetPruneOption(**hdl);
	}
	hdl = GetResource('AUTH', 128);
	if(hdl != NULL)
	{
		gAuthen.setkind((AuthenKind)**hdl);
	}
	hdl = GetResource('ISOC', 128);
	if(hdl != NULL)
	{
		SetIsoConvert((ISO8559)**hdl);
	}
	hdl = GetResource('ICTO', 128);
	if(hdl != NULL)
	{
		SetICTextOption(**hdl);
	}
	hdl = GetResource('ACTL', 128);
	if(hdl != NULL)
	{
		SetAddControl(**hdl);
	}

	for(int i = 0; i < NUM_CVSROOT; i++)
	{
		hdl = GetResource('CROL', 128 + i);
		if(hdl != NULL)
		{
			HLock(hdl);
			get_cvsroot_list(i) = (const char *)*hdl;
			HUnlock(hdl);
		}
	}
	CloseResFile (FRef);
#endif /* macintosh && !TARGET_API_MAC_CARBON */
}

#ifdef WIN32
kWinVersion CvsPrefs::WhichVersion()
{
	DWORD dwVersion = GetVersion(); // Get major and minor version numbers of Windows
	DWORD dwWindowsMajorVersion =  (DWORD)(LOBYTE(LOWORD(dwVersion)));
	DWORD dwWindowsMinorVersion =  (DWORD)(HIBYTE(LOWORD(dwVersion)));
	DWORD dwBuild;

	// Get build numbers for Windows NT or Win32s
	if (dwVersion < 0x80000000)                // Windows NT
	{
		dwBuild = (DWORD)(HIWORD(dwVersion));
		return dwWindowsMajorVersion == 3 ? kWinNT35 : kWinNT4;
	}
	else if (dwWindowsMajorVersion < 4)        // Win16s
	{
		dwBuild = (DWORD)(HIWORD(dwVersion));
		return kWin16;
	}
	
	// Windows 95 -- No build numbers provided    dwBuild =  0;
	return kWin32;
}
#endif /* WIN32 */


//
// TODO: If we sometimes implement multi-threaded CVS/TCL macro 
// execution then we have to implement thread synchronization in
// IsXXXRunning and SetXXXRunning methods.
//
// NOTE! IsCvsRunning and SetCvsRunning methods are not yet
// used in Mac/Unix/WinCVS. Each of these version have had
// their own app.gCvsRunning variable. I haven't cleaned up
// the code and changed them to use this "one and only" 
// CvsRunning methods. 
//
bool CvsPrefs::IsCvsRunning(void) const
{ 
	return(fCvsRunningCounter > 0); 
}

int  CvsPrefs::SetCvsRunning(bool launched)  
{ 
	if(launched) 
		fCvsRunningCounter++;
	else if(fCvsRunningCounter > 0) 
		fCvsRunningCounter--;

	return fCvsRunningCounter;		
}

bool CvsPrefs::IsTclFileRunning(void) const
{ 
	return(fTclFileRunningCounter > 0); 
}

int  CvsPrefs::SetTclFileRunning(bool launched)  
{ 
	if(launched) 
		fTclFileRunningCounter++;
	else if(fTclFileRunningCounter > 0) 
		fTclFileRunningCounter--;

	return fTclFileRunningCounter;		
}


bool LaunchDiff(const char *f1, const char *f2)
{
#ifdef qMacCvsPP
	const	OSType		kBBEditDiffCreatorCode 	= FOUR_CHAR_CODE('BBds');
	const	OSType		kResourcererCreatorCode = FOUR_CHAR_CODE('Doug');

	//	Resourcerer's Compare suite
	const	DescType	kAECompareClass = 'Comp';
	const	DescType	kAECompareEvent = 'Comp';
		const	DescType	keyNewer		= 'kNew';
		const	DescType	keyOlder		= 'kOld';
		const	DescType	keyShowDiff		= 'kShD';
		const	DescType	keyReturnDiff	= 'kRtD';
	
	OSErr				e = noErr;
	
	try {
		//	Convert to file specs
		Boolean				inShowDiffs = true;
		FSSpec				spec1 = GUSIFileSpec (f1);
		FSSpec				spec2 = GUSIFileSpec (f2);

		//	Find the file type
		FInfo	info;
		e = ::FSpGetFInfo (&spec2, &info);
		ThrowIfOSErr_(e);
		
		//	Decide on the application
		OSType				applCreator = FOUR_CHAR_CODE('????');
		switch (info.fdType) {
			case 'TEXT':
				applCreator = kBBEditDiffCreatorCode;
				break;
				
			default:
				applCreator = kResourcererCreatorCode;
				break;
			} // switch
		
		//	Try to launch it
		LaunchFlags			launchFlags = launchContinue + launchNoFileFlags;
		OSType				applType = FOUR_CHAR_CODE('APPL');
		FSSpec				applSpec;
		ProcessSerialNumber	applPSN;
		if (!UProcess::Launch (applCreator, applType, launchFlags, &applSpec, &applPSN)) Throw_(fnfErr);
		
		//	Now, diff the files.  We use the Resourcerer suite and a BBEdit glue applet.
			//	Create the parameters
		StAEDescriptor	newerDesc (spec1);
		StAEDescriptor	olderDesc (spec2);
		StAEDescriptor	showDesc (inShowDiffs);
		
			//	Create the event
		StAEDescriptor	addressDesc (typeProcessSerialNumber, &applPSN, sizeof (applPSN));
		
		StAEDescriptor	diffEvent;
		e = ::AECreateAppleEvent (kAECompareClass, kAECompareEvent, addressDesc, kAutoGenerateReturnID, kAnyTransactionID, diffEvent);
		ThrowIfOSErr_(e);
		
			//	Add the parameters
		e = ::AEPutParamDesc (diffEvent, keyNewer, newerDesc);
		ThrowIfOSErr_(e);
		e = ::AEPutParamDesc (diffEvent, keyOlder, olderDesc);
		ThrowIfOSErr_(e);
		e = ::AEPutParamDesc (diffEvent, keyShowDiff, showDesc);
		ThrowIfOSErr_(e);
		
			//	Send the event
		UAppleEventsMgr::SendAppleEvent (diffEvent);
		
		return true;
		} // try
		
	catch (LException ex) {
		} // catch
#endif
	if(gCvsPrefs.ExtDiff() == 0L)
	{
		cvs_err("The external diff program is not defined,\n");
		cvs_err("you need to set it in the preferences dialog.\n");
		return false;
	}
#ifdef WIN32
	CString args;
	CStr program, file1, file2;
	if(strchr(gCvsPrefs.ExtDiff(), ' ') != 0L)
	{
		program << '\"';
		program << gCvsPrefs.ExtDiff();
		program << '\"';
	}
	else
		program = gCvsPrefs.ExtDiff();

	if(strchr(f1, ' ') != 0L)
	{
		file1 << '\"';
		file1 << f1;
		file1 << '\"';
	}
	else
		file1 = f1;

	if(strchr(f2, ' ') != 0L)
	{
		file2 << '\"';
		file2 << f2;
		file2 << '\"';
	}
	else
		file2 = f2;

	args += file1;
	args += " ";
	args += file2;

	HINSTANCE hInst = ShellExecute(*AfxGetMainWnd(), NULL, gCvsPrefs.ExtDiff(), args,
						NULL, SW_SHOWDEFAULT);

	if((UINT)hInst < 32)
	{
		// ExtDiff app was not found. Let's try to execute it from
		// the application path. However, this is done only when ExtDiff
		// value doesn't contain absolute path ("c:\subdir\" or "\subdir\").
		if(strchr(gCvsPrefs.ExtDiff(), ':') == 0L && (gCvsPrefs.ExtDiff()[0]) != '\\')
		{
			CStr appPath;

			((CWincvsApp *)AfxGetApp())->GetAppPath(appPath);
			if(!appPath.endsWith(kPathDelimiter))
				appPath << kPathDelimiter;

			appPath << gCvsPrefs.ExtDiff();
		
			hInst = ShellExecute(*AfxGetMainWnd(), NULL, appPath, args,
					NULL, SW_SHOWDEFAULT);
		}
	}

	if((UINT)hInst < 32)
	{
		LPVOID lpMsgBuf;
		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_FROM_SYSTEM |
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR) &lpMsgBuf,
			0,
			NULL
			);
		cvs_err("Unable to open '%s' (%s), using console instead\n",
			(char *)gCvsPrefs.ExtDiff(), (LPCSTR)lpMsgBuf);
		// Free the buffer.
		LocalFree( lpMsgBuf );
		return false;
	}

	return true;
#elif qUnix
	CvsArgs args(false);
	args.add(gCvsPrefs.ExtDiff());
	args.add(f1);
	args.add(f2);
	UCvsApp::gApp->Execute(args.Argc(), args.Argv());
	return true;
#else
	return false;
#endif
}

bool LaunchViewer(const char *fullpath,bool useDefault /*= false*/)
{
#ifdef WIN32
	bool done = false;
	
	if( !gCvsPrefs.UseViewerAlways() )
	{
		char viewer[_MAX_PATH];
		UStr buf;
		CStr uppath, folder;
		SplitPath(fullpath, uppath, folder);
		
		HINSTANCE hInst = FindExecutable(fullpath, folder, viewer);
		if(!useDefault && (UINT)hInst >= 32)
		{
			// see Article ID: Q140724 for the problem desc of FindExecutable
			struct stat sb;
			HINSTANCE hInst;
			if (stat(viewer, &sb) != -1 && S_ISREG(sb.st_mode))
			{
				CStr fullpathQuote;
				if(strchr(fullpath, ' ') != 0L)
				{
					fullpathQuote << '\"';
					fullpathQuote << fullpath;
					fullpathQuote << '\"';
				}
				else
					fullpathQuote = fullpath;
				
				hInst = ShellExecute(*AfxGetMainWnd(), "open", viewer, fullpathQuote,
					folder, SW_SHOWDEFAULT);
			}
			else
				hInst = ShellExecute(*AfxGetMainWnd(), "open", fullpath, 0L,
					folder, SW_SHOWDEFAULT);

			if((long)hInst < 32)
			{
				cvs_err("Unable to open '%s' (error %d), using default viewer instead\n", (char *)fullpath, GetLastError());
			}
			else
				done = true;
		} 
	}

	if(!done)
	{
		if(gCvsPrefs.Viewer() == 0L)
		{
			cvs_err("You need to define a default viewer for this file in the preferences\n");
			return false;
		}

		const char * argv[3] = {0L, 0L, 0L};
		CStr program, file;
		if(strchr(gCvsPrefs.Viewer(), ' ') != 0L)
		{
			program << '\"';
			program << gCvsPrefs.Viewer();
			program << '\"';
		}
		else
			program = gCvsPrefs.Viewer();
		if(strchr(fullpath, ' ') != 0L)
		{
			file << '\"';
			file << fullpath;
			file << '\"';
		}
		else
			file = fullpath;
		argv[0] = program;
		argv[1] = file;
		int process = _spawnvp(_P_NOWAIT, gCvsPrefs.Viewer(), argv);
	}

	return true;
#endif /* WIN32 */
	
#ifdef qMacCvsPP
	return true;
#endif /* qMacCvsPP */
	
#if qUnix
	return true;
#endif // qUnix
}

bool LaunchEditor(const char* tmpFile, void* par /*= 0L*/)
{
	CStr program, file;
	if(strchr(gCvsPrefs.Viewer(), ' ') != 0L)
	{
		program << '\"';
		program << gCvsPrefs.Viewer();
		program << '\"';
	}
	else
		program = gCvsPrefs.Viewer();
	if(strchr(tmpFile, ' ') != 0L)
	{
		file << '\"';
		file << tmpFile;
		file << '\"';
	}
	else
		file = tmpFile;

	CStr command;
	command << program;
	command << " ";
	command << file;

#ifdef WIN32
	STARTUPINFO sStartupInfo;
	PROCESS_INFORMATION sProcessInformation;

	// Execute the command with a call to the CreateProcess API call.
	memset( &sStartupInfo, 0, sizeof(sStartupInfo) );
	sStartupInfo.cb = sizeof( sStartupInfo );
	sStartupInfo.dwFlags = STARTF_USESHOWWINDOW;
	sStartupInfo.wShowWindow = SW_SHOWNORMAL;
	
	if( !CreateProcess( NULL,
		(char*)command,
		NULL,
		NULL,
		FALSE,
		0,
		NULL,
		NULL, 
		&sStartupInfo,
		&sProcessInformation) )
	{
		sProcessInformation.hProcess = INVALID_HANDLE_VALUE;
		sProcessInformation.hThread = INVALID_HANDLE_VALUE;
	}
	
	*((PROCESS_INFORMATION*)par) = sProcessInformation;
	return sProcessInformation.hProcess != INVALID_HANDLE_VALUE;
#endif /* WIN32 */

#ifdef qMacCvsPP
	return true;
#endif /* qMacCvsPP */
	
#if qUnix
	return true;
#endif // qUnix
}

void CvsPrefs::SetWhichCvs(const char *cvsPath)
{
	UStr appPath;

#ifdef WIN32
	HINSTANCE hInst = ::AfxGetInstanceHandle();
	char apath[512];
	DWORD len;
	if(hInst != 0L && (len = ::GetModuleFileName(hInst, apath, 512)) > 0)
	{
		CString tmp(apath);
		tmp.MakeLower();
		appPath = tmp;
	}
#endif

	if(!appPath.empty())
	{
		UStr value;
		gWhichCvs.Concatenate(value, appPath, cvsPath);
		gWhichCvs.Insert(value);
	}
}

const char *CvsPrefs::WhichCvs(UStr & path, bool userOnly) const
{
	path = "";

	UStr appPath;

#ifdef WIN32
	HINSTANCE hInst = ::AfxGetInstanceHandle();
	char apath[512];
	DWORD len;
	if(hInst != 0L && (len = ::GetModuleFileName(hInst, apath, 512)) > 0)
	{
		CString tmp(apath);
		tmp.MakeLower();
		appPath = tmp;
	}
#endif

	// search if beside the default cvs, the user defined one to use
	// with this application only.
	UStr candidate;
	if(!appPath.empty())
	{
		UStr value;
		gWhichCvs.Concatenate(value, appPath, "");
		TMString<UStr>::list_t::const_iterator i = gWhichCvs.Find(value);
		if(i != gWhichCvs.end())
		{
			UStr key;
			gWhichCvs.Split(*i, key, candidate);
		}
	}

	// verify this cvs exe is still there
	struct stat sb;
	if(!candidate.empty() && stat(candidate, &sb) != -1 && S_ISREG(sb.st_mode))
	{
		path = candidate;
		return path.c_str();
	}

	// fallback to the default one.
	if(userOnly)
		return 0L;

#ifdef WIN32
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();

	app->GetAppPath(path);
	if(!path.endsWith(kPathDelimiter))
		path << kPathDelimiter;
	path << (gCvsPrefs.WhichVersion() == kWin32 ? "cvs95.exe" : "cvs.exe");
#endif

	return path.c_str();
}
