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

/*
 * AppGlue.cpp --- glue code to access CVS services
 */

#include "stdafx.h"

#ifdef macintosh
#	include <TextUtils.h>
#	include <Strings.h>
#	include <Dialogs.h>
#	include <unistd.h>
#	include <errno.h>
#	include <console.h>
#	include <ctype.h>
#	include "mktemp.h"
#	include "dll_process.h"
#	include "AppGlue.mac.h"
#	include "MacMisc.h"
#endif /* macintosh */

#ifdef qMacCvsPP
#	include "MacCvsApp.h"
#	include "LogWindow.h"
#	include "MacBinEncoding.h"
#	include "MacCvsAE.h"
#endif /* qMacCvsPP */

#ifdef WIN32
#	include "wincvs.h"
#	include "wincvsView.h"
#	include "PromptFiles.h"
#	include "BrowseFileView.h"
#	include <process.h>
#	include <direct.h>

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

#if !TARGET_RT_MAC_CFM
#	include "cvsgui_process.h"
#endif

#if qUnix
#	include "UCvsApp.h"
#	include "UCvsFiles.h"
#	include <ctype.h>
#endif /* qUnix */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#ifdef HAVE_ERRNO_H
#	include <errno.h>
#endif

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

#include <vector>

#include "Authen.h"
#include "CvsPrefs.h"
#include "AppGlue.h"
#include "AppConsole.h"
#include "GetPassword.h"
#include "AskYesNo.h"
#include "CvsArgs.h"
#include "FileTraversal.h"

// overide console
CCvsConsole * gConsole = 0L;
static int sLastExitCode;

#ifdef _MSC_VER
#	define CVS_EXTERN_C extern "C"
#else
#	define CVS_EXTERN_C
#endif

#if !TARGET_RT_MAC_CFM
static CvsProcessCallbacks sCallTable = {
	glue_consoleout,
	glue_consoleerr,
	glue_getenv,
	glue_exit
};
#endif

CVS_EXTERN_C void glue_exit(int code)
{
	if(!gCvsPrefs.IsTclFileRunning())
		cvs_out("\n*****CVS exited normally with code %d*****\n\n", code);
	sLastExitCode = code;
}

CVS_EXTERN_C long glue_consoleout(char *txt, long len)
{
	if(gConsole != 0L)
		return gConsole->cvs_out(txt, len);
	
#ifdef macintosh
	// apple event output
	long newlen;
	if((newlen = ae_consoleout(txt, len)) != -1)
		return newlen;
#endif

	cvs_outstr(txt, len);

	return len;
}

CVS_EXTERN_C long glue_consoleerr(char *txt, long len)
{
	if(gConsole != 0L)
		return gConsole->cvs_err(txt, len);
	
#ifdef macintosh
	// apple event output
	long newlen;
	if((newlen = ae_consoleout(txt, len)) != -1)
		return newlen;
#endif

	cvs_errstr(txt, len);

	return len;
}

CVS_EXTERN_C long glue_consolein(char *txt, long len)
{
#ifdef macintosh
	// apple event input
	long newlen;
	if((newlen = ae_consolein(txt, len)) != -1)
		return newlen;
#endif /* !macintosh */

	return 0;
}

#ifdef macintosh
#	define isGlobalEnvVariable(name) ( \
		strcmp(name, "CVS_PSERVER_PORT") == 0 || \
		strcmp(name, "CVS_RCMD_PORT") == 0 || \
		strcmp(name, "CVS_SERVER") == 0 || \
		strcmp(name, "CVS_RSH") == 0 || \
		strcmp(name, "CVS_RSA_IDENTITY") == 0 || \
		strcmp(name, "HOME") == 0 || \
		strcmp(name, "ISO8859") == 0 || \
		strcmp(name, "IC_ON_TXT") == 0 || \
		strcmp(name, "MAC_DEFAULT_RESOURCE_ENCODING") == 0 || \
		strcmp(name, "MAC_BINARY_TYPES_SINGLE") == 0 || \
		strcmp(name, "MAC_BINARY_TYPES_HQX") == 0 || \
		strcmp(name, "MAC_BINARY_TYPES_PLAIN") == 0 || \
		strcmp(name, "CVSUSEUNIXLF") == 0 || \
		strcmp(name, "DIRTY_MCP") == 0 \
		)
