/*-
* Copyright (C) 2005-2007 Nick Withers. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include "downtime.h"

#include <errno.h>
#include <gtk/gtk.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef BSD
#include <sysexits.h>
#endif

#include <time.h>

static void destroy_progress_callback(void ***args);
static void destroy_progress(shutdown_t *shutdown, GtkWidget *progress_window);
static void exit_and_abort(shutdown_t *shutdown);
static void exit_no_abort(void);
static void file_selected_callback(void ***);
static void file_selected(user_specs_t *specs, GtkWidget *file_selection, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);
static void pause_shutdown_callback(void ***args);
static void pause_shutdown(shutdown_t *shutdown, GtkWidget *progress_bar);
static void save_current_user_specs_callback(void ***args);
static void save_current_user_specs(char *config_path, GtkWindow *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);
static void save_prefs_callback(void ***args);
static void save_prefs(user_specs_t *specs, GtkWidget *window, GtkWidget *main_window_on_top_box, GtkWidget *progress_window_on_top_box, GtkWidget *main_window);
static void select_and_open_config_callback(void ***);
static void select_and_open_config(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);
static void show_about(void);
static void show_prefs_callback(void ***args);
static void show_prefs(user_specs_t *specs, GtkWidget *main_window);
static void show_progress_window(shutdown_t *shutdown, user_specs_t *specs);
static shutdown_t * start_shutdown_with_current_specs_callback(void ***args);
static shutdown_t * start_shutdown_with_current_specs(user_specs_t *specs, GtkWidget *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);
static void update_main(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);
static gboolean update_progress_callback(void ***args);
static gboolean update_progress(shutdown_t *shutdown, GtkWidget *progress_bar);
static user_specs_t * update_user_specs(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember);

static void
destroy_progress_callback(void ***args)
{
	destroy_progress(*args[0], *args[1]);
}

static void
destroy_progress(shutdown_t *shutdown, GtkWidget *progress_window)
{
	if (shutdown->paused)
		gtk_main_quit();
	else if (shutdown->pid)
	{
		GtkWidget *window, *vbox, *label, *hbox, *button;
		char *shutdown_pending_message;

		window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
		gtk_window_set_transient_for(GTK_WINDOW (window), GTK_WINDOW (progress_window));
		gtk_window_set_title(GTK_WINDOW (window), "Abort active shutdown? - " PRGNAME);
		gtk_window_set_resizable(GTK_WINDOW (window), FALSE);
		gtk_window_set_position(GTK_WINDOW (window), GTK_WIN_POS_CENTER);
		gtk_container_set_border_width (GTK_CONTAINER (window), 10);
		g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL);
		g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);

		vbox = gtk_vbox_new(FALSE, 0);

		if ((pasprintf(&shutdown_pending_message, "There is a shutdown pending (PID %d), would you like to terminate it?", shutdown->pid)) == -1)
			show_error_dialog(EX_OSERR, "pasprintf() returned -1 in create_ask_kill_window(). Error: \"%s\" (%d)", strerror(errno), errno);

		label = gtk_label_new(shutdown_pending_message);
		gtk_box_pack_start(GTK_BOX (vbox), label, FALSE, FALSE, 5);
		gtk_widget_show(label);

		free(shutdown_pending_message); /* No longer required by the GTK, it stores the string itself */

		hbox = gtk_hbox_new(FALSE, 0);

		gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, FALSE, 5);

		button = gtk_button_new_from_stock(GTK_STOCK_YES);
		g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (exit_and_abort), shutdown);
		gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5);
		gtk_widget_show(button);

		button = gtk_button_new_from_stock(GTK_STOCK_NO);
		g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (exit_no_abort), NULL);
		gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5);
		gtk_widget_show(button);

		button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
		g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), window);
		gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5);
		gtk_widget_show(button);

		gtk_container_add(GTK_CONTAINER (window), vbox);

		gtk_widget_show(hbox);
		gtk_widget_show(vbox);
		gtk_widget_show(window);

		gtk_main();
	}
	else
	{
		show_warning_dialog("Shutdown PID not known, please terminate the relevant shutdown process manually, if so desired");

		gtk_main_quit();
	}
}

