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

// wincvs.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "wincvs.h"

#include "MainFrm.h"
#include "wincvsDoc.h"
#include "GraphDoc.h"
#include "LogFrm.h"
#include "GraphFrm.h"
#include "wincvsView.h"
#include "GraphView.h"
#include "WinCvsBrowser.h"
#include "BrowseFileView.h"
#include "AppGlue.h"
#include "CvsPrefs.h"
#include "AppConsole.h"
#include "Splash.h"
#include "TextBinary.h"
#include "MacrosSetup.h"
#include "MultiString.h"
#include "CvsCommands.h"
#include "Authen.h"
#include "TclGlue.h"
#include "PythonGlue.h"
#include "BrowserBar.h"
#include "FileTraversal.h"
#include "MakeSurePathExists.h"
#include "cvsgui_process.h"

#ifdef SCC_COWORKER
#include "CVSTrace.h"

#include "CVS_Coworker_i.h"
#include "CVS_Coworker_i.c"
#include "CVSCoworker.h"
#endif

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

/// The count of authors
#define AUTHORS_COUNT			3

/// The number of permutations of author's names order
#define AUTHORS_PERMUTATIONS	6

/// Array of authors names
const char* gAuthors[AUTHORS_COUNT] = 
{
	"\t* Alexandre Parenteau <aubonbeurre@hotmail.com>\n", 
	"\t* Jerzy Kaczorowski <kaczoroj@hotmail.com>\n", 
	"\t* Karl-Heinz Bruenen <gcvs@bruenen-net.de>\n"
};

/// Array of indexes to traverse all authors names permutations
const int gAuthorsPermutations[AUTHORS_PERMUTATIONS][AUTHORS_COUNT] = 
{
	0, 1, 2,
	0, 2, 1,
	1, 0, 2,
	1, 2, 0,
	2, 0, 1,
	2, 1, 0
};

#ifdef SCC_COWORKER
CCVS_CoworkerModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CvsCoworker, CvsCoworker)
END_OBJECT_MAP()

LONG CCVS_CoworkerModule::Unlock()
{
    AfxOleUnlockApp();
    return 0;
}

LONG CCVS_CoworkerModule::Lock()
{
    AfxOleLockApp();
    return 1;
}
LPCTSTR CCVS_CoworkerModule::FindOneOf(LPCTSTR p1, LPCTSTR p2)
{
    while (*p1 != NULL)
    {
        LPCTSTR p = p2;
        while (*p != NULL)
        {
            if (*p1 == *p)
                return CharNext(p1);
            p = CharNext(p);
        }
        p1++;
    }
    return NULL;
}
#endif

// to handle auto-logout
class CLogoutThread : public CWinThread
{
public:
	DECLARE_DYNAMIC(CLogoutThread)

	CLogoutThread();
	virtual ~CLogoutThread();
	virtual void Delete();

	void KillThread(void) { m_term = true; }
		// terminate

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CLogoutThread)
	//}}AFX_VIRTUAL

protected:
	bool m_term;

	virtual BOOL InitInstance();

	// Generated message map functions
	//{{AFX_MSG(CLogoutThread)
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
};

CLogoutThread::CLogoutThread() : m_term(false)
{
}

CLogoutThread::~CLogoutThread()
{
}

void CLogoutThread::Delete()
{
	CWinThread::Delete();
}

BOOL CLogoutThread::InitInstance()
{
	for(;;)
	{
		Sleep(1000);

		if(m_term)
			return FALSE;

		CWincvsApp* app = (CWincvsApp*)AfxGetApp();
		if( gAuthen.kind() != pserver || !gCvsPrefs.HasLoogedIn() ||
			gCvsPrefs.LogoutTimeOut() <= 0 || app->IsCvsRunning() )
		{
			continue;
		}

		DWORD time = ::GetTickCount();
		if( app->GetIdleTime() != 0 &&
			(time - app->GetIdleTime()) >= (gCvsPrefs.LogoutTimeOut() * 60 * 1000) )
		{
			app->ResetIdleTime();
			cvs_err("Making automatic logout after %d minute(s):\n", gCvsPrefs.LogoutTimeOut());
			CvsCmdLogout();
		}
	}

	return FALSE;
}

IMPLEMENT_DYNAMIC(CLogoutThread, CWinThread);

BEGIN_MESSAGE_MAP(CLogoutThread, CWinThread)
	//{{AFX_MSG_MAP(CLogoutThread)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

static CLogoutThread *sLogout = 0L;

/////////////////////////////////////////////////////////////////////////////
// CWincvsApp

BEGIN_MESSAGE_MAP(CWincvsApp, CWinApp)
	ON_COMMAND(CG_IDS_TIPOFTHEDAY, ShowTipOfTheDay)
	//{{AFX_MSG_MAP(CWincvsApp)
	ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
	ON_COMMAND(ID_APP_COPYRIGHTS, OnAppCopyrights)
	ON_COMMAND(ID_APP_CREDITS, OnAppCredits)
	ON_UPDATE_COMMAND_UI(ID_APP_COPYRIGHTS, OnLogWindowIsPresent)
	ON_UPDATE_COMMAND_UI(ID_APP_CREDITS, OnLogWindowIsPresent)
	ON_COMMAND(ID_HELP_CVS, OnHelpCvs)
	ON_COMMAND(ID_HELP_CVSCLIENT, OnHelpCvsclient)
	//}}AFX_MSG_MAP
	// Standard file based document commands
	ON_COMMAND(ID_FILE_NEW, OnFileNew)
	ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
	// Standard print setup command
	ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CWincvsApp construction

