/*
** 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"
#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 qUnix || defined(WIN32)
#	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;

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

#if qUnix || defined(WIN32)
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);
}

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, "DIRTY_MCP") == 0 \
		)
#endif /* macintosh */

CVS_EXTERN_C 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)
	{
		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;

		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 */
#ifdef macintosh
	else if(strcmp(name, "HOME") == 0)
	{
		FSSpec theFolder;
		static char thePath[256];
		Str255 aPath;
		
		if(MacGetPrefsFolder(theFolder, aPath) != 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, "@@@");
		p2cstrcpy(thePath, aPath);
		return thePath;
	}
	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;
	}
#	ifdef qMacCvsPP
	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 /* qMacCvsPP */
#endif /* macintosh */
	else
		return getenv(name);
	
	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 launchCVS(const char *path, int argc, char * const *argv, CCvsConsole * console)
{
	CAuthenSetUp setupEnv;

	gConsole = console;

#ifdef macintosh
	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
	CStr cvscmd;

	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 = 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);
	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(1)
		{
			cvs_process_give_time();
			if(!cvs_process_is_active(proc))
				break;
		}
	}
	else
	{
		UCvsApp::gApp->SetCvsRunning(true);
		UCvsApp::gCurCvs = proc;
	}

	return 0;
#elif defined(WIN32)
	CStr cvscmd;

	CWincvsApp* app = (CWincvsApp *)AfxGetApp();
	if(app->gCvsRunning)
	{
		cvs_err("Error : cvs is already running !\n");
		return false;
	}

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

	app->gCvsStopping = false;

	gCvsPrefs.WhichCvs(cvscmd);

	struct stat sb;
	if(stat(cvscmd, &sb) == -1 || !(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());
	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(1)
		{
			app->PumpMessage();
			if(!cvs_process_is_active(proc))
				break;
		}
	}
	else
	{
		app->gCvsRunning = true;
		app->gCvsProcess = proc;
	}

	return 0;
#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