static void
exit_and_abort(shutdown_t *shutdown)
{
	if (kill(shutdown->pid, SIGTERM) != 0) /* shutdown->pid is checked to ensure it's > 0 in shutdown.c */
		show_error_dialog(EX_OSERR, "Couldn't kill PID %d, error: \"%s\" (%d).\n\nPlease kill the shutdown(8) process manually.", shutdown->pid, strerror(errno), errno);

	exit(EXIT_SUCCESS);
}

static void
exit_no_abort(void)
{
	exit(EXIT_SUCCESS);
}

static void
file_selected_callback(void ***args)
{
	file_selected(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5]);
}

static void
file_selected(user_specs_t *specs, GtkWidget *file_selection, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	char *config_path;

	config_path = (char *) gtk_file_selection_get_filename(GTK_FILE_SELECTION (file_selection));

	if (load_user_specs(specs, config_path)) /* Not doing any checking of config_path here. If for any reason it's invalid, load_settings() should spit */
		update_main(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember);
	else
		show_warning_dialog("Unable to open configuration file \"%s\". Error: \"%s\" (%d)", config_path, strerror(errno), errno);
}

static void
pause_shutdown_callback(void ***args)
{
	pause_shutdown(*args[0], *args[1]);
}

static void
pause_shutdown(shutdown_t *shutdown, GtkWidget *progress_bar)
{
	time_t current_time;

	current_time = time(NULL);

	if (!shutdown->paused) /* The shutdown isn't currently paused */
	{
		if (kill(shutdown->pid, SIGTERM) != 0) /* We're not checking that the PID is valid here as this has already been done in start_shutdown(), which must have been successfully called if we're here */
		{
			show_warning_dialog("Couldn't kill PID %d in order to pause the shutdown. Error: \"%s\" (%d)", shutdown->pid, strerror(errno), errno);
		}

		shutdown->timeout = max_minutes_to_shutdown(shutdown);
		shutdown->start_time -= current_time; /* shutdown->start_time now holds the start time relative to the current time (<= 0), rather than relative to the epoch */
		shutdown->paused = TRUE; /* Indicate that a shutdown is paused. This'll stop the progress bar from updating */

		gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), "(Paused)");
	}
	else
	{
		user_specs_t specs;
		char *str_time_to_shutdown;

		shutdown->start_time += current_time;
		shutdown->shutdown_time = current_time + (shutdown->timeout * 60);
		specs.timeout = shutdown->timeout;
		specs.timeout_unit = MINUTES;
		specs.shutdown_type = shutdown->type;
		specs.remember = FALSE;

		if ((start_shutdown(&specs, shutdown)) == NULL)
			show_error_dialog(EX_OSERR, "Couldn't re-launch shutdown(8) following a pause. Error: \"%s\" (%d)", strerror(errno), errno);

		shutdown->paused = FALSE; /* Indicate that no shutdown is paused, allowing the progress bar to start updating again */

		gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), str_time_to_shutdown = get_shutdown_time_left_string(shutdown));

		free(str_time_to_shutdown); /* The GTK doesn't need this anymore, as it copies the text to its own memory area */
	}
}

static void
save_current_user_specs_callback(void ***args)
{
	save_current_user_specs(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5]);
}

static void
save_current_user_specs(char *config_path, GtkWindow *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	user_specs_t specs;

	if (update_user_specs(&specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember) == NULL)
		return;

	save_user_specs(&specs, config_path);
}

static void
select_and_open_config_callback(void ***args)
{
	select_and_open_config(*args[0], *args[1], *args[2], *args[3], *args[4]);
}

