/* The main program - startup */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "elinks.h"

#include "bfu/dialog.h"
#include "cache/cache.h"
#include "config/cmdline.h"
#include "config/conf.h"
#include "config/home.h"
#include "config/kbdbind.h"
#include "config/options.h"
#include "dialogs/menu.h"
#include "document/document.h"
#include "intl/charsets.h"
#include "intl/gettext/libintl.h"
#include "main/event.h"
#include "main/interlink.h"
#include "main/main.h"
#include "main/module.h"
#include "main/select.h"
#include "main/version.h"
#include "network/connection.h"
#include "network/dns.h"
#include "network/state.h"
#include "osdep/osdep.h"
#include "osdep/signals.h"
#include "osdep/sysname.h"
#include "protocol/auth/auth.h"
#include "session/download.h"
#include "session/session.h"
#include "terminal/kbd.h"
#include "terminal/screen.h"
#include "terminal/terminal.h"
#include "util/color.h"
#include "util/error.h"
#include "util/file.h"
#include "util/memdebug.h"
#include "util/memory.h"
#include "viewer/dump/dump.h"
#include "viewer/text/marks.h"

struct program program;

static int ac;
static unsigned char **av;
static int init_b = 0;

/* Check if either stdin or stdout are pipes */
static void
check_stdio(struct list_head *url_list)
{
	assert(!remote_session_flags);

	/* Should the document be read from stdin? */
	if (!isatty(STDIN_FILENO)) {
		/* Only start reading from stdin if no URL was given on the
		 * command line. */
		if (url_list && list_empty(*url_list)) {
			get_opt_bool("protocol.file.allow_special_files") = 1;
			add_to_string_list(url_list, "file:///dev/stdin", 17);
		}
		get_cmd_opt_bool("no-connect") = 1;
	}

	/* If called for outputting to a pipe without -dump or -source
	 * specified default to using dump viewer. */
	if (!isatty(STDOUT_FILENO)) {
		int *dump = &get_cmd_opt_bool("dump");

		if (!*dump && !get_cmd_opt_bool("source"))
			*dump = 1;
	}
}

static void
check_cwd(void)
{
	unsigned char *cwd = get_cwd();

	if (!cwd || !file_is_dir(cwd)) {
		unsigned char *home = getenv("HOME");

		if (home && file_is_dir(home))
			chdir(home);
	}

	mem_free_if(cwd);
}

static void
init(void)
{
	INIT_LIST_HEAD(url_list);
	int fd = -1;
	enum retval ret;

	init_osdep();
	init_static_version();
	check_cwd();

#ifdef CONFIG_NLS
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	set_language(0);
#endif

	init_event();

	init_charsets_lookup();
	init_colors_lookup();
	init_modules(main_modules);

	init_options();
	register_modules_options(main_modules);
	register_modules_options(builtin_modules);
	set_sigcld();
	get_system_name();
	init_keymaps();

	/* XXX: OS/2 has some stupid bug and the pipe must be created before
	 * socket :-/. -- Mikulas */
	if (check_terminal_pipes()) {
		ERROR(gettext("Cannot create a pipe for internal communication."));
		program.retval = RET_FATAL;
		program.terminate = 1;
		return;
	}

	/* Parsing command line options */
	ret = parse_options(ac - 1, av + 1, &url_list);
	if (ret != RET_OK) {
		/* Command line handlers return RET_COMMAND to signal that they
		 * completed successfully and that the program should stop. */
		if (ret != RET_COMMAND)
			program.retval = ret;
		program.terminate = 1;
		free_string_list(&url_list);
		return;
	}

	if (!remote_session_flags) {
		check_stdio(&url_list);
	}

	if (!get_cmd_opt_bool("no-home")) {
		init_home();
	}

	/* If there's no -no-connect, -dump or -source option, check if there's
	 * no other ELinks running. If we found any, by-pass initialization of
	 * non critical subsystems, open socket and act as a slave for it. */
	if (get_cmd_opt_bool("no-connect")
	    || get_cmd_opt_bool("dump")
	    || get_cmd_opt_bool("source")
	    || (fd = init_interlink()) == -1) {

		load_config();
		update_options_visibility();
		/* Parse commandline options again, in order to override any
		 * config file options. */
		parse_options(ac - 1, av + 1, NULL);
		/* ... and re-check stdio, in order to override any command
		 * line options! >;) */
		if (!remote_session_flags) {
			check_stdio(NULL);
		}

		init_b = 1;
		init_modules(builtin_modules);
	}

	if (get_cmd_opt_bool("dump")
	    || get_cmd_opt_bool("source")) {
		/* Dump the URL list */
#ifdef CONFIG_ECMASCRIPT
		/* The ECMAScript code is not good at coping with this. And it
		 * makes currently no sense to evaluate ECMAScript in this
		 * context anyway. */
		get_opt_bool("ecmascript.enable") = 0;
#endif
		if (!list_empty(url_list)) {
			dump_next(&url_list);
		} else {
			unsigned char *arg = get_cmd_opt_bool("dump")
					   ? "dump" : "source";

			usrerror(gettext("URL expected after -%s"), arg);
			program.retval = RET_SYNTAX;
			program.terminate = 1;
		}

	} else if (remote_session_flags & SES_REMOTE_PING) {
		/* If no instance was running return ping failure */
		if (fd == -1) {
			usrerror(gettext("No running ELinks found."));
			program.retval = RET_PING;
		}
		program.terminate = 1;

	} else if (remote_session_flags && fd == -1) {
		/* The remote session(s) can not be created */
		usrerror(gettext("No remote session to connect to."));
		program.retval = RET_REMOTE;
		program.terminate = 1;

	} else {
		struct string info;
		struct terminal *term = NULL;

		if (!encode_session_info(&info, &url_list)) {
			ERROR(gettext("Unable to encode session info."));
			program.retval = RET_FATAL;
			program.terminate = 1;

		} else if (fd != -1) {
			/* Attach to already running ELinks and act as a slave
			 * for it. */
			close_terminal_pipes();

			handle_trm(get_input_handle(), get_output_handle(),
				   fd, fd, get_ctl_handle(), info.source, info.length,
				   remote_session_flags);
		} else {
			/* Setup a master terminal */
			term = attach_terminal(get_input_handle(), get_output_handle(),
					       get_ctl_handle(), info.source, info.length);
			if (!term) {
				ERROR(gettext("Unable to attach_terminal()."));
				program.retval = RET_FATAL;
				program.terminate = 1;
			}
		}

		/* OK, this is race condition, but it must be so; GPM installs
		 * it's own buggy TSTP handler. */
		if (!program.terminate) handle_basic_signals(term);
		done_string(&info);
	}

	if (program.terminate) close_terminal_pipes();
	free_string_list(&url_list);
}