CWincvsApp::CWincvsApp() : m_isBackgroundProcess(false)
{
	m_CvsRunning = false;
	m_CvsStopping = false;
	m_LastCvsResult = 0;
	m_idletime = 0;
	m_bATLInited = FALSE;
	m_CvsProcess = 0L;
}

void CWincvsApp::OnLogWindowIsPresent(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(GetConsoleView() != 0L);
}

void CWincvsApp::OnFileNew()
{
	gLogTempl->OpenDocumentFile(NULL);
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CWincvsApp object

CWincvsApp theApp;

// This identifier was generated to be statistically unique for your app.
// You may change it if you prefer to choose a specific identifier.

// {D2D77DC2-8299-11D1-8949-444553540000}
static const CLSID clsid =
{ 0xd2d77dc2, 0x8299, 0x11d1, { 0x89, 0x49, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } };

/////////////////////////////////////////////////////////////////////////////
// CWincvsApp initialization

/*
 * The handle is of an unknown type.  Test the validity of this OS
 * handle by duplicating it, then closing the dupe.  The Win32 API
 * doesn't provide an IsValidHandle() function, so we have to emulate
 * it here.  This test will not work on a console handle reliably,
 * which is why we can't test every handle that comes into this
 * function in this way.
 */
bool IsValidHandle( HANDLE handle ) 
{
    HANDLE dupedHandle = NULL;

    /*
     * GetFileType() returns FILE_TYPE_UNKNOWN for invalid handles.
     */

    if( GetFileType(handle) == FILE_TYPE_UNKNOWN )
	{
		if( !DuplicateHandle(GetCurrentProcess(), 
			handle,
			GetCurrentProcess(), 
			&dupedHandle, 
			0, 
			FALSE,
			DUPLICATE_SAME_ACCESS) ) 
		{
			/* 
			* Unable to make a duplicate. It's definately invalid at this point
			*/
			
			return false;
		}
		
		/*
		* Use structured exception handling (Win32 SEH) to protect the close
		* of this duped handle which might throw EXCEPTION_INVALID_HANDLE.
		*/
		__try 
		{
			CloseHandle(dupedHandle);
		}
		__except (EXCEPTION_EXECUTE_HANDLER) 
		{
			/*
			* Definately an invalid handle.  So, therefore, the original
			* is invalid also.
			*/
			
			return false;
		}
	}

	return true;
}

BOOL CWincvsApp::InitInstance()
{
#if qCvsDebug
	int tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
	tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;
	_CrtSetDbgFlag(tmpDbgFlag);
#endif
	CStr lastWorkingDir;

	// David Gravereaux <davygrvy@interwoven.com> suggests this might
	// be a work around to the bug #42 (I love the bug number,
	// really fits the bug description ;-))
#if 0
	if (!IsValidHandle(GetStdHandle(STD_OUTPUT_HANDLE)))
	{
		SetStdHandle(STD_OUTPUT_HANDLE, 0L);
	}
#endif

	// CG: The following block was added by the Splash Screen component.
	{
		CCommandLineInfo cmdInfo;
		ParseCommandLine(cmdInfo);

		CSplashWnd::EnableSplashScreen(
			(!(m_nCmdShow & SW_SHOWMINIMIZED)) && cmdInfo.m_bShowSplash);
	}

	if (!AfxSocketInit())
	{
		AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
		return FALSE;
	}

#ifdef SCC_COWORKER
    if (!InitATL())
        return FALSE;
#endif

	// Initialize OLE libraries
	if (!AfxOleInit())
	{
		AfxMessageBox(IDP_OLE_INIT_FAILED);
		return FALSE;
	}

	AfxEnableControlContainer();

	// Standard initialization
	// If you are not using these features and wish to reduce the size
	//  of your final executable, you should remove from the following
	//  the specific initialization routines you do not need.

#if _MFC_VER < 0x700
#ifdef _AFXDLL
	Enable3dControls();			// Call this when using MFC in a shared DLL
#else
	Enable3dControlsStatic();	// Call this when linking to MFC statically
#endif
#endif

	InitializeCriticalSection(&CCvsThreadLock::m_lock);

	// Change the registry key under which our settings are stored.
	// You should modify this string to be something appropriate
	// such as the name of your company or organization.
	SetRegistryKey(_T("WinCvs"));

	LoadStdProfileSettings(5);  // Load standard INI file options (including MRU)
	gCvsPrefs.load();
	
	CSplashWnd::EnableSplashScreen(!gCvsPrefs.DisableSplashScreen());

    CStr strVersion;
    GetAppVersion(strVersion);
    WriteProfileString(_T(""), _T("Current Version"), strVersion);

	//
	// We need to make a temp local copy of LastWorkingDir property here, 
	// because CWinCvsBrowser::OnSelchanging event would override 
	// LastWorkingDir preference when browser window is created.
	//
	lastWorkingDir = gCvsPrefs.LastWorkingDir(); 

	// Register the application's document templates.  Document templates
	//  serve as the connection between documents, frame windows and views.

	gLogTempl = new CMultiDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CWincvsDoc),
		RUNTIME_CLASS(CLogFrame),
		RUNTIME_CLASS(CBrowseFileView));
	AddDocTemplate(gLogTempl);

	gLogGraph = new CMultiDocTemplate(
		IDR_GRAPH,
		RUNTIME_CLASS(CGraphDoc),
		RUNTIME_CLASS(CGraphFrame),
		RUNTIME_CLASS(CGraphView));
	AddDocTemplate(gLogGraph);

	/*gLogHtml = new CMultiDocTemplate(
		IDR_GRAPH,
		RUNTIME_CLASS(CDocument),
		RUNTIME_CLASS(CMDIChildWnd),
		RUNTIME_CLASS(CGraphView));
	AddDocTemplate(gLogGraph);*/

	// Connect the COleTemplateServer to the document template.
	//  The COleTemplateServer creates new documents on behalf
	//  of requesting OLE containers by using information
	//  specified in the document template.
	m_server.ConnectTemplate(clsid, gLogTempl, TRUE);
		// Note: SDI applications register server objects only if /Embedding
		//   or /Automation is present on the command line.

	// Register all OLE server factories as running.  This enables the
	//  OLE libraries to create objects from other applications.
	COleTemplateServer::RegisterAll();
		// Note: MDI applications register all server objects without regard
		//  to the /Embedding or /Automation on the command line.

	// create main MDI Frame window
	m_mainFrame = new CMainFrame;
	if (!m_mainFrame->LoadFrame(IDR_CNTR_INPLACE))
		return FALSE;
	m_pMainWnd = m_mainFrame;

	// Initialize the cool menus...
