/*
 * main.c: logapp main file
 *
 *
 * Copyright (C) 2007-2008 Michael Brunner <mibru@gmx.de>
 *
 * 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 2
 * of the License, 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
 * General Public License for more details.
 */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>
#include <pty.h>

#include "logapp.h"
#include "configuration.h"
#include "logfile.h"
#include "capture.h"

#define LINELENGTH 80
#define DESCOFFSET 33
void usage(void)
{
	int i;
	int offset;
	char linebuf[LINELENGTH + 1];

	message("%s %s\n\n", EXECUTABLE, VERSION);
	message("Usage: %s [OPTIONS] APPLICATION...\n", EXECUTABLE);

	for (i = 0; i< arglistsize; i++) {
		int j;

		if ((!arglist[i].shrt) && (!arglist[i].lng)
		    && (arglist[i].desc)) {
			message("\n%s\n", arglist[i].desc);
			continue;
		}

		sprintf(linebuf, "  ");
		if (arglist[i].shrt) {
			sprintf(&linebuf[2], "-%c ", arglist[i].shrt);
		} else
			sprintf(&linebuf[2], "    ");

		if (arglist[i].lng)
			sprintf(&linebuf[5], "--%s", arglist[i].lng);
		else
			sprintf(&linebuf[5], "    ");

		offset = strlen(linebuf);
		if (arglist[i].parm)
			sprintf(&linebuf[offset], "=%s ", arglist[i].parm);
		offset = strlen(linebuf);

		for (j=offset; j<DESCOFFSET; j++) {
			linebuf[j] = ' ';
		}
		linebuf[j] = '\0';
		if (!arglist[i].desc) {
			message("%s\n", linebuf);
			continue;
		}
		offset = strlen(linebuf);

		int descoffset = 0;
		for (j=0; j<(strlen(arglist[i].desc) + 2); j++) {
			if (arglist[i].desc[j] == ' ' ||
			    (arglist[i].desc[j] == '\n') ||
			    (j > strlen(arglist[i].desc))) {
				if ((offset + (j - descoffset)) >= LINELENGTH) {
					linebuf[offset] = '\n';
					linebuf[offset+1] = '\0';
					/* Indent further description lines */
					offset = DESCOFFSET + 1;
					message("%s", linebuf);
					memset(linebuf, ' ', offset);
				}
				memcpy(&linebuf[offset],
				       &arglist[i].desc[descoffset],
				       j - descoffset);
				offset += j - descoffset;
				descoffset = j;
				linebuf[offset] = '\0';
			}
		}

		
		offset = strlen(linebuf);
		message("%s\n", linebuf);
	}

	message("\nPlease send bug reports and feature requests to %s\n",
		AUTHOR);
}

static void sig_handler(int signr)
{
	switch (signr) {
		case SIGWINCH:	adjust_clipping();
				break;
		case SIGCONT:	app.active = 1;
				break;
		case SIGCHLD: 
		case SIGTERM:
		case SIGQUIT:
		case SIGINT:
		default:	app.active = 0;
				app.doexit = 1;
				break;
	}
}

int fork_child()
{
	int pipe_stdout[2];
	int pipe_stderr[2];

	if (!config.disable) {
		get_display_parameters();

		if (app.ptytermios != NULL) {
			memcpy(app.ptytermios_bak, app.ptytermios,
		       sizeof(struct termios));
		}

		if (CONFIG_SUPPORT_PTY && config.usepty) {
			openpty(&pipe_stdout[0], &pipe_stdout[1], NULL,
				app.ptytermios, app.ptysize);
		} else {
			if (pipe(pipe_stdout) < 0) {
				error("creating stdout pipe handler\n");
				return -1;
			}
		}

		if (pipe(pipe_stderr) < 0) {
			error("creating stderr pipe handler\n");
			return -1;
		}
	}

	signal(SIGCONT, sig_handler);

	if ((app.pid = fork()) < 0) {
		error("fork error\n");
		return -1;
	};
	
	if (app.pid> 0) {
		if (!config.disable) {
			close(pipe_stdout[1]);
			close(pipe_stderr[1]);
			app.pstdout->fh = pipe_stdout[0]; //mpty;
			app.pstderr->fh = pipe_stderr[0];
		}

		/* synchronize with child */
		while (!app.active && !app.doexit) /* wait for child */
			usleep(10000);
		kill(app.pid, SIGCONT); /* tell the child we are ready */

		/* Store current time */
		time(&app.starttime);
		gettimeofday(&app.toffset, NULL);
	} else if (app.pid==0) {
		if (!config.disable) {
			close(pipe_stdout[0]);
			close(pipe_stderr[0]);

			if (pipe_stdout[1] != STDOUT_FILENO) {
				if (dup2(pipe_stdout[1], STDOUT_FILENO)
				    != STDOUT_FILENO)
					error("redirecting stdout failed");
				close(pipe_stdout[1]);
			}

			if (pipe_stderr[1] != STDERR_FILENO) {
				if (dup2(pipe_stderr[1], STDERR_FILENO)
				    != STDERR_FILENO)
					error("redirecting stderr failed");
				close(pipe_stderr[1]);
			}

			if (app.ptytermios != NULL) {
				tcsetattr(STDIN_FILENO, TCSADRAIN,
					  app.ptytermios);
			}
		}
		
		/* synchronize with parent */
		kill(getppid(), SIGCONT); /* tell the parent we are ready */
		while (!app.active && !app.doexit) /* wait for parent */
			usleep(10000);

		if (execvp(app.exe, app.argv) < 0) {
			error("executing application %s failed\n", app.exe);
			exit(EXIT_FAILURE);
		}
	} else {
		error("forking process failed\n");
		return -1;
	};

	return 0;
}