#endif /* macintosh */

CVS_EXTERN_C const char *glue_getenv(char *name)
{
	if(name == NULL)
		return NULL;
	
#ifdef WIN32
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(::GetCurrentThreadId() != app->m_nThreadID &&
		(strcmp(name, "CVS_GETPASS") == 0 || strcmp(name, "CVSLIB_YESNO") == 0 ||
		strcmp(name, "HOME") == 0))
	{
		// synchonize it since the thread calls and
		// we call some MFC objects in it (prompt
		// password, get path...)
		CCvsThreadLock threadLock;

		return app->GetEnvMainThread(name);
	}
#endif /* WIN32 */

#ifdef macintosh
	char *result;
	if((result = ae_getenv(name)) != (char *)-1)
	{
		if(result == 0L && isGlobalEnvVariable(name))
		{
			// in this case only we let continue
			// to get the MacCVS settings. So the CWCVS
			// users (or any AppleEvent user) can set
			// these variables in MacCVS but still can overide
			// them using Apple Events.
		}
		else
			return result;
	}
#endif /* macintosh */
	
	// new : give a chance for the new AuthenModel to react to the getenv
	// callback
	AuthenModel *curModel = AuthenModel::GetInstance(gAuthen.kind());
	std::vector<AuthenModel *> & allInstances = AuthenModel::GetAllInstances();
	std::vector<AuthenModel *>::const_iterator i;

	if(curModel != 0L)
	{
		const char *res = curModel->OnGetEnv(name);
		if(res != 0L)
			return res;
	}

	for(i = allInstances.begin(); i != allInstances.end(); ++i)
	{
		AuthenModel *model = *i;

		if(model == curModel)
			continue;

		const char *res = model->OnGetEnv(name);
		if(res != 0L)
			return res;
	}

	if(strcmp(name, "USER") == 0
#ifdef WIN32
		// I do that (and see the "STRATA_HACK" in
		// subr.c) because people get tired on Windows
		// to have to check their Windows login :
		// this is only for .rhosts authentication.
		|| strcmp(name, "LOGNAME") == 0
#endif /* WIN32 */
		)
	{
		static char user[64];
		
		// extract from the cvsroot
		const char *ccvsroot = gCvsPrefs;
		ccvsroot = Authen::skiptoken(ccvsroot);
		char *login = strchr(ccvsroot, '@');
		if(login == NULL)
		{
			// for WIN32 this means the CVSROOT is local
#ifdef macintosh
			cvs_err("MacCvs: Cannot extract login from cvsroot %s !\n", ccvsroot);
#endif /* macintosh */
			return NULL;
		}
		int length = login - ccvsroot;
		memcpy(user, ccvsroot, length * sizeof(char));
		user[length] = '\0';
		return user;
	}
	else if(strcmp(name, "CVSROOT") == 0)
	{
		static CStr root;
		
		// extract from the cvsroot
		const char *ccvsroot = gCvsPrefs;
		ccvsroot = Authen::skiptoken(ccvsroot);
		root = gAuthen.token();
		root << ccvsroot;
		return root;
	}
	else if(strcmp(name, "CVSREAD") == 0)
	{
		return gCvsPrefs.CheckoutRO() ? (char *)"1" : 0L;
	}
	else if(strncmp(name, "CVS_GETPASS", strlen("CVS_GETPASS")) == 0)
	{
		UStr str("Enter the password:");
		char *tmp;
		if((tmp = strchr(name, '=')) != 0L)
		{
			str = ++tmp;
		}
		return CompatGetPassword(str);
	}
	else if(strcmp(name, "CVSLIB_YESNO") == 0)
	{
		return (char *)AskYesNo();
	}
	else if(strcmp(name, "DIRTY_MCP") == 0)
	{
		return gCvsPrefs.DirtySupport() ? (char *)"yes" : (char *)"no";
	}
#ifdef WIN32
	else if(strcmp(name, "HOME") == 0)
	{
		// prompt for home the first time
		if(gCvsPrefs.Home() == 0L)
		{
			char oldpath[512];
			getcwd(oldpath, 511);
			const char *chome = BrowserGetDirectory("Select your home directory :");
			if(chome == 0L)
			{
				chdir(oldpath);
				return 0L;
			}

			gCvsPrefs.SetHome(chome);
			gCvsPrefs.save();
			chdir(oldpath);
		}

		// Windows 98 doesn't like C:\/.cvspass
		static CStr home;
		home = gCvsPrefs.Home();
		if(home.endsWith(kPathDelimiter))
		{
			home[home.length() - 1] = '\0';
		}

		return home;
	}
	else if(strcmp(name, "CVSUSEUNIXLF") == 0)
	{
		return gCvsPrefs.UnixLF() ? (char *)"1" : 0L;
	}
#endif /*WIN32 */
#if TARGET_RT_MAC_MACHO
	else if(strcmp(name, "CVS_MACLF") == 0)
	{
		return gCvsPrefs.MacLF() ? (char *)"1" : 0L;
	}
#endif
#if TARGET_RT_MAC_CFM
	else if(strcmp(name, "HOME") == 0)
	{
		UFSSpec theFolder;
		static UStr thePath;
		
		if(MacGetPrefsFolder(theFolder, thePath) != noErr)
		{
			cvs_err("Unable to locate the preferences folder !\n");
			return 0L;
		}
		
		// We do not use any more the "@@@" trick. Instead
		// we let cvs convert it to a Unix path.
		//strcpy(thePath, "@@@");
		return thePath;
	}
	else if(strcmp(name, "CVSUSEUNIXLF") == 0)
	{
		return gCvsPrefs.UnixLF() ? (char *)"1" : 0L;
	}
#endif
#ifdef macintosh
	else if(strcmp(name, "ISO8859") == 0)
	{
		static char iso[6];
		
		if(gCvsPrefs.IsoConvert() == ISO8559_none)
			return 0L;
		
		sprintf(iso, "%d", (int)gCvsPrefs.IsoConvert());
		return iso;
	}
	else if(strcmp(name, "IC_ON_TXT") == 0)
	{
		return gCvsPrefs.ICTextOption() ? (char *)"1" : 0L;
	}
	else if(strcmp(name, "MAC_DEFAULT_RESOURCE_ENCODING") == 0)
	{
		switch(gCvsPrefs.MacBinEncoding())
		{
			case MAC_HQX:
				return (char *)"HQX";
				break;
			case MAC_APPSINGLE:
				return (char *)"AppleSingle";
				break;
		}
		return 0L;
	}
	else if(strcmp(name, "MAC_BINARY_TYPES_SINGLE") == 0)
	{
		return (char *)MacBinMaps::GetEnvTypes(MAC_APPSINGLE);
	}
	else if(strcmp(name, "MAC_BINARY_TYPES_HQX") == 0)
	{
		return (char *)MacBinMaps::GetEnvTypes(MAC_HQX);
	}
	else if(strcmp(name, "MAC_BINARY_TYPES_PLAIN") == 0)
	{
		return (char *)MacBinMaps::GetEnvPlainTypes();
	}
#endif /* macintosh */
	else {
		char *res;
		res = getenv(name);
		return res;
	}
	
	return NULL;
}

