/*
** 1998-05-17 -	I just have to build an Opus-look-alike GTK GUI! :)
** 1998-09-11 -	Er, it like, grew, or something.
*/

#include "gentoo.h"

#include <dlfcn.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <sys/wait.h>

#include "errors.h"
#include "dirpane.h"
#include "dialog.h"
#include "userinfo.h"
#include "fileutil.h"
#include "xmlutil.h"
#include "strutil.h"
#include "types.h"
#include "styles.h"
#include "sizeutil.h"
#include "buttons.h"
#include "queue.h"
#include "cmdseq.h"
#include "children.h"
#include "dpformat.h"
#include "options.h"
#include "shortcuts.h"
#include "mount.h"
#include "keyboard.h"
#include "controls.h"
#include "iconutil.h"
#include "cmdseq_config.h"

#include "cfg_gui.h"
#include "cfg_module.h"
#include "cfg_possize.h"

/* No default icon path supplied (expected from use of -D on command line)? */
#if !defined(PTH_ICN)
#define	PTH_ICN	"icons:/usr/local/lib/gentoo/icons"
#endif

/* Also make sure there's a GTK+ RC default path. */
#if !defined(PTH_GRC)
#define PTH_GRC	"$HOME/"
#endif

#include "graphics/icon_iconify.xpm"

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-18 -	Filter out files the user really doesn't want to see, and that recursive
**		directory traversing code really, really, REALLY, doesn't.
*/
static gboolean dir_filter(const gchar *name)
{
	if((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0))
		return FALSE;
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-11 -	This is called as GTK+ quits. Very handy. */
static gint quit_handler(gpointer data)
{
	chd_kill_children();

	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-30 -	Update the position and size things, since we're about to quit. */
static void update_possize(MainInfo *min)
{
	gint	save = FALSE;

	if(min->cfg.wininfo.pos.flags & PSPF_SAVE)
	{
		gdk_window_get_position(min->gui->window->window, &min->cfg.wininfo.pos.a, &min->cfg.wininfo.pos.b);
		save = TRUE;
	}
	if(min->cfg.wininfo.size.flags & PSPF_SAVE)
	{
		gdk_window_get_size(min->gui->window->window, &min->cfg.wininfo.size.a, &min->cfg.wininfo.size.b);
		save = TRUE;
	}
	if(save)
		csq_execute(min, "ConfigureSave");
}

/* 1998-10-21 -	This is gets called when the user closes the main window, i.e. when gentoo
**		being shut down nicely (as opposed to being QuitNow:ed).
*/
static gint evt_main_delete(GtkWidget *wid, GdkEventAny *evt)
{
	MainInfo	*min = (MainInfo *) gtk_object_get_user_data(GTK_OBJECT(wid));
	Dialog		*dlg = NULL;
	guint		res;

	update_possize(min);

	if(min->cfg.flags & CFLG_CHANGED)
	{
		dlg = dlg_dialog_simple("You may have some unsaved configuration changes.\n"
					   "Quitting without saving will lose them. Really quit?",
					   "Confirm Quitting", "Quit|Save, then Quit|Cancel", NULL, NULL);
		res = dlg_dialog_wait(dlg);
		if(res == 1)
			csq_execute(min, "ConfigureSave");
		else if(res == 2)
			return TRUE;
	}
	gtk_main_quit();
	return FALSE;
}

/* 1998-10-21 -	This gets called as the user closes the window. */
static void evt_main_destroy(GtkWidget *wid, gpointer data)
{
	gtk_main_quit();
}

/* 1998-10-26 -	Initialize some fields in the dp structure. */
static void clear_pane(DirPane *dp, int index)
{
	dp->index = index;
	dp->dir.line = NULL;
	dp->dir.auxbuf = NULL;
	dp->hist.history = NULL;
}

/* 1998-05-17 -	Build a couple of dir-panes.
** 1998-05-18 -	Now also puts the panes in a horizontally paned window, for extra Opusity. ;^)
** 1998-08-02 -	Fixed *huge* bug where pane 1 (the right) was built using pane 0's format!!
*/
static GtkWidget * build_dirpanes(MainInfo *min)
{
	GtkWidget	*box, *left, *right;
	GuiInfo		*gui;

	gui = min->gui;

	if((box = gtk_hbox_new(FALSE, 0)) != NULL)
	{
		if((gui->hpane = gtk_hpaned_new()) != NULL)
		{
			clear_pane(&gui->pane[0], 0);
			if((left = dp_build(min, &min->cfg.dp_format[0], &gui->pane[0])) != NULL)
			{
				clear_pane(&gui->pane[1], 1);
				if((right = dp_build(min, &min->cfg.dp_format[1], &gui->pane[1])) != NULL)
				{
					gtk_paned_add1(GTK_PANED(gui->hpane), left);
					gtk_paned_add2(GTK_PANED(gui->hpane), right);
					gtk_paned_set_handle_size(GTK_PANED(gui->hpane), 10);
					gtk_paned_set_gutter_size(GTK_PANED(gui->hpane), 10);
					gtk_box_pack_start(GTK_BOX(box), gui->hpane,  TRUE, TRUE, 0);
					gtk_widget_show(gui->hpane);
					gtk_widget_show(box);
					return box;
				}
				gtk_widget_destroy(left);
			}
			gtk_widget_destroy(gui->hpane);
		}
		gtk_widget_destroy(box);
	}
	return NULL;
}

/* 1998-05-19 -	Create the top widgetry, returning something the caller can just add to
**		a box. Sets the <label> pointer to a GtkLabel which can be used later
**		to display funny messages and stuff. The label is internally wrapped
**		in a GtkEventbox, which makes updating it possible without causing
**		instant epilepsy in everyone watching. GTK+ really is smooth.
** 1998-11-26 -	Made the label focusable (?), in order to *finally* have somewhere safe
**		to put the focus when I don't want it on path entry widgets. Nice.
*/
static GtkWidget * build_top(GtkLabel **label)
{
	GtkWidget	*top, *lab, *hbox;

	if((top = gtk_event_box_new()) != NULL)
	{
		hbox = gtk_hbox_new(FALSE, 0);
		lab = gtk_label_new("");
		GTK_WIDGET_SET_FLAGS(GTK_WIDGET(lab), GTK_CAN_FOCUS);
		gtk_box_pack_start(GTK_BOX(hbox), lab, TRUE, TRUE, 0);
		gtk_widget_show(lab);
		gtk_container_add(GTK_CONTAINER(top), hbox);
		gtk_widget_show(hbox);
		gtk_widget_show(top);
		if(label != NULL)
			*label = GTK_LABEL(lab);
	}
	return top;
}

/* 1998-12-25 -	Rewritten in a lot less lines thanks to the new shortcuts module. */
static GtkWidget * build_bottom(MainInfo *min)
{
	GtkWidget	*hbox, *bbank;

	if((hbox = gtk_hbox_new(FALSE, 0)) != NULL)
	{
		bbank = btn_buttonsheet_build(min, &min->cfg.buttons, NULL, FALSE, NULL, min);
		btn_buttonsheet_built_add_keys(min, GTK_CONTAINER(bbank), NULL);
		shc_shortcutbank_pack(min, min->cfg.shortcutbank, hbox, bbank);
	}
	return hbox;
}

/* 1998-10-26 -	Rebuild the middle part of the GUI, i.e. the panes. */
void rebuild_middle(MainInfo *min)
{
	GtkWidget	*left, *right;

	gtk_container_remove(GTK_CONTAINER(min->gui->hpane), GTK_WIDGET(min->gui->pane[0].vbox));
	gtk_container_remove(GTK_CONTAINER(min->gui->hpane), GTK_WIDGET(min->gui->pane[1].vbox));
	if((left = dp_build(min, &min->cfg.dp_format[0], &min->gui->pane[0])) != NULL)
		gtk_paned_add1(GTK_PANED(min->gui->hpane), left);
	if((right = dp_build(min, &min->cfg.dp_format[1], &min->gui->pane[1])) != NULL)
		gtk_paned_add2(GTK_PANED(min->gui->hpane), right);
}

/* 1998-07-14 -	Rebuild the bottom part of the GUI, whose main responsibility is to contain
**		the button bank(s).
*/
void rebuild_bottom(MainInfo *min)
{
	gtk_widget_destroy(min->gui->bottom);
	if((min->gui->bottom = build_bottom(min)) != NULL)
	{
		gtk_box_pack_start(GTK_BOX(min->gui->vbox), min->gui->bottom, FALSE, FALSE, 0);
		gtk_widget_show(min->gui->bottom);
	}
}

#define	MNU_SEP	"---"
#define	MNU_SUB	'/'

/* 1998-06-04 -	Go through the menu definitions in min->cfg.dpmenuroot, and construct
**		the corresponding GTK menu structure, which is returned on success.
**		Has rudimentary support for one-level submenus. Lesson learned while
**		doing that: don't ever connect "activate" signal of a submenu-having
**		item. GTK+ won't like that much.
** 1998-07-12 -	Tried adding support for separators, in the most obvious way I could
**		figure out. Doesn't look good at all. Then wisened up, looked in the
**		source for GtkMenuFactory, and simplified. Works fine.
*/
static GtkWidget * build_dp_menu(MainInfo *min)
{
	char		*sub;
	MenuItem	*mni = min->cfg.dpmenuroot.menuitem;
	GtkWidget	*menu, *item, *last = NULL, *submenu = NULL;
	gint		lasth = 0;

	if((menu = gtk_menu_new()) != NULL)
	{
		while(mni->text)
		{
			if(!strncmp(mni->text, MNU_SEP, strlen(MNU_SEP)))
			{
				if((item = gtk_menu_item_new()) != NULL)
				{
					gtk_menu_append(GTK_MENU(menu), item);
					gtk_widget_show(item);
				}
			}
			else
			{
				if((sub = strrchr(mni->text, MNU_SUB)) != NULL && last != NULL)
				{
					if(submenu == NULL)
						submenu = gtk_menu_new();
					if(submenu != NULL)
					{
						if((item = gtk_menu_item_new_with_label(sub + 1)) != NULL)
						{
							gtk_menu_append(GTK_MENU(submenu), item);
							gtk_widget_show(item);
							gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(evt_dirpane_menu), mni->command);
							gtk_object_set_user_data(GTK_OBJECT(item), (gpointer) min);
						}
					}
				}
				else
				{
					if(submenu != NULL)
					{
						gtk_menu_item_set_submenu(GTK_MENU_ITEM(last), submenu);
						gtk_signal_disconnect(GTK_OBJECT(last), lasth);
						submenu = NULL;
					}
					if((item = gtk_menu_item_new_with_label(mni->text)) != NULL)
					{
						gtk_menu_append(GTK_MENU(menu), item);
						gtk_widget_show(item);
						gtk_object_set_user_data(GTK_OBJECT(item), (gpointer) min);
						lasth = gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(evt_dirpane_menu), (gpointer) mni->command);
						last = item;
					}
				}

			}
			mni++;
		}
		if(last != NULL && submenu != NULL)
		{
			gtk_menu_item_set_submenu(GTK_MENU_ITEM(last), submenu);
			gtk_signal_disconnect(GTK_OBJECT(last), lasth);
		}
	}
	return menu;
}