#if 0
	UINT barIds[] = {IDR_BROWSER, IDR_MAINFRAME, IDR_MULTIUSER, IDR_TAGS, IDR_FILELIST, IDR_FILTER};
	m_mainFrame->InitializeMenu(gLogTempl,
		IDR_MAINFRAME, barIds, _countof(barIds), IDR_MAINFRAME);
	m_mainFrame->InitializeMenu(gLogGraph,
		IDR_GRAPH, barIds, _countof(barIds), IDR_GRAPH);
#endif

	// Parse command line for standard shell commands, DDE, file open
	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);

	// Check to see if launched as OLE server
	if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
	{
#if 0
		// Register all OLE server (factories) as running.  This enables the
		//  OLE libraries to create objects from other applications.
		COleTemplateServer::RegisterAll();
#endif

		// Application was run with /Embedding or /Automation.  Don't show the
		//  main window in this case.
		return TRUE;
	}

	// When a server application is launched stand-alone, it is a good idea
	//  to update the system registry in case it has been damaged.
	m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
	COleObjectFactory::UpdateRegistryAll();

	//JK - try to resolve the long filename if the argument passed is the short filename (like when it's a call thru SendTo shell menu)
	if( -1 != cmdInfo.m_strFileName.Find('~') )
	{
		CString strLongFileName;
		CString strTempTileName = cmdInfo.m_strFileName;

		SHFILEINFO sFileInfo;
		int nSlashPos = -1;

		BOOL bError = FALSE;

		while( -1 != (nSlashPos = strTempTileName.ReverseFind(kPathDelimiter)) )
		{
			if( SHGetFileInfo((LPCTSTR)strTempTileName, 0, &sFileInfo, sizeof(sFileInfo), SHGFI_DISPLAYNAME) )
			{
				CString strFileNameBit = strTempTileName.Mid(nSlashPos + 1);

				strLongFileName = sFileInfo.szDisplayName + strLongFileName;
				strLongFileName = kPathDelimiter + strLongFileName;
				strTempTileName = strTempTileName.Left(nSlashPos);
			}
			else
			{
				bError = TRUE;
				break;	//we need it or we get the dead loop
			}
		}//end while
		
		if( !bError )
		{
			cmdInfo.m_strFileName = strTempTileName + strLongFileName;
		}
	}
				
	//JK - open in the directory, so remove the file
	CString strOpenDirectory;
	CString strSelectFile;
	if( !cmdInfo.m_strFileName.IsEmpty() )
	{
		TCHAR fullpath[_MAX_PATH];
		_tfullpath(fullpath, cmdInfo.m_strFileName, _MAX_PATH);
		if( GetFileAttributes(cmdInfo.m_strFileName) != 0xFFFFFFFF )
		{
			cmdInfo.m_strFileName.Replace('/', kPathDelimiter);	//normalize path
			TCHAR drive[_MAX_DRIVE];
			TCHAR dir[_MAX_DIR];
			_tsplitpath(fullpath, drive, dir, NULL, NULL);
			strSelectFile = fullpath;
			_tmakepath(fullpath, drive, dir, NULL, NULL);
			strOpenDirectory = fullpath;
		}
	}

	// Dispatch commands specified on the command line
	//JK - we need to trick the ProcessShellCommand a bit
	//because we might get a directory as a parameter we don't want it to check after the file...
	cmdInfo.m_nShellCommand = CCommandLineInfo::FileNew;
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;

	// The one and only window has been initialized, so show and update it.
	m_mainFrame/*m_pMainWnd*/->InitialShowWindow(m_nCmdShow);
	m_mainFrame/*m_pMainWnd*/->UpdateWindow();

	m_mainFrame/*m_pMainWnd*/->DragAcceptFiles();
	//CWincvsView *cons = GetConsoleView();
	//if(cons != 0L)
	//	cons->DragAcceptFiles();


	CWinCvsBrowser *browser = GetBrowserView();
	CWincvsDoc *doc = (CWincvsDoc *)GetDocument();
	if(doc != 0L && browser != 0L)
		doc->SetTitle(browser->GetRoot());
	CBrowseFileView *fileView = GetFileView();
	if(fileView != 0L)
	{
		if( strOpenDirectory.IsEmpty() )
		{
			// Goto the last working directory, which was selected
			// when WinCVS was closed
			if( !lastWorkingDir.empty() && browser->StepToLocation(lastWorkingDir) )
			{
				fileView->ResetView(lastWorkingDir, false, (LPCTSTR) 0L);
				gCvsPrefs.SetLastWorkingDir(lastWorkingDir);
			}
			else
 				fileView->ResetView(browser->GetRoot());
		}
		else
		{
			CColorConsole out;
			out << kBold << "Locating: " << kNormal << kUnderline << cmdInfo.m_strFileName << kNormal <<  kNormal << kNL;

			//search the browse location (will create a new location if not found)
			extern CMString gHistoryLocs;
			CString bestRoot = CHistComboBoxEx::FindBestRoot(gHistoryLocs, strOpenDirectory);
			if( !bestRoot.IsEmpty() )
			{
				browser->ResetBrowser((LPCTSTR)bestRoot);
			}

			if( browser->StepToLocation((LPCTSTR)strOpenDirectory) )
			{
				fileView->ResetView( (LPCTSTR)strOpenDirectory, false, (LPCTSTR)strSelectFile );
			}
			else
			{
				fileView->ResetView( browser->GetRoot() );
			}
		}
	}

	CPython_Interp::InitPythonSupport();

	//MacrosReloadAll();
	PyMacrosReloadAll();

	//JK - if you launch WinCvs with the file or directory as a parameter you obviously don't need a "tip of the day" because you're in a hurry
	if( strOpenDirectory.IsEmpty() )
	{
		// CG: This line inserted by 'Tip of the Day' component.
		ShowTipAtStartup();
	}

	// auto-logout thread
	sLogout = new CLogoutThread();
	if (sLogout == 0L)
	{
		cvs_err("Impossible to create the auto-logout thread !\n");
	}
	else
	{
		ASSERT_VALID(sLogout);

		// Create Thread in a suspended state so we can set the Priority 
		// before it starts getting away from us
		if (!sLogout->CreateThread(CREATE_SUSPENDED))
		{
			cvs_err("Impossible to start the auto-logout thread (error %d)!\n", GetLastError());
			delete sLogout;
			sLogout = 0L;
		}

		if(sLogout != 0L)
		{
			// If you want to make the sample more sprightly, set the thread priority here 
			// a little higher. It has been set at idle priority to keep from bogging down 
			// other apps that may also be running.
			VERIFY(sLogout->SetThreadPriority(THREAD_PRIORITY_NORMAL));
			// Now the thread can run wild
			sLogout->ResumeThread();
		}
	}

	// load preferences. If CVSROOT is not defined
	// then prompt first preferences dialog.
	if(gCvsPrefs.empty())
		SendMessage(*m_mainFrame, WM_COMMAND, ID_APP_PREFERENCES, 0L);
		//doc->OnAppPreferences();
	else
		cvs_out("CVSROOT: %s (%s)\n", (const char *)gCvsPrefs, gAuthen.kindstr());

	bool hasTCL = CTcl_Interp::IsAvail();
	bool hasPY = CPython_Interp::IsAvail();

	if(gCvsPrefs.GetUseShell() == SHELL_PYTHON && !hasPY && hasTCL)
	{
		gCvsPrefs.SetUseShell(SHELL_TCL);
	}
	else if(gCvsPrefs.GetUseShell() == SHELL_TCL && hasPY && !hasTCL)
	{
		gCvsPrefs.SetUseShell(SHELL_PYTHON);
	}

	if(gCvsPrefs.GetUseShell() == SHELL_TCL)
	{
		cvs_out("TCL is available, shell is enabled : help (select and press enter)\n");
	}

	if(!hasTCL && !hasPY)
	{
		cvs_err("TCL or Python are *not* available, shell is disabled\n");
	}

	//make sure the current directory is properly setup
	if( 0L != fileView )
	{
		chdir( fileView->GetPath() );
	}

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	CCJHyperLink	m_CvsGuiSfUrl;
	CCJHyperLink	m_WinCvsUrl;
	CString	m_vers;
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	virtual BOOL OnInitDialog();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	m_vers = _T("WinCvs ");
	//}}AFX_DATA_INIT

	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	CStr vers;

	if(app->GetAppVersion(vers))
	{
		m_vers = vers;
	}
}