class CAuthenSetUp
{
public:
	CAuthenSetUp()
	{
		AuthenModel *curModel = AuthenModel::GetInstance(gAuthen.kind());
		if(curModel != 0L)
			curModel->OnSetupEnv();
	}

	~CAuthenSetUp()
	{
		AuthenModel *curModel = AuthenModel::GetInstance(gAuthen.kind());
		if(curModel != 0L)
			curModel->OnRestoreEnv();
	}
};

int testCVS(const char *cvsfile, int argc, char * const *argv)
{
	CAuthenSetUp setupEnv;

#ifdef macintosh
	return -1;
#elif defined(WIN32) || defined qUnix
	CStr cvscmd(cvsfile);

	sLastExitCode = 1;

#ifdef WIN32
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if( app->gCvsRunning && cvs_process_is_active(app->gCvsProcess) )	{
		cvs_err("Error : cvs is already running !\n");
		return -1;
	}

	app->gCvsStopping = false;
#elif qUnix
	if(UCvsApp::gApp->IsCvsRunning())	{
		cvs_err("Error : cvs is already running !\n");
		return errInternal;
	}

	UCvsApp::gApp->SetCvsStopping(false);
#endif

	struct stat sb;

	if(stat(cvscmd, &sb) == -1 ||
#ifdef qUnix
		 S_ISLNK(sb.st_mode) ||
#endif
		 !(S_ISREG(sb.st_mode)))
	{
		cvs_err("Unable to find the cvs process in : (error %d)\n", errno);
		cvs_err("1) %s\n", (const char *)cvscmd);
		return -1;
	}

	//CvsProcess *proc = cvs_process_test(cvscmd, argc - 1, (char **)argv + 1, &sCallTable);
	CvsProcess *proc = cvs_process_open(cvscmd, argc - 1, (char **)argv + 1,
		&sCallTable, gCvsPrefs.CvsConsole(), NULL);

	if(proc == 0L)
	{
		cvs_err("Unable to initialize the cvs process (error %d)\n", errno);
		cvs_err("The cvs used is : %s\n", (const char *)cvscmd);
	}
	else 
	{
#ifdef WIN32
		app->gCvsRunning = true;
		app->gCvsProcess = proc;
		app->PeekPumpAndIdle(true); 
#endif

		time_t start = time(NULL);
		
		while( (time(NULL) - start ) < 20) {
#ifdef WIN32
			app->PeekPumpAndIdle(false); 
#endif
			cvs_process_give_time();
				
			if( !cvs_process_is_active(proc) )
				break;
		
			if( (time(NULL) - start ) > 10 ) {
				cvs_err("testing cvs timed out");
				cvs_process_kill( proc );
			}
		}
				
/*#ifdef qUnix
		else {
			UCvsApp::gApp->SetCvsRunning(true);
			UCvsApp::gCurCvs = proc;
		}
#endif*/
	}

	return sLastExitCode;
#endif
}