/* 1999-03-26 -	Set icon for when gentoo gets iconified. Neato. */
static void set_icon(GtkWidget *win)
{
	GdkPixmap	*pmap;
	GdkBitmap	*bmap;

	if((pmap = gdk_pixmap_create_from_xpm_d(win->window, &bmap, NULL, icon_iconify_xpm)) != NULL)
	{
		gdk_window_set_icon(win->window, NULL, pmap, bmap);
		gdk_window_set_icon_name(win->window, "gentoo");
	}
}

static GtkWidget * build_gui(MainInfo *min)
{
	GtkWidget	*top, *panes, *bottom;

	if((min->gui = malloc(sizeof(*min->gui))) != NULL)
	{
		min->gui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
		gtk_window_set_title(GTK_WINDOW(min->gui->window), "gentoo v" VERSION);
		gtk_object_set_user_data(GTK_OBJECT(min->gui->window), (gpointer) min);
		min->gui->pane[0].main = min;
		min->gui->pane[0].list = NULL;
		min->gui->pane[1].main = min;
		min->gui->pane[1].list = NULL;
		min->gui->pane[0].flags = min->gui->pane[1].flags = 0UL;
		min->gui->vbox = gtk_vbox_new(FALSE, 0);
		min->gui->cur_dir = NULL;
		min->gui->kbd_ctx = kbd_context_new(min);
		kbd_context_attach(min->gui->kbd_ctx, GTK_WINDOW(min->gui->window));
		ctrl_keys_install(min->cfg.ctrlinfo, min->gui->kbd_ctx);
		if((top = build_top(&min->gui->top)) != NULL)
		{
			if((panes = build_dirpanes(min)) != NULL)
			{
				if((bottom = build_bottom(min)) != NULL)
				{
					if((min->gui->dpmenu = build_dp_menu(min)) != NULL)
					{
						gtk_box_pack_start(GTK_BOX(min->gui->vbox), top,    FALSE, FALSE, 0);
						gtk_box_pack_start(GTK_BOX(min->gui->vbox), panes,  TRUE,  TRUE,  0);
						gtk_box_pack_start(GTK_BOX(min->gui->vbox), bottom, FALSE, FALSE, 0);
						gtk_widget_show(bottom);
						gtk_widget_show(min->gui->vbox);
						min->gui->middle = panes;
						min->gui->bottom = bottom;
						return min->gui->vbox;
					}
					gtk_widget_destroy(bottom);	
				}
				gtk_widget_destroy(panes);
			}
			gtk_widget_destroy(top);
		}
		gtk_widget_destroy(min->gui->vbox);
	}
	return NULL;
}