BOOL CAboutDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	m_WinCvsUrl.SetURL(_T("http://www.wincvs.org"));
	m_CvsGuiSfUrl.SetURL(_T("http://cvsgui.sourceforge.net/"));
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	DDX_Control(pDX, IDC_CVSGUISFURL, m_CvsGuiSfUrl);
	DDX_Control(pDX, IDC_WINCVSURL, m_WinCvsUrl);
	DDX_Text(pDX, IDC_WINCVSVER, m_vers);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CWincvsApp::OnAppAbout()
{
	CAboutDlg aboutDlg;
	aboutDlg.DoModal();
}

CWincvsView *CWincvsApp::GetConsoleView(void)
{
	if(m_mainFrame == 0L)
		return 0L;

	return m_mainFrame->GetConsoleView();
}

CStatusBar *CWincvsApp::GetStatusBar()
{
	if(m_mainFrame == 0L)
		return 0L;

	return m_mainFrame->GetStatusBar();
}

CWinCvsBrowser *CWincvsApp::GetBrowserView(void)
{
	if(m_mainFrame == 0L)
		return 0L;

	return m_mainFrame->GetBrowserView();
}

CString CWincvsApp::GetRoot()
{
    CString root;
    CWinCvsBrowser* view = GetBrowserView();
    if (view)
    {
        root = view->GetRoot();
    }
    return root;
}

