/*
** 1998-09-27 -	A pretty cool dialog, that allows the selection of a command sequence
**		OR a built-in command. Should be very convenient.
** 1998-12-16 -	Now tracks and remembers the collapsed/expanded state of the two sub-trees.
** 1999-03-13 -	Adjustments for new dialog module.
** 1999-05-08 -	Slight adjustments; now keeps the command sequence as a GString.
** 1999-05-09 -	Celbrated my birthday by changing my old (sucky) auto-completion code into
**		use of glib's g_completion_XXX() API. A lot nicer, although a bit tricky
**		and (for me) non-intuitive to use. I think it works now, though.
*/

#include "gentoo.h"

#include <gdk/gdkkeysyms.h>

#include "strutil.h"
#include "dialog.h"

#include "cmdseq_dialog.h"

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

typedef struct {
	GString		*cmd;		/* Keep at top for correct initialization. */
	Dialog		*dlg;
	GtkWidget	*vbox;
	GtkWidget	*scwin;
	GtkWidget	*tree;
	GtkWidget	*entry;
	guint		evt_changed;
	GList		*builtin, *cmdseq;
	GCompletion	*completion;
	GList		*clist, *citer;
} CDlg;

static CDlg	the_cdlg = { NULL };
static gint	the_state[2] = {FALSE, FALSE};		/* Expanded/!collapsed states. */

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

/* 1998-09-27 -	Add <key> to a list, sorted. */
static void insert_key(gpointer key, gpointer data, gpointer user)
{
	GList	**list = user;

	*list = g_list_insert_sorted(*list, key, (GCompareFunc) strcmp);
}

/* 1998-09-27 -	Take a hash table with string keys, and build a linear list from it, sorting
**		the keys in lexicographically.
*/
static GList * hash_to_list(GHashTable *hash)
{
	GList	*list = NULL;

	if(hash != NULL)
		g_hash_table_foreach(hash, insert_key, &list);

	return list;
}

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

/* 1998-09-27 -	One of the tree items was just selected. */
static gint evt_item_selected(GtkWidget *wid, gpointer data)
{
	CDlg	*cdlg = (CDlg *) data;
	gchar	*ptr;

	if((ptr = (gchar *) gtk_object_get_user_data(GTK_OBJECT(wid))) != NULL)
	{
		gtk_signal_handler_block(GTK_OBJECT(cdlg->entry), cdlg->evt_changed);
		gtk_entry_set_text(GTK_ENTRY(cdlg->entry), ptr);
		gtk_widget_grab_focus(cdlg->entry);
		gtk_signal_handler_unblock(GTK_OBJECT(cdlg->entry), cdlg->evt_changed);
		g_string_assign(cdlg->cmd, ptr);
	}
	return TRUE;
}

/* 1998-12-16 -	An item was collapsed. Remember which. */
static gint evt_item_collapsed(GtkWidget *wid, gpointer data)
{
	if(data != NULL)
		*((gint *) data) = FALSE;

	return TRUE;
}

/* 1998-12-16 -	An item was expanded. Remember which. */
static gint evt_item_expanded(GtkWidget *wid, gpointer data)
{
	if(data != NULL)
		*((gint *) data) = TRUE;

	return TRUE;
}

/* 1998-09-29 -	Take a list, containing alphabetically sorted strings, and build a tree
**		with each string as an item.
*/
static GtkWidget * list_to_tree(GList *list, gpointer user)
{
	GtkWidget	*root = NULL, *item;
	GList		*iter;

	if((root = gtk_tree_new()) != NULL)
	{
		for(iter = list; iter != NULL; iter = g_list_next(iter))
		{
			if((item = gtk_tree_item_new_with_label((char *) iter->data)) != NULL)
			{
				gtk_object_set_user_data(GTK_OBJECT(item), iter->data);
				gtk_signal_connect(GTK_OBJECT(item), "select", GTK_SIGNAL_FUNC(evt_item_selected), user);
				gtk_tree_append(GTK_TREE(root), item);
				gtk_widget_show(item);
			}
		}
	}
	return root;
}