/* 1998-08-23 -	Initialize the paths config data. */
static void init_paths(CfgInfo *cfg)
{
	cfg->path.path[PTID_ICON]  = g_string_new(PTH_ICN);
	cfg->path.path[PTID_GTKRC] = g_string_new(PTH_GRC);
	cfg->path.path[PTID_FSTAB] = g_string_new("/etc/fstab");
	cfg->path.path[PTID_MTAB]  = g_string_new("/proc/mounts");

	cfg->path.hideinfo.mode = HIDE_NONE;
	cfg->path.hideinfo.hide_re_src[0] = '\0';
	cfg->path.hideinfo.hide_re = NULL;
}

/* 1998-06-04 -	Initialize the menu displayed when user right-clicks on a DirPane. */
static void init_dp_menu(CfgInfo *cfg)
{
	static MenuItem	mnu[] = { {"Parent", "DirParent"}, {"Other", "DirFromOther"}, {"Rescan", "DirRescan"},
				{"Select", NULL}, {"/All", "SelectAll"}, {"/None", "SelectNone"},
				{"/Toggle", "SelectToggle"}, {"/RegExp...", "SelectRE"}, {"/Same Type", "SelectType"},
				{"/Extend", "SelectExt"},
				{"File Action", NULL}, {"/Default", "FileAction"}, {"/View", "FileAction action=View"},
				{"/Edit", "FileAction action=Edit"}, {"/Print", "FileAction action=Print"},
				{"/Play", "FileAction action=Play"},
				{"Run...", "Run"},
				{"---", NULL}, {"Configure...", "Configure"}, {NULL, NULL} };

	cfg->dpmenuroot.menuitem = mnu;
}