void CWincvsApp::ActivateBrowserView(void)
{
	if(m_mainFrame == 0L)
		return;

	m_mainFrame->GetWorkspace()->SetActiveView(RUNTIME_CLASS(CBrowserBar));
}

CCJShellTree *CWincvsApp::GetExplorerView(void)
{
	if(m_mainFrame == 0L)
		return 0L;

	return m_mainFrame->GetExplorerView();
}

CBrowseFileView *CWincvsApp::GetFileView(void)
{
	POSITION post = GetFirstDocTemplatePosition();
	CDocTemplate* tmpl;
	while(post != NULL)
	{
		tmpl = GetNextDocTemplate(post);
		if(!tmpl->IsKindOf(RUNTIME_CLASS(CMultiDocTemplate)))
			continue;
		POSITION posd = tmpl->GetFirstDocPosition();
		CDocument* doc;
		while(posd != NULL)
		{
			doc = tmpl->GetNextDoc(posd);
			if(!doc->IsKindOf(RUNTIME_CLASS(CWincvsDoc)))
				continue;
			POSITION posv = doc->GetFirstViewPosition();
			CView* view;
			while(posv != NULL)
			{
				view = doc->GetNextView(posv);
				if(view->IsKindOf(RUNTIME_CLASS(CBrowseFileView)))
					return (CBrowseFileView *)view;
			}
		}
	}
	return NULL;
}

CDocument *CWincvsApp::GetDocument(void)
{
	CWinApp *app = AfxGetApp();
	POSITION post = app->GetFirstDocTemplatePosition();
	CDocTemplate* tmpl;
	while(post != NULL)
	{
		tmpl = app->GetNextDocTemplate(post);
		POSITION posd = tmpl->GetFirstDocPosition();
		CDocument* doc;
		while(posd != NULL)
		{
			doc = tmpl->GetNextDoc(posd);
			if(!doc->IsKindOf(RUNTIME_CLASS(CWincvsDoc)))
				continue;
			return doc;
		}
	}
	ASSERT(0);
	return 0L;
}

/////////////////////////////////////////////////////////////////////////////
// CWincvsApp commands

/// Trim the trailing spaces
/// if the bTest is true false then return: true if it was trimmed, false if not trimmed
/// if bTest is true then the return: true is there are trailing spaces, false if not
bool TrimRight( char* szValue, bool bTest = false )
{
	bool bRes = false;

	char* pszPos = szValue;
	char* pszLastSpacePos = NULL;

	//locate last white space
	while( *pszPos != '\0' )
	{
		if( isspace(*pszPos) )
		{
			if( pszLastSpacePos == NULL )
			{
				pszLastSpacePos = pszPos;
			}
		}
		else
		{
			pszLastSpacePos = NULL;
		}

		pszPos++;
	}

	//and cut it off
	if( pszLastSpacePos )
	{
		if( !bTest )
		{
			*pszLastSpacePos = '\0';
		}
		bRes = true;
	}

	return bRes;
}

/// Check the path is valid and prompt to create if not (depending on the promptForCreation parameter)
void AFXAPI DDV_CheckPathExists(CDataExchange* pDX, int nIDC, CString& value, bool promptForCreation /*= true*/)
{
	if( pDX->m_bSaveAndValidate )
	{
		if( !CompatMakeSurePathExists(value, false) )
		{
			if( CStr(value).endsWith(kPathDelimiter) )
			{
				value.TrimRight(kPathDelimiter);
			}

			if( !CompatMakeSurePathExists(value, promptForCreation) )
			{
				AfxMessageBox("Cannot find folder: " + value);
				pDX->PrepareEditCtrl(nIDC);
				pDX->Fail();
			}
		}
	}
}

/// Check whether the CVSROOT entered is valid, correct if not...
void AFXAPI DDV_CheckCVSROOT(CDataExchange* pDX, CString& value)
{
	//check after the trailing white spaces - often an unwanted result of copy-paste
	if( pDX->m_bSaveAndValidate && 
		TrimRight(value.GetBuffer(value.GetLength()+1), true) )
	{
		value.ReleaseBuffer();
		
		if( AfxMessageBox("The CVSROOT value contains the trailing spaces. Do you want to trim them?", MB_ICONQUESTION | MB_YESNO) == IDYES )
		{
			TrimRight(value.GetBuffer(value.GetLength()+1));
			value.ReleaseBuffer();
		}
	}
}