int launchCVS(const char *path, int argc, char * const *argv, CCvsConsole * console)
{
	CAuthenSetUp setupEnv;
	
	gConsole = console;

#if TARGET_RT_MAC_CFM
	CompatConnectID connID;
	
	if(!loadCVS(connID))
		return errInternal;
	
	int exitc = 0;

	// call the main
	if(!CompatCallLibrary3(connID, exitc, uppMainProcInfo, "dllglue_main",
		dllglue_main_func, path, argc, (char **)argv))
	{
		cvs_err("Error : Cannot find symbol ""dllglue_main""\n");
		exitc = errInternal;
	}
	

	unloadCVS(connID, exitc);
	
	return exitc;
#elif qUnix_old
	CStr cvscmd;

	sLastExitCode = 0;

	if(UCvsApp::gApp->IsCvsRunning())
	{
		cvs_err("Error : cvs is already running !\n");
		return errInternal;
	}

	if(path != 0L && chdir(path) != 0)
	{
		cvs_err("Error : cannot chdir to %s (error %d)!\n", path, errno);
	}

	UCvsApp::gApp->SetCvsStopping(false);

	struct stat sb;
	cvscmd = "/home/bruenen/PRIVAT/cvsgui/cvsunix/src/cvs"; //UCvsApp::gApp->GetAppLibFolder();
	if(!cvscmd.endsWith(kPathDelimiter))
		cvscmd << kPathDelimiter;
	cvscmd << "cvs";
	UStr firstCmd = cvscmd;
	if(stat(cvscmd, &sb) == -1 || !(S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)))
	{
		cvscmd = PACKAGE_SOURCE_DIR;
		CStr uppath, folder;
		SplitPath(cvscmd, uppath, folder);
		cvscmd = uppath;
		if(!cvscmd.endsWith(kPathDelimiter))
			cvscmd << kPathDelimiter;
		cvscmd << "cvsunix/src/cvs";
	}

	if(stat(cvscmd, &sb) == -1 || !(S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)))
	{
		cvs_err("Unable to find the cvs process in : (error %d)\n", errno);
		cvs_err("1) %s\n", (const char *)firstCmd);
		cvs_err("2) %s\n", (const char *)cvscmd);
	}

	CvsProcess *proc = cvs_process_open(cvscmd, argc - 1, (char **)argv + 1, &sCallTable, false, 0L);
	if(proc == 0L)
	{
		cvs_err("Unable to initialize the cvs process (error %d)\n", errno);
		cvs_err("The cvs used is : %s\n", (const char *)cvscmd);
	}
	else if(console != 0L)
	{
		while(true)
		{
			cvs_process_give_time();
			if(!cvs_process_is_active(proc))
				break;
		}
	}
	else
	{
		UCvsApp::gApp->SetCvsRunning(true);
		UCvsApp::gCurCvs = proc;
	}

	return sLastExitCode;