static void
select_and_open_config(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	GtkWidget *file_selection;
	void **file_selected_callback_args[6];

	file_selected_callback_args[0] = (void **) &specs;
	file_selected_callback_args[1] = (void **) &file_selection;
	file_selected_callback_args[2] = (void **) &timeout_field;
	file_selected_callback_args[3] = (void **) &timeout_unit_combo_box;
	file_selected_callback_args[4] = (void **) &shutdown_type_combo_box;
	file_selected_callback_args[5] = (void **) &toggle_remember;

	file_selection = gtk_file_selection_new("Select configuration file");
	gtk_file_selection_set_filename(GTK_FILE_SELECTION (file_selection), specs->config_path == NULL ? CONFIG_FILE_NAME : specs->config_path);

	g_signal_connect(G_OBJECT (file_selection), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL);
	g_signal_connect(G_OBJECT (file_selection), "destroy", G_CALLBACK (gtk_main_quit), NULL);
	g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->ok_button), "clicked", G_CALLBACK (file_selected_callback), file_selected_callback_args);
	g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->ok_button), "clicked", G_CALLBACK (gtk_widget_destroy), file_selection);
	g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (file_selection)->cancel_button), "clicked", G_CALLBACK (gtk_widget_destroy), file_selection);

	gtk_widget_show(GTK_WIDGET (file_selection));

	gtk_main();
}

static void
save_prefs_callback(void ***args)
{
	save_prefs(*args[0], *args[1], *args[2], *args[3], *args[4]);
}

static void
save_prefs(user_specs_t *specs, GtkWidget *window, GtkWidget *main_window_on_top_box, GtkWidget *progress_window_on_top_box, GtkWidget *main_window)
{
	user_specs_t save_specs;

	init_user_specs(&save_specs);

	save_specs.main_window_always_on_top = specs->main_window_always_on_top = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (main_window_on_top_box));
	save_specs.progress_window_always_on_top = specs->progress_window_always_on_top = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (progress_window_on_top_box));

	gtk_window_set_keep_above(GTK_WINDOW(main_window), save_specs.main_window_always_on_top);

	if (!specs->config_path)
		show_stderr("No configuration file open, preferences will not be remembered");
	else if (!load_user_specs(&save_specs, specs->config_path) && errno != ENOENT)	/* If there ain't an existing configuration file, we'll just write a new 'un */
		show_warning_dialog("Couldn't update preferences in configuration file \"%s\". Error: \"%s\" (%d)", specs->config_path, strerror(errno), errno);
	else
		save_user_specs(&save_specs, specs->config_path);

	gtk_widget_destroy(window);
}

static void
show_about(void)
{
	GtkWidget *about_dialog;
	char *authors[] = AUTHORS;

	about_dialog = gtk_about_dialog_new();

	gtk_about_dialog_set_name(GTK_ABOUT_DIALOG (about_dialog), PRGNAME);
	gtk_about_dialog_set_version(GTK_ABOUT_DIALOG (about_dialog), VERSION);
	gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG (about_dialog), COPYRIGHT);
	gtk_about_dialog_set_website(GTK_ABOUT_DIALOG (about_dialog), CONTACT_WWW);
	gtk_about_dialog_set_license(GTK_ABOUT_DIALOG (about_dialog), LICENSE_LONG);
	gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG (about_dialog), (const gchar **) authors);

	g_signal_connect(GTK_ABOUT_DIALOG (about_dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL);

	gtk_widget_show(about_dialog);
}