/// \note If pDX passed as NULL then the combo is populated
void AFXAPI DDX_ComboMString(CDataExchange* pDX, int nIDC, CSmartComboBox& combo)
{
	CMString* mstr = combo.GetItems();
	ASSERT(mstr != NULL);

	if( mstr )
	{
		if( !pDX || !pDX->m_bSaveAndValidate )
		{
			CString strOldText;
			combo.GetWindowText(strOldText);
			
			combo.ResetContent();

			const vector<CStr> & list = mstr->GetList();
			vector<CStr>::const_iterator i;
			for(i = list.begin(); i != list.end(); ++i)
			{
				combo.AddString(*i);
			}
			
			combo.SetWindowText(strOldText);
		}
		else
		{
			CString value;
			DDX_CBString(pDX, nIDC, value);
			if( !value.IsEmpty() )
			{
				mstr->Insert(value);
			}
		}
	}
}
	
/// Verify the entry has at least nChars characters
void AFXAPI DDV_MinChars(CDataExchange* pDX, CString const& value, int nChars)
{
	ASSERT(nChars > 0);        // allow them something
	if (pDX->m_bSaveAndValidate && value.GetLength() < nChars)
	{
		TCHAR szT[255];
		wsprintf(szT, _T("Please enter more than %d character"), nChars);
		CString prompt;
		AfxMessageBox(szT, MB_ICONEXCLAMATION, AFX_IDP_PARSE_STRING_SIZE);
		prompt.Empty(); // exception prep
		pDX->Fail();
	}
}

int CWincvsApp::ExitInstance() 
{
	// Save existing preferences. This will save, for example,
	// the current working directory as "LastWorkingDir" property.
	gCvsPrefs.save();
	
	DeleteCriticalSection(&CCvsThreadLock::m_lock);
	
    if (m_bATLInited)
    {
#ifdef SCC_COWORKER
        LogCleanup();
        _Module.RevokeClassObjects();
        _Module.Term();
#endif
        CoUninitialize();
    }

	return CWinApp::ExitInstance();
}

void CWincvsApp::OnAppCopyrights() 
{
	cvs_out("\nBoth WinCVS and CVS are distributed under the terms of\n");
	cvs_out("the GNU General Public Licence (GPL).\n\n");
	cvs_out("* MacCVS/WinCvs : maintained by Alexandre Parenteau (aubonbeurre@hotmail.com)\n");
	cvs_out("* WinCvs documentation : Copyright © 1999 Don Harper <don@lamrc.com>.\n");
	cvs_out("* CVS : Copyright © 1989-1998 Brian Berliner, david d `zoo' zuhn,\n");
	cvs_out("        Jeff Polk, and other authors\n");
	cvs_out("* CJLIB : Copyright © 1998-99 Kirk Stowell <kstowell@codejockeys.com>\n");
	cvs_out("* Kerberos : Copyright © 1997 the Massachusetts Institute of Technology\n");
	cvs_out("* TCL : Copyright © Sun Microsystems Inc.\n");
	cvs_out("* Python : Copyright © Guido van Rossum\n");
	cvs_out("* MFC : Copyright © 1992-1997 Microsoft Corporation\n");
	cvs_out("\nSome others copyrights may apply, check the source code for details.\n");
}

void CWincvsApp::OnAppCredits() 
{
	// WinCvs version
	CStr vers;

	if( GetAppVersion(vers) )
		cvs_out("\n%s - Client/Local\n", (const char*)vers);
	else
		cvs_out("\nWinCvs 1.3 - Client/Local\n");

	// Authors in random order
	cvs_out("- WinCvs is developed by:\n");
	
	srand((unsigned)time(NULL));
	const int option = rand() % AUTHORS_PERMUTATIONS;
	
	for(int index = 0; index < AUTHORS_COUNT; index++)
	{
		cvs_out(gAuthors[gAuthorsPermutations[option][index]]);
	}

	cvs_out("\t* Other contributors (see the ChangeLog)\n");
	
	// More info
	cvs_out("- WinCvs page : http://www.wincvs.org or http://cvsgui.sourceforge.net/\n");
	cvs_out("- cvs page : http://www.cvshome.org\n");
	cvs_out("- WinCvs documentation : Don Harper <don@lamrc.com>.\n");
	cvs_out("- WinHelp cvs documentation : Norbert Klamann.\n");
	
	cvs_out("- Special thanks to :\n");
	cvs_out("\t* The Strata Inc. developpers who are so patients with WinCvs\n");
	cvs_out("\t  and help to identify a lot of problems.\n");
	cvs_out("\t* All the people who are submitting bugs, patches and\n");
	cvs_out("\t  answer the cvsgui mailing list.\n");
	
	cvs_out("- Bug reports, suggestions :\n");
	cvs_out("\t* Richard Wesley, Alain Aslag Roy, Eric Aubourg and many others\n");
	cvs_out("- Cvs contributors :\n");
	cvs_out("\t* Too many to be listed here, see the 'ChangeLog' instead !\n");
}

void CWincvsApp::ShowTipAtStartup(void)
{
	// CG: This function added by 'Tip of the Day' component.

	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);
	if (cmdInfo.m_bShowSplash)
	{
		CTipDlg dlg( IsWindow(m_pMainWnd->GetSafeHwnd()) ? m_pMainWnd : NULL );
		if (dlg.m_bStartup)
			dlg.DoModal();
	}
}

void CWincvsApp::ShowTipOfTheDay(void)
{
	// CG: This function added by 'Tip of the Day' component.

	CTipDlg dlg;
	dlg.DoModal();
}