int main(int argc, char *argv[])
{
	char*	argv0;
	int	ret;

	/* check if we are executed from a dumb terminal */
	if (!strcmp(getenv("TERM"), "dumb")) {
		config.dumbterm = 1;
	}

	/* check if the called filename defines an application to run */
	argv0 = strrchr(argv[0], '/');
	if (argv0)
		argv0++;
	else
		argv0 = argv[0];
	if (strcmp(argv0, EXECUTABLE)) {
		app.exe = strstr(argv0, config.strip_prefix);
		if (app.exe == argv0) {
			if (strlen(app.exe)>strlen(config.strip_prefix))
				app.exe += strlen(config.strip_prefix);
		} else {
			app.exe = argv0;
		}
	}

	if ((ret = parse_args(argc, argv))) {
		error("error parsing " EXECUTABLE " argument %d\n", ret);
		exit(EXIT_FAILURE);
	}

	if (show_usage || (!app.exe && !show_config)) {
		usage();
		exit(EXIT_SUCCESS);
	}

	/* get default configuration from file */
	if (get_config()) {
		error("problem while loading default config from file\n");
		exit(EXIT_FAILURE);
	}

	if (fixup_config()) {
		error("problem processing configuration\n");
		exit(EXIT_FAILURE);
	}

	if (check_for_disable_keywords()) {
		error("failed to check for disable keywords\n");
		exit(EXIT_FAILURE);
	}

	if (show_config) {
		show_configuration();
		exit(EXIT_SUCCESS);
	}

	/* Get window size changes */
	if ((app.pstderr->eclip == 2) || (app.pstdout->eclip == 2)) {
		if (signal(SIGWINCH, sig_handler) == SIG_ERR) {
			error("creating signal handler\n");
			return -1;
		}
		adjust_clipping();
	}

	if (signal(SIGINT, sig_handler) == SIG_ERR) {
		error("creating signal handler\n");
		return -1;
	}

	if (signal(SIGQUIT, sig_handler) == SIG_ERR) {
		error("creating signal handler\n");
		return -1;
	}

	if (signal(SIGTERM, sig_handler) == SIG_ERR) {
		error("creating signal handler\n");
		return -1;
	}

	if (signal(SIGCHLD, sig_handler) == SIG_ERR) {
		error("creating signal handler\n");
		return -1;
	}

	/* Start the application */
	if (fork_child()) {
		exit(EXIT_FAILURE);
	}

	/* Open logfile */
	if (logfile_open(config.logname, app.logfile)) {
		error("unable to open logfile %s\n", config.logname);
		exit(EXIT_FAILURE);
	}

	/* execute preexec command */
	if (config.preexeccmd)
		system(config.preexeccmd);

	/* Start the capture threads for stdin and stderr */
	if (!config.disable) {
		if (capture_start()) {
			error("unable to capture streams\n");
			exit(EXIT_FAILURE);
		}
	}

	/* Wait for application to exit get its exit status */
	wait(&app.exit_state);
	
	/* Wait for capture threads to exit */
	if (!config.disable)
		capture_end();

	if (logfile_close(app.logfile)) {
		error("problem closing logfile");
	}

	/* execute postexec command */
	if (config.postexeccmd)
		system(config.postexeccmd);

	/* Be sure the console is not messed up when we exit */
	if (!config.disable)
		reset_console();

	/* Print short summary */
	if (config.printsummary)
		print_summary();

	/* We are returning the exit status of the application if possible */
	if (WIFEXITED(app.exit_state))
		exit(WEXITSTATUS(app.exit_state));
	else
		exit(EXIT_FAILURE);
}