void
show_main_window(user_specs_t *specs)
{
	GtkAccelGroup *accel_group;
	GtkWidget *window, *timeout_field, *timeout_unit_combo_box, *shutdown_type_combo_box, *toggle_remember;
	GtkWidget *menu_bar, *menu, *menu_item, *vbox, *hbox, *label, *separator, *button, *ok_button;
	void **select_and_open_config_callback_args[5], **show_prefs_callback_args[2], **save_current_user_specs_callback_args[6], **start_shutdown_with_current_specs_callback_args[6];

	select_and_open_config_callback_args[0] = (void **) &specs;
	select_and_open_config_callback_args[1] = (void **) &timeout_field;
	select_and_open_config_callback_args[2] = (void **) &timeout_unit_combo_box;
	select_and_open_config_callback_args[3] = (void **) &shutdown_type_combo_box;
	select_and_open_config_callback_args[4] = (void **) &toggle_remember;
	save_current_user_specs_callback_args[0] = (void **) &(specs->config_path);
	save_current_user_specs_callback_args[1] = (void **) &window;
	save_current_user_specs_callback_args[2] = (void **) &timeout_field;
	save_current_user_specs_callback_args[3] = (void **) &timeout_unit_combo_box;
	save_current_user_specs_callback_args[4] = (void **) &shutdown_type_combo_box;
	save_current_user_specs_callback_args[5] = (void **) &toggle_remember;
	show_prefs_callback_args[0] = (void **) &specs;
	show_prefs_callback_args[1] = (void **) &window;
	start_shutdown_with_current_specs_callback_args[0] = (void **) &specs;
	start_shutdown_with_current_specs_callback_args[1] = (void **) &window;
	start_shutdown_with_current_specs_callback_args[2] = (void **) &timeout_field;
	start_shutdown_with_current_specs_callback_args[3] = (void **) &timeout_unit_combo_box;
	start_shutdown_with_current_specs_callback_args[4] = (void **) &shutdown_type_combo_box;
	start_shutdown_with_current_specs_callback_args[5] = (void **) &toggle_remember;

	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL);
	g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
	gtk_window_set_title(GTK_WINDOW(window), PRGNAME);
	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
	gtk_window_set_keep_above(GTK_WINDOW(window), specs->main_window_always_on_top);

	accel_group = gtk_accel_group_new();
	gtk_window_add_accel_group(GTK_WINDOW (window), accel_group);

	vbox = gtk_vbox_new(FALSE, 0);

	menu_bar = gtk_menu_bar_new();
	gtk_box_pack_start(GTK_BOX (vbox), menu_bar, FALSE, FALSE, 0);

	/* File menu */
	menu = gtk_menu_new();

	menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, accel_group);
	g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (select_and_open_config_callback), select_and_open_config_callback_args);
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

	menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, accel_group);
	g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (save_current_user_specs_callback), save_current_user_specs_callback_args);
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

        menu_item = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

	menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);
	g_signal_connect(G_OBJECT (menu_item), "activate", G_CALLBACK (gtk_main_quit), NULL);
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

	menu_item = gtk_menu_item_new_with_mnemonic("_File");
	gtk_widget_show(menu_item);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu);
	gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item);

	/* Edit menu */
	menu = gtk_menu_new();

	menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, accel_group);
	g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (show_prefs_callback), show_prefs_callback_args);
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

	menu_item = gtk_menu_item_new_with_mnemonic("_Edit");
	gtk_widget_show(menu_item);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu);
	gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item);

	/* Help menu */
	menu = gtk_menu_new();

	menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, accel_group);
	g_signal_connect_swapped(G_OBJECT (menu_item), "activate", G_CALLBACK (show_about), NULL);
	gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
	gtk_widget_show(menu_item);

	menu_item = gtk_menu_item_new_with_mnemonic("_Help");
	gtk_widget_show(menu_item);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM (menu_item), menu);
	gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), menu_item);

	gtk_widget_show(menu_bar);

	/*
	* Timeout selection
	*/

	hbox = gtk_hbox_new(FALSE, 0);

	label = gtk_label_new("Timeout:");
	gtk_box_pack_start(GTK_BOX (hbox), label, FALSE, FALSE, 5);
	gtk_widget_show(label);

	timeout_field = gtk_entry_new();
	g_signal_connect_swapped(G_OBJECT (timeout_field), "activate", G_CALLBACK (start_shutdown_with_current_specs_callback), start_shutdown_with_current_specs_callback_args);
	gtk_box_pack_start(GTK_BOX (hbox), timeout_field, FALSE, FALSE, 5);
	gtk_widget_show(timeout_field);

	timeout_unit_combo_box = gtk_combo_box_new_text();
	gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "minutes"); /* Be careful of order here - replicate in TIMEOUT_TYPES (in downtime.h) */
	gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "hours");
	gtk_combo_box_append_text(GTK_COMBO_BOX (timeout_unit_combo_box), "days");
	gtk_combo_box_set_active(GTK_COMBO_BOX (timeout_unit_combo_box), (gint) specs->timeout_unit);
	gtk_box_pack_start(GTK_BOX (hbox), timeout_unit_combo_box, FALSE, FALSE, 5);
	gtk_widget_show(timeout_unit_combo_box);

	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	/*
	* Shutdown type selection
	*/

	hbox = gtk_hbox_new(FALSE, 0);

	label = gtk_label_new("Shutdown type:");
	gtk_box_pack_start(GTK_BOX (hbox), label, FALSE, FALSE, 5);
	gtk_widget_show(label);

	shutdown_type_combo_box = gtk_combo_box_new_text();
	gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Power-off"); /* Be careful of order here - replicate in SHUTDOWN_TYPES (in downtime.h) */
	gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Restart");
	gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Halt");
	gtk_combo_box_append_text(GTK_COMBO_BOX (shutdown_type_combo_box), "Single user mode");
	gtk_combo_box_set_active(GTK_COMBO_BOX (shutdown_type_combo_box), (gint) specs->shutdown_type);
	gtk_box_pack_start(GTK_BOX (hbox), shutdown_type_combo_box, FALSE, TRUE, 5);
	gtk_widget_show(shutdown_type_combo_box);

	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	separator = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX (vbox), separator, FALSE, TRUE, 5);
	gtk_widget_show(separator);

	/*
	* "Remember these settings"
	*/

	toggle_remember = gtk_check_button_new_with_label("Remember these settings");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (toggle_remember), specs->remember);
	gtk_box_pack_start(GTK_BOX (vbox), toggle_remember, FALSE, TRUE, 5);
	gtk_widget_show(toggle_remember);

	separator = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX (vbox), separator, FALSE, TRUE, 5);
	gtk_widget_show(separator);

	/*
	* OK / Cancel buttons
	*/

	hbox = gtk_hbox_new(FALSE, 0);

	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
	g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (gtk_main_quit), NULL);
	gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, TRUE, 5);
	gtk_widget_show(button);

	ok_button = gtk_button_new_from_stock(GTK_STOCK_OK);
	g_signal_connect_swapped(G_OBJECT (ok_button), "clicked", G_CALLBACK (start_shutdown_with_current_specs_callback), start_shutdown_with_current_specs_callback_args);
	gtk_box_pack_start(GTK_BOX (hbox), ok_button, FALSE, TRUE, 5);
	gtk_widget_show(ok_button);

	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	gtk_container_add(GTK_CONTAINER (window), vbox);
	gtk_widget_show(vbox);

	GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
	gtk_widget_grab_default(ok_button);

	update_main(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember);

	gtk_widget_show(GTK_WIDGET (window));

	gtk_main();
}