#elif defined(WIN32) || defined(qUnix) || TARGET_RT_MAC_MACHO
	CStr cvscmd;
	
	sLastExitCode = 0;

#ifdef WIN32
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if( app->gCvsRunning && cvs_process_is_active(app->gCvsProcess) )	{
		cvs_err("Error : cvs is already running !\n");
		return -1;
	}
	//Jerzy - we don't want to chdir, on windows we can use the "currentDirectory" parameter
#if _0_
	if(path != 0L && chdir(path) != 0) {
		cvs_err("Error : cannot chdir to %s (error %d)!\n", path, errno);
	}
#endif // _0_

	app->gCvsStopping = false;
#elif qUnix
	if(UCvsApp::gApp->IsCvsRunning())	{
		cvs_err("Error : cvs is already running !\n");
		return errInternal;
	}

	if(path != 0L && chdir(path) != 0){
		cvs_err("Error : cannot chdir to %s (error %d)!\n", path, errno);
	}

	UCvsApp::gApp->SetCvsStopping(false);
#elif TARGET_RT_MAC_MACHO
	if(CMacCvsApp::gApp->IsCvsRunning())	{
		cvs_err("Error : cvs is already running !\n");
		return errInternal;
	}

	if(path != 0L && chdir(path) != 0){
		cvs_err("Error : cannot chdir to %s (error %d)!\n", path, errno);
	}

	CMacCvsApp::gApp->SetCvsStopping(false);
