/*
 * serelhistory.c
 * serelhistory main() program and client-side logic.
 *
 * Copyright (C) Leni Mayo 2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *	
 *	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.
 */
#include <stdlib.h>
#include <string.h> /* for strerror() */
#include <errno.h>
#include <getopt.h>
#include <glib.h>
#include "config.h"  /* generated by autoconf configure */
#include "command.h"
#include "common.h"
#include "clienthandle.h"
#include "log.h"

Handle eventHandle;  /* global event handle */
static const char *argv0 = NULL;

static void printUsage(FILE *fp, const char *argv0);
static Command * commandFromArgv(ViaFileCode code, int argc, int start_index, char *argv[]);
static void parseCommandLine(int argc, char *argv[], ViaFileCode *pCode,
                   Command **ppCommand, int *pDebugLevel, const char **ppszLogName,
				   const char **ppszRoster, LogOpenMode *pLogMode);

static int onEventRosterApply(const MessageReply *pReplyMessage, void *pUserData);

int main (int argc, char *argv[])
{
	Message msg;
	Command *pCommand = NULL;
	int debuglevel = 0;
	const char *pszLogName = NULL;
	LogOpenMode logMode = eLogOpenModeAppend;
	const char *pszRoster  = NULL;
	ViaFileCode code;
	int ret;

	argv0 = getArgv0(argv, "serelc");

	commandInitialiseTables();

	parseEnvironment(&debuglevel, &pszLogName);

	parseCommandLine(argc, argv, &code, &pCommand, &debuglevel, &pszLogName, &pszRoster, &logMode);

#ifdef TAKENOUT
	printf("Command line is:\nCommand = %s\ndebuglevel = %d, pszLogName = %s\n",
			commandAsString(pCommand), debuglevel,
			pszLogName ? pszLogName : "NULL");
#endif

	logInitialise(argv0, eLogContextTerminal, debuglevel, pszLogName, logMode);

	{
		/* create an initial entry in the log for easier tracability */
		int i;
	
		printLog(eLogLevelClient, "%d: ", getpid());

		for (i = 0; i < argc && argv[i] != NULL && argv[i][0] != '\0'; i++)
		{
			printLog(eLogLevelClient, "%s ", argv[i]);
		}

		printLog(eLogLevelClient, "\n");
	}

	ret = handleInitialise(&eventHandle);

	if (ret == 0)
	{
		HandleCallback callback;
		HandleEventDefaultUserData ud;

		ud.pCommand = pCommand;

		assert(pCommand->c == eCommandTypeViaFile);

		callback = ((CommandViaFile *) pCommand)->code == eViaFileCodeRosterApply
							? onEventRosterApply : onEventDefault;

		messageInitialise(&msg);
		messagePush(&msg, pCommand);

		ret = handleInitiate(&eventHandle, &msg, callback, &ud);

		if (ret == 0)
		{
			handleEventLoop(&eventHandle);

			ret = ud.retval;
		}
		else
		{
			printLog(eLogLevelError, "%d: handleInitiate() failed to send: %s\n", getpid(), commandAsString(pCommand));
		}

		handleCleanup(&eventHandle);

		free(pCommand);
	}

	printLog(eLogLevelClient, "%d: exit status: %d\n", getpid(), ret);

	return ret;
}

/*
 * The message sequence between the serelhistory client and the server is as follows:
 *	client                                    server
 *  sends query via	eViaFileCodeRosterApply 
 *	                                          responds by printing space-separated list of:
 *	                                          provider needer1 needer2 to the channel
 *	reads the channel
 *	decomposes the channel into a list
 *	pops words from the list, sends
 *	provide need need ... until list empty
 *	or server indicates failure via signal
 */