void
show_error_dialog(int eval, const char *fmt, ...)
{
	va_list ap;
	char *exp_fmt;
	GtkWidget *message_dialog;

	va_start(ap, fmt);

	if ((pvasprintf(&exp_fmt, fmt, ap)) == -1)
	{
		show_stderr("pvasprintf() returned -1 in show_error_dialog(). Error: \"%s\" (%d)", strerror(errno), errno);

		exit(EX_OSERR);
	}

	va_end(ap);

	show_stderr(exp_fmt);

	message_dialog = gtk_message_dialog_new(NULL, (GtkDialogFlags) NULL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, exp_fmt);
	gtk_window_set_title(GTK_WINDOW (message_dialog), "Error - " PRGNAME);
	gtk_dialog_run(GTK_DIALOG (message_dialog));
	gtk_widget_destroy(message_dialog);

	exit(eval);
}

static void
show_prefs_callback(void ***args)
{
	show_prefs(*args[0], *args[1]);
}

static void
show_prefs(user_specs_t *specs, GtkWidget *main_window)
{
	GtkWidget *window, *main_window_on_top_box, *progress_window_on_top_box;
	GtkWidget *vbox, *hbox, *button_cancel, *button_ok;
	void **save_prefs_callback_args[5];

	save_prefs_callback_args[0] = (void **) &specs;
	save_prefs_callback_args[1] = (void **) &window;
	save_prefs_callback_args[2] = (void **) &main_window_on_top_box;
	save_prefs_callback_args[3] = (void **) &progress_window_on_top_box;
	save_prefs_callback_args[4] = (void **) &main_window;

	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(window), "Preferences - " PRGNAME);
	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

	vbox = gtk_vbox_new(FALSE, 0);

	hbox = gtk_hbox_new(FALSE, 0);
	main_window_on_top_box = gtk_check_button_new_with_mnemonic("Main window always on _top");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (main_window_on_top_box), specs->main_window_always_on_top);
	gtk_box_pack_start(GTK_BOX (hbox), main_window_on_top_box, FALSE, TRUE, 5);
	gtk_widget_show(main_window_on_top_box);
	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	hbox = gtk_hbox_new(FALSE, 0);
	progress_window_on_top_box = gtk_check_button_new_with_mnemonic("_Progress window always on top");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (progress_window_on_top_box), specs->progress_window_always_on_top);
	gtk_box_pack_start(GTK_BOX (hbox), progress_window_on_top_box, FALSE, TRUE, 5);
	gtk_widget_show(progress_window_on_top_box);
	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	hbox = gtk_hbox_new(FALSE, 0);
	button_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
	button_ok = gtk_button_new_from_stock(GTK_STOCK_OK);
	gtk_box_pack_start(GTK_BOX (hbox), button_cancel, FALSE, TRUE, 5);
	gtk_box_pack_start(GTK_BOX (hbox), button_ok, FALSE, TRUE, 5);
	gtk_widget_show(button_ok);
	gtk_widget_show(button_cancel);
	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, TRUE, 5);
	gtk_widget_show(hbox);

	gtk_container_add(GTK_CONTAINER (window), vbox);
	gtk_widget_show(vbox);

	g_signal_connect(G_OBJECT (window), "delete_event", G_CALLBACK (gtk_widget_destroy), NULL);
	g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
	g_signal_connect_swapped(G_OBJECT (button_cancel), "clicked", G_CALLBACK (gtk_widget_destroy), window);
	g_signal_connect_swapped(G_OBJECT (button_ok), "clicked", G_CALLBACK (save_prefs_callback), save_prefs_callback_args);

	GTK_WIDGET_SET_FLAGS(button_ok, GTK_CAN_DEFAULT);
	gtk_widget_grab_default(button_ok);

	gtk_widget_show(window);

	gtk_main();
}