BOOL CWincvsApp::PreTranslateMessage(MSG* pMsg)
{
	// CG: The following lines were added by the Splash Screen component.
	if (CSplashWnd::PreTranslateAppMessage(pMsg))
		return TRUE;

	DWORD time = ::GetTickCount();
	if (m_idletime == 0 ||
		pMsg->message == WM_KEYDOWN ||
	    pMsg->message == WM_SYSKEYDOWN ||
	    pMsg->message == WM_LBUTTONDOWN ||
	    pMsg->message == WM_RBUTTONDOWN ||
	    pMsg->message == WM_MBUTTONDOWN ||
	    pMsg->message == WM_NCLBUTTONDOWN ||
	    pMsg->message == WM_NCRBUTTONDOWN ||
	    pMsg->message == WM_NCMBUTTONDOWN)
	{
		m_idletime = time;
		PyInvalUICache();
	}

	IdleCvsProgress();

	return CWinApp::PreTranslateMessage(pMsg);
}

void CWincvsApp::GetAppPath(CStr & path) const
{
	path = "";
	HINSTANCE hInst = ::AfxGetInstanceHandle();
	if(hInst != 0L)
	{
		char apath[512];
		DWORD len = ::GetModuleFileName(hInst, apath, 512);
		CStr uppath;
		CStr exefile;
		if(len > 0 && ::SplitPath(apath, uppath, exefile))
		{
			path = uppath;
		}
	}
}

void CWincvsApp::GetAppModule(CStr & module) const
{
	module = "";
	HINSTANCE hInst = ::AfxGetInstanceHandle();
	if(hInst != 0L)
	{
		char apath[512];
		DWORD len = ::GetModuleFileName(hInst, apath, 512);
		if(len > 0)
		{
			module = apath;
		}
	}
}

void CWincvsApp::LookForResourceFile(const char *what, CStr & path)
{
	path = "";
	GetAppPath(path);

	CString appPath = path;
	// first the macro folder may be ./macro
	CString test = appPath;
	test.TrimRight('\\');
	test += "\\";
	test += what;

	struct stat sb;
	if(stat(test, &sb) != -1 && S_ISREG(sb.st_mode))
	{
		path = test;
		return;
	}

	CStr uppath, file;
	while(SplitPath(appPath, uppath, file) && !uppath.empty())
	{
		test = uppath;
		test.TrimRight('\\');
		test += "\\";
		test += what;

		if(stat(test, &sb) != -1 && S_ISREG(sb.st_mode))
		{
			path = test;
			return;
		}
		appPath = uppath;
	}
	path = test;
}


void CWincvsApp::OnHelpCvs() 
{
	CStr hlp;
	LookForResourceFile("cvs.hlp", hlp);
	::WinHelp(*AfxGetMainWnd(), hlp, HELP_CONTENTS, 0L);
}

void CWincvsApp::OnHelpCvsclient() 
{
	CStr hlp;
	LookForResourceFile("cvscli.hlp", hlp);
	::WinHelp(*AfxGetMainWnd(), hlp, HELP_CONTENTS, 0L);
}

bool CWincvsApp::GetAppVersion(CStr & vers) const
{
	bool bRet=false;
	char  szFullPath[_MAX_PATH]; 
    DWORD len = ::GetModuleFileName(AfxGetInstanceHandle(), szFullPath, sizeof(szFullPath)); 
	DWORD dwVerHnd; 
    DWORD dwVerInfoSize = GetFileVersionInfoSize(szFullPath, &dwVerHnd); 
    if (dwVerInfoSize && len)
    { 
        // If we were able to get the information, process it: 
        HANDLE  hMem; 
        LPVOID  lpvMem; 
        char    szGetName[256]; 
        int     cchRoot; 
 
        hMem = GlobalAlloc(GMEM_MOVEABLE, dwVerInfoSize); 
        lpvMem = GlobalLock(hMem); 
        GetFileVersionInfo(szFullPath, dwVerHnd, dwVerInfoSize, lpvMem); 
        lstrcpy(szGetName, "\\StringFileInfo\\040904b0\\"); 
        cchRoot = lstrlen(szGetName); 
 
        BOOL  fRet; 
        UINT  cchVer = 0; 
        LPVOID lszVer = NULL; 

        lstrcpy(&szGetName[cchRoot], "ProductVersion");
        fRet = VerQueryValue(lpvMem, szGetName, &lszVer, &cchVer); 

        if (fRet && cchVer && lszVer) 
        { 
            vers = (char *)lszVer;
            bRet = true;
        }
        GlobalUnlock(hMem); 
        GlobalFree(hMem); 
    }
	return bRet;
}

BOOL CWincvsApp::SaveAllModified() 
{
	// prevent from exiting when cvs is running
	if( m_CvsRunning )
	{
		m_CvsStopping = true;
		return TRUE;
	}

	if( sLogout != 0L )
	{
		HANDLE hdl = sLogout->m_hThread;
		sLogout->KillThread();
		WaitForSingleObject(hdl, INFINITE);
		//delete sLogout;
		sLogout = 0L;
	}

	// auto-logout
	if( gAuthen.kind() == pserver && gCvsPrefs.HasLoogedIn() &&
		gCvsPrefs.LogoutTimeOut() > 0 )
	{
		cvs_err("Making automatic logout before quitting:\n");
		CvsCmdLogout();
		WaitForCvs();
	}

	return CWinApp::SaveAllModified();
}

char *CWincvsApp::GetEnvMainThread(const char *name)
{
	return m_mainFrame->GetEnvMainThread(name);
}