static void
terminate_all_subsystems(void)
{
	done_interlink();
	check_bottom_halves();
	abort_all_downloads();
	check_bottom_halves();
	destroy_all_terminals();
	check_bottom_halves();
	free_all_itrms();

	/* When aborting all connections also keep-alive connections are
	 * aborted. A (normal) connection will be started for any keep-alive
	 * connection that needs to send a command to the server before
	 * aborting. This means we need to abort_all_connections() twice.
	 *
	 * It forces a some what unclean connection tear-down since at most the
	 * shutdown routine will be able to send one command. But else it would
	 * take too long time to terminate. */
	abort_all_connections();
	check_bottom_halves();
	abort_all_connections();

	if (init_b) {
#ifdef CONFIG_SCRIPTING
		trigger_event_name("quit");
#endif
#ifdef CONFIG_MARKS
		free_marks();
#endif
		free_history_lists();
		free_auth();
		done_modules(builtin_modules);
		done_screen_drivers();
		done_saved_session_info();
	}

	shrink_memory(1);
	free_charsets_lookup();
	free_colors_lookup();
	done_modules(main_modules);
	free_keymaps();
	free_conv_table();
	check_bottom_halves();
	done_home();
	done_state_message();
	done_bfu_colors();
	unregister_modules_options(builtin_modules);
	unregister_modules_options(main_modules);
	done_options();
	done_event();
	terminate_osdep();
}

void
shrink_memory(int whole)
{
#ifdef CONFIG_SCRIPTING
	/* The SMJS pre-format-html hook constructs an SMJS object for
	 * each cache entry.  Give all scripting modules a cue to garbage
	 * collect any such objects so that the entries can be freed.  */
	if (whole)
		trigger_event_name("flush-caches");
#endif
	shrink_dns_cache(whole);
	shrink_format_cache(whole);
	garbage_collection(whole);
}

#ifdef CONFIG_NO_ROOT_EXEC
static void
check_if_root(void)
{
	if (!getuid() || !geteuid()) {
		fprintf(stderr, "%s\n\n"
				"Permission to run this program as root "
				"user was disabled at compile time.\n\n",
				full_static_version);
		exit(-1);
	}
}
#else
#define check_if_root()
#endif

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

	program.terminate = 0;
	program.retval = RET_OK;
	program.path = argv[0];
	ac = argc;
	av = (unsigned char **) argv;

	select_loop(init);
	terminate_all_subsystems();

#ifdef DEBUG_MEMLEAK
	check_memory_leaks();
#endif
	return program.retval;
}