/* ----------------------------------------------------------------------------------------- */

static gint evt_main_sizealloc(GtkWidget *wid, GtkAllocation *alc, gpointer data)
{
	MainInfo	*min = (MainInfo *) data;

	if(min->cfg.wininfo.size.flags & PSPF_TRACK)	/* Do we track size changes? */
	{
		min->cfg.wininfo.size.a = alc->width;
		min->cfg.wininfo.size.b = alc->height;
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-11-29 -	Load the GTK+ RC file, allowing users to configure gentoo's looks. The
**		file is always named ".gentoogtkrc" (too long, I know) but you can put
**		it anywhere as long as you given gentoo the path (in the config).
*/
static void load_gtk_rc(MainInfo *min)
{
	gchar	*name;

	if((name = fut_locate(min->cfg.path.path[PTID_GTKRC]->str, ".gentoogtkrc")) != NULL)
		gtk_rc_parse(name);
}

/* 1999-04-04 -	Initialize default (er, and non-configurable) command options. I'll
**		build a GUI for this stuff real soon now.
*/
static void init_cmd_options(CfgInfo *cfg)
{
	cfg->opt_overwrite.show_info = TRUE;
	str_strncpy(cfg->opt_overwrite.datefmt, "%Y-%m-%d %H:%M.%S", sizeof cfg->opt_overwrite.datefmt);
}

#if 0
/* 1999-05-10 -	This is for temporary key checks during development. Simpler to get a trigger this way
**		than to mess with the controls and/or keyboard modules. Is that a sign of bad code there?
*/
static void evt_key_press(GtkWidget *wid, GdkEventKey *evt, gpointer user)
{
	MainInfo	*min = user;

	if((evt->keyval == GDK_s))
		dp_focus(min->gui->cur_dir, 3);
	else if(evt->keyval == GDK_x)
		dp_focus(min->gui->cur_dir, 8);
	else if(evt->keyval == GDK_z)
		dp_unfocus(min->gui->cur_dir);
}
#endif

int main(int argc, char *argv[])
{
	gchar		rcname[PATH_MAX], *dir[2];
	guint32		arg_flags, cfg_flags = 0U;
	gint		ww;
	GtkWidget	*box;
	static MainInfo	min;

	/* Parse options. Might exit, if --version or root-problems occur. */
	arg_flags = opt_extract(&argc, argv);

	memset(&min, 0, sizeof min);
	min.gui = NULL;

	/* Must be run early, before anyone changes the directory. */
	fut_initialize();

	gtk_init(&argc, &argv);

	gtk_quit_add(1, quit_handler, (gpointer) &min);

	min.cfg.flags = 0;
	dpf_init_defaults(&min.cfg);
	csq_init_commands(&min);
	mnt_init_defaults(&min.cfg);
	min.cfg.shortcutbank = shc_shortcutbank_new_default();
	btn_buttoninfo_default(&min, &min.cfg.buttons);
	init_dp_menu(&min.cfg);
	init_paths(&min.cfg);
	min.cfg.style = stl_styleinfo_default();
	typ_init(&min.cfg);
	min.cfg.ctrlinfo = ctrl_new_default(&min);
	min.cfg.dir_filter = dir_filter;
	chd_initialize(&min);
	min.que = que_initialize();

	if(!(arg_flags & OPTF_NO_RC))
		cfg_flags = cfg_load_config(&min);

	min.ico = ico_initialize(&min);

	if(!usr_init())
	{
		fprintf(stderr, "%s: Couldn't initialize userinfo module\n", g_get_prgname());
		exit(EXIT_FAILURE);
	}
	if((min.cfg.mount.mode != MNT_NEVER) && (!mnt_init(&min)))
		fprintf(stderr, "%s: Couldn't initialize mount data - automount won't work\n", g_get_prgname());

	if(!(arg_flags & OPTF_NO_GTKRC))
		load_gtk_rc(&min);

	if((box = build_gui(&min)) != NULL)
	{
		min.gui->win_del_evt = gtk_signal_connect(GTK_OBJECT(min.gui->window), "delete_event", GTK_SIGNAL_FUNC(evt_main_delete), &min);
		gtk_signal_connect(GTK_OBJECT(min.gui->window), "destroy", GTK_SIGNAL_FUNC(evt_main_destroy), NULL);
		min.gui->win_sa_evt = gtk_signal_connect(GTK_OBJECT(min.gui->window), "size_allocate", GTK_SIGNAL_FUNC(evt_main_sizealloc), &min);
#if 0
		gtk_signal_connect(GTK_OBJECT(min.gui->window), "key_press_event", GTK_SIGNAL_FUNC(evt_key_press), &min);
#endif
		min.cfg.flags = 0;
		init_cmd_options(&min.cfg);		

		gtk_container_add(GTK_CONTAINER(min.gui->window), box);
		dir[0] = (argc > 1) ? argv[1] : min.cfg.dp_format[0].def_path;
		dir[1] = (argc > 2) ? argv[2] : min.cfg.dp_format[1].def_path;

		if(cfg_flags & CLDF_WINDOW_POSSIZE)
			cps_set_size(&min);
		if(cfg_flags & CLDF_WINDOW_POSSIZE)
			cps_set_pos(&min);
		gtk_widget_show(min.gui->window);
		set_icon(min.gui->window);
		gdk_window_get_size(min.gui->window->window, &ww, NULL);
		gtk_paned_set_position(GTK_PANED(min.gui->hpane), ww / 2);
		gtk_window_set_policy(GTK_WINDOW(min.gui->window), TRUE, TRUE, FALSE);

		dp_enter_dir(&min.gui->pane[0], dir[0]);
		dp_enter_dir(&min.gui->pane[1], dir[1]);
		dp_set(&min.gui->pane[0]);

		g_snprintf(rcname, sizeof rcname, "gentoo v%s by Emil Brink, <emil@obsession.se>", VERSION);
		gtk_label_set_text(GTK_LABEL(min.gui->top), rcname);

		if(cfg_flags & CLDF_NONE_FOUND)
			csq_execute(&min, "About");
		if(cfg_flags & CLDF_WINDOW_POSSIZE)
			cps_set_pos(&min);
		gtk_main();
	}
	mnt_shutdown(&min);

	return 0;
}