void CWincvsApp::IdleCvsProgress(void)
{
	static bool bWasCvsRunning = false;	//to restore the idle message once CVS has stoped

	if( !m_CvsRunning )
	{
		if( bWasCvsRunning )
		{
			bWasCvsRunning = false;

			CString strIdleString;
			strIdleString.LoadString(AFX_IDS_IDLEMESSAGE);

			CStatusBar *bar = GetStatusBar();
			if(bar != 0L)
				bar->SetWindowText(strIdleString);
		}

		return;
	}

	if( m_CvsProcess != 0L )
	{
		if( !cvs_process_is_active(m_CvsProcess) )
		{
			m_CvsRunning = false;
			m_CvsStopping = false;
			m_CvsProcess = 0L;
			GetFileView()->CheckChanges();
		}
	}

	bWasCvsRunning = true;

	DWORD whatTimeIsIt = ::GetTickCount();
	static DWORD lastTime = 0;
	// let's make some stuff every second while cvs is idling
	if( (whatTimeIsIt - lastTime) < 1000 )
		return;

	lastTime = whatTimeIsIt;
	char path[_MAX_PATH];
	getcwd(path, _MAX_PATH);
	CStatusBar* bar = GetStatusBar();

	if( bar != 0L )
		bar->SetWindowText(path);
}

static inline bool MatchToken(LPCTSTR sLine,LPCTSTR sToken)
{
    int n = _tcslen(sToken);
    if (0 != _tcsnicmp(sLine,sToken,n)) {
        return false;
    }
    LPCTSTR p = sLine + n;
    return ((!*p) || (0 != _tcschr(_T(" \t"),*p)));
}

#ifdef _DEBUG
static inline void DEBUG_MESSAGE(LPCTSTR sMessage)
{
    if (::getenv("NOCVSDEBUG")) return;
    TCHAR s[256];
    ::wsprintf(s,_T("\n[PID %u THREAD %u] CVS_Coworker %s\n"),
        (unsigned) GetCurrentProcessId(),
        (unsigned) GetCurrentThreadId(),
        sMessage
        );
    ::MessageBox(0,s,_T("DEBUG"),MB_OK);
}
#else
#define DEBUG_MESSAGE(S) 0
#endif

BOOL CWincvsApp::InitATL()
{
    m_bATLInited = TRUE;

#if _WIN32_WINNT >= 0x0400
    HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
    HRESULT hRes = CoInitialize(NULL);
#endif

    if (FAILED(hRes))
    {
        m_bATLInited = FALSE;
        return FALSE;
    }

#ifdef SCC_COWORKER
	_Module.Init(ObjectMap, AfxGetInstanceHandle());
    _Module.dwThreadID = GetCurrentThreadId();

    LPTSTR lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
    TCHAR szTokens[] = _T("-/");
    BOOL bRun = TRUE;

    LPCTSTR lpszToken = _Module.FindOneOf(lpCmdLine, szTokens);
    while (lpszToken != NULL)
    {
        if (MatchToken(lpszToken,_T("UnregServer"))) {
            _Module.UpdateRegistryFromResource(IDR_CVS_COWORKER, FALSE);
            _Module.UnregisterServer(TRUE); //TRUE means typelib is unreg'd
            bRun = FALSE;
            DEBUG_MESSAGE(_T("Now UNREGISTERED"));
            break;
        }
        if (MatchToken(lpszToken,_T("RegServer"))) {
            //RegisterLogging();  // specific to CVS_Coworker
            _Module.UpdateRegistryFromResource(IDR_CVS_COWORKER, TRUE);
            _Module.RegisterServer(TRUE);
            bRun = FALSE;
            DEBUG_MESSAGE(_T("REGISTERED"));
            break;
        }
        lpszToken = _Module.FindOneOf(lpszToken, szTokens);
    }

    if (!bRun)
    {
        m_bATLInited = FALSE;
        _Module.Term();
        CoUninitialize();
        return FALSE;
    }

    //StartLogging();         // specific to CVS_Coworker
    hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,REGCLS_MULTIPLEUSE);
    if (FAILED(hRes))
    {
        m_bATLInited = FALSE;
        CoUninitialize();
        return FALSE;
    }
#endif

    return TRUE;
}

// retrieve filtering model to use
KiFilterModel* CWincvsApp::GetFilterModel() const
{
	return m_mainFrame->GetFilterModel();
}

// retrieve recursion model to use
KiRecursionModel* CWincvsApp::GetRecursionModel() const
{
	return m_mainFrame->GetRecursionModel();
}

KiIgnoreModel* CWincvsApp::GetIgnoreModel() const
{
	return m_mainFrame->GetIgnoreModel();
}

void CWincvsApp::UpdateAllDocsView()
{
	POSITION pos = GetFirstDocTemplatePosition();
	while( pos )
	{
		CDocTemplate* pTemplate = GetNextDocTemplate(pos); 
		POSITION pos2 = pTemplate->GetFirstDocPosition();
		while(pos2)
		{
			CDocument* pDocument;
			if(pDocument = pTemplate->GetNextDoc(pos2))
			{
				pDocument->UpdateAllViews(NULL);
			}
		}
	}
}

void CWincvsApp::PeekPumpAndIdle(bool doIdle)
{
	MSG Msg;
	while( ::PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE) )
	{
		PumpMessage();
	}
	
	if( doIdle )
	{
		LONG lIdle = 0;
		while( OnIdle(lIdle++) )
			;
	}
}