int onEventRosterApply(const MessageReply *pReplyMessage, void *pUserData)
{
	HandleEventDefaultUserData *pUd = (HandleEventDefaultUserData *) pUserData;
	static int fFirstTime = TRUE;
	static GSList *pList = NULL;
	int fDoProvider = FALSE;
	int fContinueEventLoop;

	assert(pReplyMessage != NULL);
	assert(pUserData != NULL);

	onEventDefaultBase(pReplyMessage, pUserData);

	if (pUd->retval)
	{
		return FALSE;
	}

	free(pUd->pCommand);

	/*
	 * If it's the first response from the server,
	 * suck the channel into a list of words: provider needer1 needer2 ...
	 */
	if (fFirstTime)
	{
		char *pszResponse = NULL;

		fFirstTime = FALSE;
		fDoProvider = TRUE;

		if (strlen(pUd->fileChannel) > 0)
		{
			FILE *fp;

			fp = fopen(pUd->fileChannel, "r");

			if (fp == NULL)
			{
				printLog(eLogLevelError, "Unable to open file for reading: \"%s\" - %s\n",
					pUd->fileChannel, strerror(errno));

				pUd->retval = 1;
			}
			else
			{
				pszResponse = stringFromFp(fp);

				fclose(fp);

				unlink(pUd->fileChannel);
			}
		}

		if (pszResponse)
		{
			GSList *pElement;

			pList = splitStringIntoList(pszResponse, isWhitespace);

			/* copy the strings in the list in order to free pszResponse
			*/
			for (pElement = pList; pElement != NULL; pElement = pElement->next)
			{
				assert(pElement->data);

				pElement->data = strdup(pElement->data);
			}

			free(pszResponse);
		}
	}

	if (pList)
	{
		CommandRegister *pCommand = malloc(sizeof(CommandRegister));
		CommandType ct = fDoProvider ? eCommandTypeRegisterProvide : eCommandTypeRegisterNeed;
		Message msg;
		int ret;

		fContinueEventLoop = TRUE;

		commandInitialise(&(pCommand->cmd), ct, getpid());

		pCommand->pid = getppid();
		snprintf(pCommand->name, sizeof(pCommand->name), "%s", (char *) pList->data);

		pList = pList->next;

		pUd->pCommand = (Command *) pCommand;

		messageInitialise(&msg);
		messagePush(&msg, (Command *) pCommand);

		ret = handleInitiate(&eventHandle, &msg, onEventRosterApply, pUd);

		if (ret != 0)
		{
			pUd->retval = 1;
			printLog(eLogLevelError, "%d: handleInitiate() failed to send: %s\n", getpid(), commandAsString(&(pCommand->cmd)));
			return FALSE;
		}
	}
	else
	{
		fContinueEventLoop = FALSE;
	}

	return fContinueEventLoop;
}

Command * commandFromArgv(ViaFileCode code, int argc, int start_index, char *argv[])
{
	RosterQueryCode rqCode = eRosterQueryCodeMax;
	Command *pReturn = NULL;
	CommandViaFile *pC;
	pid_t pidReply;
	FILE *fp;

#ifdef TAKENOUT
	printf("Entering commandFromArgv(%s, start_index == %d)\n",
	                commandTypeAsString(eCommandTypeViaFile), start_index);
#endif

	pidReply = getpid();

	pC = malloc(sizeof(CommandViaFile));

	commandInitialise(&(pC->cmd), eCommandTypeViaFile, pidReply);
	pC->code = code;

	fp = createAndOpenTmpFp(pC->fileChannel, sizeof(pC->fileChannel), "r+");

	if (fp == NULL)
	{
		fprintf(stderr, "Unable to create temporary file: \"%s\" - %s\n", pC->fileChannel, strerror(errno));
		return NULL;
	}

	if (code == eViaFileCodeRosterApply)
	{
		/* parse for: --apply path
		*/
		if (argv[start_index] != NULL)
		{
			fprintf(fp, "%s", argv[start_index]);

			pReturn = (Command *) pC;
		}
	}
	else if (code == eViaFileCodeRosterApplyStarted)
	{
		/* parse for: --apply-started
		*/
		pReturn = (Command *) pC;
	}
	else if (code == eViaFileCodeRosterPopulate)
	{
		copyFpToFp(fp, stdin);

		pReturn = (Command *) pC;
	}
	else if (code == eViaFileCodeRosterQuery)
	{
		/* parse for: --query verify | prior-history
		*/
		if (argv[start_index] != NULL)
		{
			rqCode = rosterQueryCodeFromString(argv[start_index]);
			
			if (rqCode != eRosterQueryCodeMax)
			{
				fprintf(fp, "%d", rqCode);

				pReturn = (Command *) pC;
			}
		}
	}

	fclose(fp); /* this closes the file descriptor as well */

	if (pReturn == NULL)
	{
		unlink(pC->fileChannel);
	}

	return pReturn;
}

