/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- February 2000
 */

/*
 * 
 */

#include "stdafx.h"

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

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#if qGTK
#include <gtk/gtk.h>
#include <signal.h>
// glade generated that
extern "C"
{
#include "support.h"
#include "interface.h"
}
#endif

#include "UCvsApp.h"
#include "UCvsCommands.h"
#include "UCvsFolders.h"
#include "UCvsFiles.h"
#include "TclGlue.h"
#include "GetPrefs.h"
#include "CvsPrefs.h"
#include "AppConsole.h"
#include "MacrosSetup.h"
#include "Authen.h"
#include "getopt.h"

UIMPLEMENT_DYNAMIC(UCvsApp, UWidget)

UBEGIN_MESSAGE_MAP(UCvsApp, UWidget)
	ON_TIMER(UCvsApp::kProcessTimer, UCvsApp::OnMainTimer)
UEND_MESSAGE_MAP()

UCvsApp *UCvsApp::gApp = 0L;
CvsProcess *UCvsApp::gCurCvs = 0L;

static void usage(void)
{
	fprintf(stderr, "gcvs [--root=path]\n");
}

UCvsApp::UCvsApp(int argc, char **argv) : UWidget(::UEventGetWidID()), gCvsRunning(false), gCvsStopping(false)
{
	gApp = this;

#if qGTK
	add_pixmap_directory (PACKAGE_DATA_DIR "/pixmaps");
	add_pixmap_directory (PACKAGE_SOURCE_DIR "/pixmaps");
#endif

	// load the persistent values (i.e. preferences) and the locate the macros
	gCvsPrefs.load();
	MacrosReloadAll();

	AddTimer(50, kProcessTimer);

	if(argc > 0)
	{
		static struct option long_options[] =
		{
			{"root", required_argument, 0L, 1},
			{0, 0, 0, 0}
		};
		optind = 0;
		int option_index = 0, c;
		while ((c = getopt_long
				(argc, argv, "+", long_options, &option_index)) != EOF)
		{
			switch (c)
			{
			case 1:
				UCvsFolders::SetDefaultRoot(optarg);
				break;
			default:
				cvs_err("Warning : unknown option\n");
				usage();
				break;
			}
		}
		
	}
}

UCvsApp::~UCvsApp()
{
}

void UCvsApp::SayWelcome(void)
{
	// say hello from the application, optionnaly prompt for the CVSROOT
	cvs_out("Welcome to %s %s\n", PACKAGE, VERSION);
	if(gCvsPrefs.empty())
		CompatGetPrefs();
	else
		cvs_out("CVSROOT: %s (%s)\n", (const char *)gCvsPrefs, gAuthen.kindstr());

	if(CTcl_Interp::IsAvail())
	{
		cvs_out("TCL is available, shell and macros are enabled : help (select and press enter)\n");
	}
	else
	{
		cvs_err("TCL is *not* available, shell and macros are disabled\n");
	}
}

void UCvsApp::SetCvsRunning(bool state)
{
	if(state ^ gCvsRunning)
	{
		gCvsRunning = state;
	}
}

void UCvsApp::SetCvsStopping(bool state)
{
	if(state ^ gCvsStopping)
	{
		gCvsStopping = state;
	}
}

UCvsFolders *UCvsApp::GetBrowserView(void)
{
	UCmdTarget *cmd = UEventQueryWidget(kUCvsFoldersID);
	if(cmd != 0L && cmd->IsKindOf(URUNTIME_CLASS(UCvsFolders)))
		return (UCvsFolders *)cmd;

	return 0L;
}

UCvsFiles *UCvsApp::GetFilesView(void)
{
	UCmdTarget *cmd = UEventQueryWidget(kUCvsFilesID);
	if(cmd != 0L && cmd->IsKindOf(URUNTIME_CLASS(UCvsFiles)))
		return (UCvsFiles *)cmd;

	return 0L;
}

const char *UCvsApp::GetAppName() const
{
#ifdef macintosh
	return "MacCvs";
#elif defined(WIN32)
	return "WinCvs";
#elif defined(qUnix)
	return PACKAGE;
#endif
}

const char *UCvsApp::GetAppVersion() const
{
#ifdef macintosh
	
#elif defined(WIN32)
	
#elif defined(qUnix)
	return VERSION;
#endif
}

const char *UCvsApp::GetAppLibFolder() const
{
#if qUnix
	return EXECLIB;
#else
	return 0L;
#endif
}

#if qUnix
static void
set_cloexec (gint fd)
{
	fcntl (fd, F_SETFD, FD_CLOEXEC);
}