/* 1998-09-27 -	Build and return a tree item called <label>, with all of <hash> as a subtree.
** 1998-09-29 -	Now accepts a GList rather than a hash table.
** 1998-12-16 -	If the (new) arg <state> is non-NULL, it is used as a collapse/expand state tracker,
**		and the required signal handlers are connected.
** 1998-12-21 -	No longer builds stuff if <list> is NULL. Handy when there are no user-defined command.
*/
static GtkWidget * list_to_labeled_tree(GList *list, GtkWidget *parent, char *label, gpointer user, gint *state)
{
	GtkWidget	*item = NULL, *tree;

	if((list != NULL) && (item = gtk_tree_item_new_with_label(label)) != NULL)
	{
		if(state != NULL)
		{
			gtk_signal_connect(GTK_OBJECT(item), "collapse", GTK_SIGNAL_FUNC(evt_item_collapsed), state);
			gtk_signal_connect(GTK_OBJECT(item), "expand",   GTK_SIGNAL_FUNC(evt_item_expanded),  state);
		}
		gtk_tree_append(GTK_TREE(parent), item);
		if((tree = list_to_tree(list, user)) != NULL)
		{
			gtk_tree_item_set_subtree(GTK_TREE_ITEM(item), tree);
			if(state != NULL)
				*state ? gtk_tree_item_expand(GTK_TREE_ITEM(item)) : gtk_tree_item_collapse(GTK_TREE_ITEM(item));
			else
				gtk_tree_item_expand(GTK_TREE_ITEM(item));
		}
	}
	return item;
}

/* 1998-09-27 -	Build a tree showing all built-in and user-defined commands in two subtrees.
** 1998-09-28 -	Added the <seq> argument, which if non-NULL overrides min->cfg.
** 1998-09-29 -	Rewritten. We now keep the lists around, so they're already built at this point.
*/
static GtkWidget * build_tree(MainInfo *min, CDlg *cdlg)
{
	GtkWidget	*tree, *ctree;

	if((tree = gtk_tree_new()) != NULL)
	{
		gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
		if((ctree = list_to_labeled_tree(cdlg->builtin, tree, "Built-Ins", cdlg, &the_state[0])) != NULL)
			gtk_widget_show(ctree);
		if((ctree = list_to_labeled_tree(cdlg->cmdseq, tree, "User Defined", cdlg, &the_state[1])) != NULL)
			gtk_widget_show(ctree);
	}
	return tree;
}

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

/* 1999-05-09 -	Construct a GCompletion capable of doing completion on command name
**		strings.
*/
static void init_completion(CDlg *cdlg)
{
	if((cdlg->completion = g_completion_new(NULL)) != NULL)
	{
		g_completion_add_items(cdlg->completion, cdlg->builtin);
		g_completion_add_items(cdlg->completion, cdlg->cmdseq);
		cdlg->clist = cdlg->citer = NULL;
	}
}

/* 1999-05-09 -	Free the completion as the dialog closes. */
static void free_completion(CDlg *cdlg)
{
	if(cdlg->completion != NULL)
	{
		g_completion_free(cdlg->completion);
		cdlg->completion = NULL;
		cdlg->clist = cdlg->citer = NULL;
	}
}

/* 1999-05-09 -	This gets called as the user edits the entry. Zzap any buffered completion. */
static void evt_entry_changed(GtkWidget *wid, gpointer user)
{
	CDlg	*cdlg = user;

	if(cdlg->clist != NULL)
		free_completion(cdlg);
	g_string_assign(cdlg->cmd, gtk_entry_get_text(GTK_ENTRY(wid)));
}

/* 1999-09-05 -	Set the command text, without losing the completion state. */
static void set_command(CDlg *cdlg, const gchar *text)
{
	if(cdlg != NULL)
	{
		gtk_signal_handler_block(GTK_OBJECT(cdlg->entry), cdlg->evt_changed);
		gtk_entry_set_text(GTK_ENTRY(cdlg->entry), text);
		gtk_signal_handler_unblock(GTK_OBJECT(cdlg->entry), cdlg->evt_changed);
		g_string_assign(cdlg->cmd, text);
	}
}