#endif

	gCvsPrefs.WhichCvs(cvscmd, gCvsPrefs.UseAltCvs());
	
	struct stat sb;

	if(stat(cvscmd, &sb) == -1 ||
#if qUnix || TARGET_RT_MAC_MACHO
		 S_ISLNK(sb.st_mode) ||
#endif
		 !(S_ISREG(sb.st_mode)))
	{
		cvs_err("Unable to find the cvs process in : (error %d)\n", errno);
		cvs_err("1) %s\n", (const char *)cvscmd);
	}

	CvsProcess *proc = cvs_process_open(cvscmd, argc - 1, (char **)argv + 1,
		&sCallTable, gCvsPrefs.CvsConsole(), path);
	if(proc == 0L)
	{
		cvs_err("Unable to initialize the cvs process (error %d)\n", errno);
		cvs_err("The cvs used is : %s\n", (const char *)cvscmd);
	}
	else 
	{
#if qUnix
		UCvsApp::gApp->SetCvsRunning(true);
		UCvsApp::gCurCvs = proc;
#elif TARGET_RT_MAC_MACHO
		CMacCvsApp::gApp->SetCvsRunning(true);
		CMacCvsApp::gCurCvs = proc;
		UInt32 start = ::TickCount ();

	if(console != 0L )
#else
		app->gCvsRunning = true;
		app->gCvsProcess = proc;
		app->PeekPumpAndIdle(true); 
	
	if(console != 0L )
#endif
		while( TRUE ) {
#ifdef WIN32
			app->PeekPumpAndIdle(false); 
#endif
#if TARGET_RT_MAC_MACHO
			if((::TickCount () - start) > 40)
			{
				glue_eventdispatcher(0L);
				start = ::TickCount ();
			}
#endif
			cvs_process_give_time();
			
			if( !cvs_process_is_active(proc) )
				break;
		}
	/*}
#ifdef qUnix
		else {
			UCvsApp::gApp->SetCvsRunning(true);
			UCvsApp::gCurCvs = proc;
		}
#endif*/
	}

	return sLastExitCode;
#endif
}

class CStreamConsole : public CCvsConsole
{
public:
	CStreamConsole(FILE *out) : fOut(out) {}

	virtual long cvs_out(char *txt, long len)
	{
		fwrite(txt, sizeof(char), len, fOut);
		return len;
	}
	virtual long cvs_err(char *txt, long len)
	{
		cvs_errstr(txt, len);
		return len;
	}
protected:
	FILE *fOut;
};

FILE *launchCVS(const char *dir, const CvsArgs & args, CStr & tmpFile, const char *prefix, const char *extension)
{
	if(!MakeTmpFile(tmpFile, prefix, extension))
		return 0L;

	FILE * res = fopen(tmpFile, "w+");
	if(res == 0L)
	{
		cvs_err("Impossible to open for write the temp file '%s' (error %d)",
			(const char *)tmpFile, errno);
		return 0L;
	}

	CStreamConsole myconsole(res);

	launchCVS(dir, args.Argc(), args.Argv(), &myconsole);

	fseek(res, 0L, SEEK_SET);

	return res;
}

class CTmpFiles
{
public:
	~CTmpFiles()
	{
		std::vector<CStr>::const_iterator i;
		for(i = allfiles.begin(); i != allfiles.end(); ++i)
		{
			const char *file = *i;
			unlink((char *)file);
		}
	}

	inline const char *push_back(const char *file)
	{
		allfiles.push_back(file);
		return allfiles[allfiles.size() - 1];
	}
protected:
	std::vector<CStr> allfiles;
} sTmpList;

const char *launchCVS(const char *dir, const CvsArgs & args, const char *prefix, const char *extension)
{
	CStr filename;
	FILE *file = launchCVS(dir, args, filename, prefix, extension);
	if(file == 0L)
		return 0L;

	fclose(file);
	return sTmpList.push_back(filename);
}

#ifdef WIN32
void WaitForCvs(void)
{
	// pump messages manually
	CWincvsApp* app = (CWincvsApp *)AfxGetApp();

	while(1)
	{
		if(app->gCvsProcess == 0L)
			return;

		if(!cvs_process_is_active(app->gCvsProcess))
			return;

		Sleep(100);
	}
}
#endif

#if TARGET_RT_MAC_MACHO
void WaitForCvs(void)
{
	while(cvs_process_is_active (CMacCvsApp::gCurCvs))
	{
		UInt32 start = ::TickCount ();
		
		while(cvs_process_give_time ())
		{
			if((::TickCount () - start) >= 60)
				break;
		}
		glue_eventdispatcher(0L);
	}
	CMacCvsApp::gApp->SetCvsRunning (false);
}
#endif