static int
gnome_execute_async_with_env_fds(const char *dir, int argc, 
				       char * const argv[], int envc, 
				       char * const envv[], 
				       gboolean close_fds)
{
	int parent_comm_pipes[2], child_comm_pipes[2];
	int child_errno, itmp, i, open_max;
	int res;
	char **cpargv;
	pid_t child_pid, immediate_child_pid; /* XXX this routine assumes
					   pid_t is signed */

	if(pipe(parent_comm_pipes))
		return -1;

	child_pid = immediate_child_pid = fork();

	switch(child_pid) {
	case -1:
		close(parent_comm_pipes[0]);
		close(parent_comm_pipes[1]);
		return -1;

	case 0: /* START PROCESS 1: child */
		child_pid = -1;
		res = pipe(child_comm_pipes);
		close(parent_comm_pipes[0]);
		if(!res)
			child_pid = fork();

		switch(child_pid) {
		case -1:
			itmp = errno;
			child_pid = -1; /* simplify parent code */
			write(parent_comm_pipes[1], &child_pid, sizeof(child_pid));
			write(parent_comm_pipes[1], &itmp, sizeof(itmp));
			close(child_comm_pipes[0]);
			close(child_comm_pipes[1]);
			_exit(0); break;      /* END PROCESS 1: monkey in the middle dies */

		default:
		{
			char buf[16];
	
			close(child_comm_pipes[1]);
			while((res = read(child_comm_pipes[0], buf, sizeof(buf))) > 0)
				write(parent_comm_pipes[1], buf, res);
			close(child_comm_pipes[0]);
			_exit(0); /* END PROCESS 1: monkey in the middle dies */
		}
			break;

		case 0:                 /* START PROCESS 2: child of child */
			close(parent_comm_pipes[1]);
			/* pre-exec setup */
			close (child_comm_pipes[0]);
			set_cloexec (child_comm_pipes[1]);
			child_pid = getpid();
			res = write(child_comm_pipes[1], &child_pid, sizeof(child_pid));

			if(envv) {
				for(itmp = 0; itmp < envc; itmp++)
					putenv(envv[itmp]);
			}

			if(dir) chdir(dir);

			cpargv = (char **)malloc((argc + 1) * sizeof(char *));
			memcpy(cpargv, argv, argc * sizeof(char *));
			cpargv[argc] = NULL;

			if(close_fds)
			{
				int stdinfd;
				/* Close all file descriptors but stdin stdout and stderr */
				open_max = sysconf (_SC_OPEN_MAX);
				for (i = 3; i < open_max; i++)
					set_cloexec (i);

				if(child_comm_pipes[1] != 0) {
					close(0);
					/* Open stdin as being nothingness, so that if someone tries to
					   read from this they don't hang up the whole GNOME session. BUGFIX #1548 */
					stdinfd = open("/dev/null", O_RDONLY);
					g_assert(stdinfd >= 0);
					if(stdinfd != 0)
					{
						dup2(stdinfd, 0);
						close(stdinfd);
					}
				}
			}
			signal (SIGPIPE, SIG_DFL);
			/* doit */
			execvp(cpargv[0], cpargv);
			free(cpargv);

			/* failed */
			itmp = errno;
			write(child_comm_pipes[1], &itmp, sizeof(itmp));
			_exit(1); break;      /* END PROCESS 2 */
		}
		break;

	default: /* parent process */
		/* do nothing */
		break;
	}

	close(parent_comm_pipes[1]);

	res = read (parent_comm_pipes[0], &child_pid, sizeof(child_pid));
	if (res != sizeof(child_pid))
    {
		g_message("res is %d instead of %d", res, sizeof(child_pid));
		child_pid = -1; /* really weird things happened */
    }
	else if (read (parent_comm_pipes[0], &child_errno, sizeof(child_errno))
			 == sizeof(child_errno))
    {
		errno = child_errno;
		child_pid = -1;
    }

	/* do this after the read's in case some OS's handle blocking on pipe writes
     differently */
	waitpid(immediate_child_pid, &itmp, 0); /* eat zombies */

	close(parent_comm_pipes[0]);

	if(child_pid < 0)
		g_message("gnome_execute_async_with_env_fds: returning %d", child_pid);

	return child_pid;
}
#endif

void UCvsApp::Execute(int argc, char * const *argv)
{
#if qUnix
	gnome_execute_async_with_env_fds(0L, argc, argv, 0, 0L, true);
#endif
}

void UCvsApp::OnMainTimer(long time)
{
	if(gCurCvs != 0L)
	{
		cvs_process_give_time();
		if(!cvs_process_is_active(gCurCvs))
		{
			UCvsApp::gApp->SetCvsRunning(false);
			gCurCvs = 0L;
			UWidget::DoValidUI(false);
			UCvsApp::gApp->GetFilesView()->ResetView();
		}
	}
}

#if qGTK
#	define NEWDLG(name) \
	void *UCreate_##name(void) \
	{ \
		return create_##name(); \
	}
	NEWDLG(MainWindow)
	NEWDLG(GraphWindow)
	NEWDLG(AskYesNo)
	NEWDLG(gcvs_Settings)
	NEWDLG(UpdateDlg)
	NEWDLG(PasswdDlg)
	NEWDLG(CommitDlg)
	NEWDLG(DiffDlg)
	NEWDLG(CheckoutDlg)
	NEWDLG(LogDlg)
	NEWDLG(ImportFilterDlg)
	NEWDLG(ImpWarningDlg)
	NEWDLG(ImpEntryDlg)
	NEWDLG(ImportDlg)
	NEWDLG(CmdlineDlg)
	NEWDLG(TagModDlg)
	NEWDLG(BranchModDlg)
	NEWDLG(UntagModDlg)
	NEWDLG(TagDlg)
	NEWDLG(BranchDlg)
	NEWDLG(UntagDlg)
	NEWDLG(SettingsDlg)
#endif

void *UCreate_pixmap(UWidget *parent, const char *fileIcon)
{
#if qGTK
	GtkWidget *wid = (GtkWidget *)parent->GetWidget(kUMainWidget);
	return GTK_PIXMAP(create_pixmap(wid, fileIcon));
#else
	return 0L;
#endif
}