static void
show_progress_window(shutdown_t *shutdown, user_specs_t *specs)
{
	GtkWidget *window, *progress_bar, *vbox, *hbox, *button, *close_button;
	char *str_time_to_shutdown;
	void **destroy_progress_callback_args[2], **update_progress_callback_args[2], **pause_shutdown_callback_args[2];

	destroy_progress_callback_args[0] = (void **) &shutdown;
	destroy_progress_callback_args[1] = (void **) &window;
	update_progress_callback_args[0] = (void **) &shutdown;
	update_progress_callback_args[1] = (void **) &progress_bar;
	pause_shutdown_callback_args[0] = (void **) &shutdown;
	pause_shutdown_callback_args[1] = (void **) &progress_bar;

	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect_swapped(G_OBJECT (window), "delete_event", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args);
	g_signal_connect_swapped(G_OBJECT (window), "destroy", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args);

	gtk_window_set_title(GTK_WINDOW (window), "Shutdown progress - " PRGNAME);
	gtk_window_set_resizable(GTK_WINDOW (window), FALSE);
	gtk_window_set_position(GTK_WINDOW (window), GTK_WIN_POS_CENTER);
	gtk_window_set_keep_above(GTK_WINDOW(window), specs->progress_window_always_on_top);
	gtk_container_set_border_width (GTK_CONTAINER (window), 10);

	vbox = gtk_vbox_new(FALSE, 0);

	progress_bar = gtk_progress_bar_new();
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), (str_time_to_shutdown = get_shutdown_time_left_string(shutdown)));
	g_timeout_add((((shutdown->shutdown_time - shutdown->start_time) > 100) ? 1000 : (shutdown->shutdown_time - shutdown->start_time) * 10), (GSourceFunc) update_progress_callback, update_progress_callback_args); /* Read "((shutdown->shutdown_time - shutdown->start_time) > 100) ? 1000" as "(shutdown->shutdown_time - shutdown->start_time) * 10 > 1000". * 10 because the first part of the expression should be divided by 100 to get a percentage, then multiplied by 1000 to go from seconds to milliseconds */

	free(str_time_to_shutdown);
	gtk_box_pack_start(GTK_BOX (vbox), progress_bar, FALSE, FALSE, 5);
	gtk_widget_show(progress_bar);

	hbox = gtk_hbox_new(FALSE, 0);

	button = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PAUSE);
	g_signal_connect_swapped(G_OBJECT (button), "clicked", G_CALLBACK (pause_shutdown_callback), pause_shutdown_callback_args);

	if (!shutdown->pid)
		gtk_widget_set_sensitive(button, FALSE); /* Disable the pause button if we don't know the shutdown(8) process (if any)' PID */

	gtk_box_pack_start(GTK_BOX (hbox), button, FALSE, FALSE, 5);
	gtk_widget_show(button);

	close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
	g_signal_connect_swapped(G_OBJECT (close_button), "clicked", G_CALLBACK (destroy_progress_callback), destroy_progress_callback_args);
	gtk_box_pack_start(GTK_BOX (hbox), close_button, FALSE, FALSE, 5);
	gtk_widget_show(close_button);

	gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, FALSE, 5);
	gtk_widget_show(hbox);
	gtk_container_add(GTK_CONTAINER (window), vbox);

	gtk_widget_show(vbox);
	gtk_widget_show(window);

	gtk_main();
}