/* 1999-05-09 -	Happy birthday to me! :) Um, this is an attempt to move to TAB-triggered
**		command name completion, rather than automatic as-you-type one. Using glib.
*/
static gint evt_entry_key_press(GtkWidget *wid, GdkEventKey *evt, gpointer user)
{
	gboolean	back;
	CDlg		*cdlg = user;

	if((evt->keyval == GDK_Tab) || (evt->keyval == GDK_ISO_Left_Tab))
	{
		back = evt->keyval == GDK_ISO_Left_Tab;
		if(cdlg->completion == NULL)
			init_completion(cdlg);
		if(cdlg->completion != NULL)
		{
			if(cdlg->clist == NULL)
			{
				gchar	*text, *prefix = NULL;

				text = gtk_entry_get_text(GTK_ENTRY(wid));
				if((cdlg->clist = g_completion_complete(cdlg->completion, text, &prefix)) != NULL)
				{
					set_command(cdlg, prefix);
					g_free(prefix);
				}
				if(back)
					cdlg->citer = g_list_last(cdlg->clist);
				else
					cdlg->citer = cdlg->clist;
			}
			else
			{
				set_command(cdlg, cdlg->citer->data);
				if(back)
				{
					cdlg->citer = g_list_previous(cdlg->citer);
					if(cdlg->citer == NULL)
						cdlg->citer = g_list_last(cdlg->clist);
				}
				else
				{
					cdlg->citer = g_list_next(cdlg->citer);
					if(cdlg->citer == NULL)
						cdlg->citer = cdlg->clist;
				}
			}
			gtk_signal_emit_stop_by_name(GTK_OBJECT(wid), "key_press_event");
			return TRUE;
		}
	}
	return FALSE;
}

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

/* 1998-09-27 -	Open up a dialog where the user can select (or type) a command name.
**		The <cmdseq> argument should contain the current user-defined command sequences.
**		If it is NULL, the possibly-not-so-current ones in min->cfg.commands are used.
** 1999-03-29 -	Simplified semantics; now runs synchronously, and simply returns command name (or NULL).
*/
const gchar * csq_dialog(MainInfo *min, GHashTable *cmdseq)
{
	CDlg		*cdlg = &the_cdlg;
	const gchar	*ret = NULL;

	if(cmdseq == NULL)			/* No alternative command sequence hash? */
		cmdseq = min->cfg.commands.cmdseq;

	cdlg->builtin = hash_to_list(min->cfg.commands.builtin);
	cdlg->cmdseq = hash_to_list(cmdseq);
	if(cdlg->cmd == NULL)
		cdlg->cmd = g_string_new(NULL);
	else
		g_string_truncate(cdlg->cmd, 0);

	cdlg->completion = NULL;
	cdlg->clist = cdlg->citer = NULL;

	cdlg->vbox   = gtk_vbox_new(FALSE, 0);
	cdlg->scwin  = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(cdlg->scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	if((cdlg->tree = build_tree(min, cdlg)) != NULL)
	{
		gtk_tree_set_view_mode(GTK_TREE(cdlg->tree), GTK_TREE_VIEW_ITEM);
		gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(cdlg->scwin), cdlg->tree);
		gtk_widget_show(cdlg->tree);
		gtk_box_pack_start(GTK_BOX(cdlg->vbox), cdlg->scwin, TRUE, TRUE, 0);
		gtk_widget_show(cdlg->scwin);
		cdlg->entry = gtk_entry_new();
		gtk_signal_connect(GTK_OBJECT(cdlg->entry), "key_press_event", GTK_SIGNAL_FUNC(evt_entry_key_press), cdlg);
		cdlg->evt_changed = gtk_signal_connect(GTK_OBJECT(cdlg->entry), "changed", GTK_SIGNAL_FUNC(evt_entry_changed), cdlg);
		gtk_box_pack_start(GTK_BOX(cdlg->vbox), cdlg->entry, FALSE, FALSE, 0);
		gtk_widget_show(cdlg->entry);
		cdlg->dlg = dlg_dialog(cdlg->vbox, "Pick Command", "OK|Cancel", NULL, NULL);
		dlg_dialog_set_size(cdlg->dlg, -1, 384);
		gtk_widget_grab_focus(cdlg->entry);
		if(dlg_dialog_wait(cdlg->dlg) == DLG_POSITIVE)
			ret = cdlg->cmd->str;
		g_list_free(cdlg->builtin);
		g_list_free(cdlg->cmdseq);
		free_completion(cdlg);
	}
	return ret;
}