void parseCommandLine(int argc, char *argv[], ViaFileCode *pCode,
          Command **ppCommand, int *pDebugLevel, const char **ppszLogName,
		  const char **ppszRoster, LogOpenMode *pLogMode)
{
	int c;
	int option_index = 0;
	static struct option long_options[] =
	{
		{"help",         no_argument,       NULL, 'h'},
		{"version",      no_argument,       NULL, 'v'},
		{"debuglevel",   required_argument, NULL, 'd'},
		{"log",          required_argument, NULL, 'l'},
		{"log-append",   no_argument,       NULL, 'a'},
		{"log-truncate", no_argument,       NULL, 't'},
		{"apply",        no_argument,       NULL,  0 },
		{"apply-started", no_argument,      NULL,  0 },
		{"populate",     no_argument,       NULL,  0 },
		{"query",        no_argument,       NULL,  0 },
		{0, 0, 0, 0}
		/* {"roster",       required_argument, NULL, 'r'}, */
	};

	assert(pCode && ppCommand && pDebugLevel && ppszLogName && ppszRoster);

	*pCode = eViaFileCodeMax;
	*ppCommand = NULL;

	while ((c = getopt_long (argc, argv, "ad:hl:r:tv", long_options, &option_index)) != -1)
	{
		switch(c)
		{
		case 0:
		{
			const char *p = long_options[option_index].name;

			if (strcmp(p, viaFileCodeAsString(eViaFileCodeRosterApply)) == 0)
			{
				*pCode = eViaFileCodeRosterApply;
			}
			else if (strcmp(p, viaFileCodeAsString(eViaFileCodeRosterApplyStarted)) == 0)
			{
				*pCode = eViaFileCodeRosterApplyStarted;
			}
			else if (strcmp(p, viaFileCodeAsString(eViaFileCodeRosterPopulate)) == 0)
			{
				*pCode = eViaFileCodeRosterPopulate;
			}
			else if (strcmp(p, viaFileCodeAsString(eViaFileCodeRosterQuery)) == 0)
			{
				*pCode = eViaFileCodeRosterQuery;
			}
			else
			{
				printUsage(stdout, argvCodeAsString(eArgvSerelhistory));
				exit(0);
			}
			break;
		}

		case 'a':
			*pLogMode = eLogOpenModeAppend;
			break;
		case 'd':
			*pDebugLevel = atoi(optarg);
			break;
		case 'h':
			printUsage(stdout, argvCodeAsString(eArgvSerelhistory));
			exit(0);
		case 'l':
			*ppszLogName = optarg;
			break;
#ifdef TAKENOUT
		case 'r':
			*ppszRoster = optarg;
			break;
#endif
		case 't':
			*pLogMode = eLogOpenModeTruncate;
			break;
		case 'v':
			printVersion(stdout, PACKAGE, VERSION);
			exit(0);
		default:
			printUsage(stderr, argvCodeAsString(eArgvSerelhistory));
			exit(1);
		}
	}

	if (*pCode != eViaFileCodeMax)
	{
		*ppCommand = commandFromArgv(*pCode, argc, optind, argv);
	}

	if (*ppCommand == NULL)
	{
		printUsage(stderr, argvCodeAsString(eArgvSerelhistory));
		exit(1);
	}
}

void printUsage(FILE *fp, const char *argv0)
{
	assert(fp);

	fprintf(fp, "Usage:\n");
	fprintf(fp, "  %s [general_options] options args\n", argv0);
	fprintf(fp, "\n");
	fprintf(fp, "  [general_options]\n");
	printCommonCmdlineOptions(fp);
	fprintf(fp, "\n");
	fprintf(fp, "  options args\n");
	fprintf(fp, "   --populate             Populate the history with services and pathnames\n");
	fprintf(fp, "   --query args           Query the integrity or status of the populated history\n");
	fprintf(fp, "   --apply path           Register the provided service and dependencies assoc with a path\n");
	fprintf(fp, "   --apply-started        Register any already-started services identified via --populate.\n");
	/* fprintf(fp, "   -r --roster id         The roster is specified by the given id\n"); */
}