void
show_stderr(const char *fmt, ...)
{
	va_list ap;
	char *exp_fmt;
	time_t curr_time;
	struct tm *tm;

	va_start(ap, fmt);

	if ((pvasprintf(&exp_fmt, fmt, ap)) == -1)
		show_error_dialog(EX_OSERR, "pvasprintf() returned -1 in show_stderr(). Error: \"%s\" (%d)", strerror(errno), errno);

	va_end(ap);

	curr_time = time((time_t *) NULL);
	tm = localtime(&curr_time);

	fprintf(stderr, "%d-%.2d-%.2d %.2d:%.2d:%.2d %s: %s\n", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, PRGNAME, exp_fmt);
}

void
show_warning_dialog(const char *fmt, ...)
{
	va_list ap;
	char *exp_fmt;
	GtkWidget *message_dialog;

	va_start(ap, fmt);

	if ((pvasprintf(&exp_fmt, fmt, ap)) == -1)
		show_error_dialog(EX_OSERR, "pvasprintf() returned -1 in show_warning_dialog(). Error: \"%s\" (%d)", strerror(errno), errno);

	va_end(ap);

	show_stderr(exp_fmt);

	message_dialog = gtk_message_dialog_new(NULL, (GtkDialogFlags) NULL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, exp_fmt);
	gtk_window_set_title(GTK_WINDOW (message_dialog), "Warning - " PRGNAME);
	gtk_dialog_run(GTK_DIALOG (message_dialog));
	gtk_widget_destroy(message_dialog);

	free(exp_fmt);
}

static shutdown_t *
start_shutdown_with_current_specs_callback(void ***args)
{
	return (start_shutdown_with_current_specs(*args[0], *args[1], *args[2], *args[3], *args[4], *args[5]));
}

static shutdown_t *
start_shutdown_with_current_specs(user_specs_t *specs, GtkWidget *main_window, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	shutdown_t *shutdown;

	if (update_user_specs(specs, timeout_field, timeout_unit_combo_box, shutdown_type_combo_box, toggle_remember) == NULL) /* If we weren't able to successfully read the user specs... Note also that update_user_specs() deals with alerting the user about input dramas */
		return (NULL);

	if (specs->remember)
		save_user_specs(specs, specs->config_path);

	if ((shutdown = start_shutdown(specs, NULL)))
	{
		gtk_widget_destroy(GTK_WIDGET (main_window));

		show_progress_window(shutdown, specs);
	}

	return (shutdown);
}

static void
update_main(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	char *timeout;

	if ((pasprintf(&timeout, "%lu", specs->timeout)) == -1)
		show_error_dialog(EX_OSERR, "pasprintf() returned -1 in update_main(). Error: \"%s\" (%d)", strerror(errno), errno);

	gtk_entry_set_text(GTK_ENTRY (timeout_field), timeout);

	free(timeout);

	gtk_combo_box_set_active(GTK_COMBO_BOX (timeout_unit_combo_box), (gint) specs->timeout_unit);
	gtk_combo_box_set_active(GTK_COMBO_BOX (shutdown_type_combo_box), (gint) specs->shutdown_type);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (toggle_remember), specs->remember);
}

static gboolean
update_progress_callback(void ***args)
{
	return (update_progress(*args[0], *args[1]));
}

static gboolean
update_progress(shutdown_t *shutdown, GtkWidget *progress_bar)
{
	double new_progress_percent;
	char *str_time_to_shutdown;

	if (shutdown->paused) /* If we're paused, we only need to update the progress bar text */
		return (TRUE); /* Return TRUE so that the timer call to this function isn't canned */

	if (!shutdown->in_progress)
		return (FALSE);

	if (shutdown->pid && kill(shutdown->pid, 0) != 0) /* Ensure that shutdown(8) is still running. shutdown->pid is checked to ensure it's > 0 in shutdown.c */
	{
		int status;

		waitpid(shutdown->pid, &status, WNOHANG);

		if (WIFEXITED (status))
			show_error_dialog(EX_UNAVAILABLE, "shutdown(8) (PID: %ld) exited unexpectedly with exit status %d (\"%s\")", shutdown->pid, WEXITSTATUS (status), strerror(WEXITSTATUS (status)));
		else
			show_error_dialog(EX_UNAVAILABLE, "shutdown(8) (PID: %ld) terminated unexpectedly", shutdown->pid);
	}

	new_progress_percent = difftime(shutdown->shutdown_time, shutdown->start_time) > 0 ? (100 * ((double) difftime(time(NULL), shutdown->start_time)) / (double) (difftime(shutdown->shutdown_time, shutdown->start_time))) : 0;

	if (new_progress_percent > 100)
		new_progress_percent = 100;
	else if (new_progress_percent < 0)
		new_progress_percent = 0;

	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR (progress_bar), new_progress_percent / 100);
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), (str_time_to_shutdown = get_shutdown_time_left_string(shutdown)));

	free(str_time_to_shutdown);

	return (TRUE);
}

/* Updates the structure pointed to by specs with user-supplied specifications from the DownTime main dialogue. DO NOT call after the main window has been destroyed */
static user_specs_t *
update_user_specs(user_specs_t *specs, GtkWidget *timeout_field, GtkWidget *timeout_unit_combo_box, GtkWidget *shutdown_type_combo_box, GtkWidget *toggle_remember)
{
	char *nptr, *endptr;
	long int timeout;

	nptr = (char *) gtk_entry_get_text(GTK_ENTRY (timeout_field));
	timeout = (unsigned long) strtol(nptr, &endptr, 10);
	
	if (!(*nptr != '\0' && *endptr == '\0'))
	{
		show_warning_dialog("An invalid timeout - \"%s\" - was specified", nptr);
	
		return (NULL);
	}

	specs->timeout = timeout;
	specs->timeout_unit = gtk_combo_box_get_active(GTK_COMBO_BOX (timeout_unit_combo_box));
	specs->shutdown_type = gtk_combo_box_get_active(GTK_COMBO_BOX (shutdown_type_combo_box));
	specs->remember = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (toggle_remember));

	return (specs);
}



syntax highlighted by Code2HTML, v. 0.9.1