/*
** 1999-03-16 -	A module to deal with various forms of user controls. Currently, this
**		will simply implement global keyboard shortcuts. In future, this might
**		be the place to add configuration of mouse buttons, and stuff.
** 1999-05-08 -	Minor changes due to the fact that command sequences are now best stored
**		as GStrings.
*/

#include "gentoo.h"

#include "strutil.h"

#include "controls.h"

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

struct _CtrlInfo {
	MainInfo	*min;		/* So incredibly handy to have around. */
	GHashTable	*keys;		/* Contains all key-to-cmdseq bindings. */
	GSList		*keys_inst;	/* List of KbdContexts into which we're currently installed. */
};

struct _CtrlKey {				/* A simple key-to-cmdseq mapping. */
	gchar	keyname[KEY_NAME_MAX];
	GString	*cmdseq;
};

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

static CtrlKey *	key_new(const gchar *keyname, const gchar *cmdseq);
static void		key_destroy(CtrlKey *key);

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

/* 1999-03-16 -	Create a new control info, the main representation of this module's work. */
CtrlInfo * ctrl_new(MainInfo *min)
{
	CtrlInfo	*ctrl;

	ctrl = g_malloc(sizeof *ctrl);
	ctrl->min = min;
	ctrl->keys = g_hash_table_new(g_str_hash, g_str_equal);
	ctrl->keys_inst = NULL;

	return ctrl;
}

/* 1999-03-16 -	Return a control info with the default controls already configured into it.
**		These are simply the few keys that have had hardcoded functions since more
**		or less the beginning of gentoo. It's nice to see them reborn like this. :)
*/
CtrlInfo * ctrl_new_default(MainInfo *min)
{
	CtrlInfo	*ctrl;

	ctrl = ctrl_new(min);

	ctrl_key_add(ctrl, "BackSpace",	"DirParent");
	ctrl_key_add(ctrl, "Delete",	"Delete");
	ctrl_key_add(ctrl, "F2",	"Rename");
	ctrl_key_add(ctrl, "F5",	"DirRescan");
	ctrl_key_add(ctrl, "h",		"DpHide");
	ctrl_key_add(ctrl, "r",		"DpRecenter");
	ctrl_key_add(ctrl, "space",	"ActivateOther");

	return ctrl;
}

/* 1999-03-16 -	Remove a key; this is a g_hash_table_foreach_remove() callback. */
static gboolean key_remove(gpointer key, gpointer value, gpointer user)
{
	key_destroy(value);
	return TRUE;
} 

/* 1999-03-16 -	Copy a key. A callback for ctrl_copy() below. */
static void copy_key(gpointer key, gpointer value, gpointer user)
{
	if(((CtrlKey *) value)->cmdseq != NULL)
		ctrl_key_add(user, ((CtrlKey *) value)->keyname, ((CtrlKey *) value)->cmdseq->str);
	else
		ctrl_key_add(user, ((CtrlKey *) value)->keyname, NULL);
}

/* 1999-03-16 -	Copy the entire control info structure. Very useful for config. Note that
**		the copy will NOT retain the installation status of the original; in fact,
**		it will not be installed anywhere (which is generally what you (should) want).
*/
CtrlInfo * ctrl_copy(CtrlInfo *ctrl)
{
	CtrlInfo	*copy = NULL;

	if(ctrl != NULL)
	{
		copy = ctrl_new(ctrl->min);
		g_hash_table_foreach(ctrl->keys, copy_key, copy);
	}
	return copy;
}

/* 1999-03-16 -	Destroy a control info. */
void ctrl_destroy(CtrlInfo *ctrl)
{
	if(ctrl != NULL)
	{
		ctrl_keys_uninstall_all(ctrl);
		g_hash_table_foreach_remove(ctrl->keys, key_remove, NULL);
		g_hash_table_destroy(ctrl->keys);
		g_free(ctrl);
	}
}

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

/* 1999-03-16 -	Create a new key control with the given mapping. */
static CtrlKey * key_new(const gchar *keyname, const gchar *cmdseq)
{
	CtrlKey	*key;

	key = g_malloc(sizeof *key);
	str_strncpy(key->keyname, keyname, sizeof key->keyname);
	if(cmdseq != NULL)
		key->cmdseq = g_string_new(cmdseq);
	else
		key->cmdseq = NULL;

	return key;
}

/* 1999-03-16 -	Destroy a key mapping. */
static void key_destroy(CtrlKey *key)
{
	if(key != NULL)
	{
		if(key->cmdseq != NULL)
			g_string_free(key->cmdseq, TRUE);
		g_free(key);
	}
}

/* 1999-03-16 -	Add a keyboard mapping to the given <ctrl>. Note that if the ctrl has
**		already been installed, adding keys to it won't activate them until it
**		is reinstalled.
*/
CtrlKey * ctrl_key_add(CtrlInfo *ctrl, const gchar *keyname, const gchar *cmdseq)
{
	CtrlKey	*key = NULL;

	if((ctrl != NULL) && (keyname != NULL))
	{
		key = key_new(keyname, cmdseq);
		ctrl_key_remove_by_name(ctrl, key->keyname);
		g_hash_table_insert(ctrl->keys, key->keyname, key);
	}
	return key;
}

/* 1999-03-17 -	Add a new key->command mapping, with the rather peculiar property that
**		the key name is in fact illegal (it is not an existing key), and the
**		command part is empty. The key name will, however, be unique among all
**		existing maps. Useful when adding keys interactively.
*/
CtrlKey * ctrl_key_add_unique(CtrlInfo *ctrl)
{
	gchar	buf[KEY_NAME_MAX], *name = NULL;
	gint	i;

	for(i = 0; (name == NULL) && (i < 1000); i++)	/* Um, not quite unique, since we can give up. */
	{
		if(i == 0)
			g_snprintf(buf, sizeof buf, "none");
		else
			g_snprintf(buf, sizeof buf, "none-%d", i + 1);
		if(g_hash_table_lookup(ctrl->keys, buf) == NULL)
			name = buf;
	}
	return ctrl_key_add(ctrl, name, NULL);
}

/* 1999-03-16 -	Remove the given key mapping, and also destroy it. */
void ctrl_key_remove(CtrlInfo *ctrl, CtrlKey *key)
{
	if((ctrl != NULL) && (key != NULL))
		ctrl_key_remove_by_name(ctrl, key->keyname);
}

/* 1999-03-16 -	Remove a named key mapping, and destroy it. */
void ctrl_key_remove_by_name(CtrlInfo *ctrl, const gchar *keyname)
{
	if((ctrl != NULL) && (keyname != NULL))
	{
		CtrlKey	*key;

		if((key = g_hash_table_lookup(ctrl->keys, keyname)) != NULL)
		{
			g_hash_table_remove(ctrl->keys, key->keyname);
			key_destroy(key);
		}
	}
}

/* 1999-03-17 -	Remove all key mappings from <ctrl>. It is a very good idea to first
**		uninstall it from (all) keyboard contexts.
*/
void ctrl_key_remove_all(CtrlInfo *ctrl)
{
	if(ctrl != NULL)
		g_hash_table_foreach_remove(ctrl->keys, key_remove, NULL);
}

/* 1999-03-16 -	Return the key part of given key->command mapping. */
const gchar * ctrl_key_get_keyname(CtrlKey *key)
{
	if(key != NULL)
		return key->keyname;
	return NULL;
}

/* 1999-03-16 -	Return the command part of a key-to-command mapping. */
const gchar * ctrl_key_get_cmdseq(CtrlKey *key)
{
	if((key != NULL) && (key->cmdseq != NULL))
		return key->cmdseq->str;
	return NULL;
}

/* 1999-03-16 -	Change the key part of the given mapping. Changes it in the
**		<ctrl> as well. Is basically equivalent to a remove followed
**		by an add, but slightly more efficient.
*/
void ctrl_key_set_keyname(CtrlInfo *ctrl, CtrlKey *key, const gchar *keyname)
{
	if((ctrl != NULL) && (key != NULL) && (keyname != NULL))
	{
		if(g_hash_table_lookup(ctrl->keys, key->keyname) == key)
		{
			g_hash_table_remove(ctrl->keys, key->keyname);
			str_strncpy(key->keyname, keyname, sizeof key->keyname);
			g_hash_table_insert(ctrl->keys, key->keyname, key);
		}
	}
}

/* 1999-03-16 -	Change command sequence part of given mapping. Equivalent to,
**		but far more efficient than, a remove+add pair.
*/
void ctrl_key_set_cmdseq(CtrlInfo *ctrl, CtrlKey *key, const gchar *cmdseq)
{
	if((ctrl != NULL) && (key != NULL))
	{
		if(g_hash_table_lookup(ctrl->keys, key->keyname) == key)
		{
			if(cmdseq != NULL)
			{
				if(key->cmdseq != NULL)
					g_string_assign(key->cmdseq, cmdseq);
				else
					key->cmdseq = g_string_new(cmdseq);
			}
			else
			{
				if(key->cmdseq != NULL)
					g_string_free(key->cmdseq, TRUE);
				key->cmdseq = NULL;
			}
		}
	}
}

/* 1999-05-08 -	Return TRUE if given <key> already has a command sequence equal to <cmdseq>.
**		If not, FALSE is returned.
*/
gboolean ctrl_key_has_cmdseq(CtrlInfo *ctrl, CtrlKey *key, const gchar *cmdseq)
{
	if((ctrl != NULL) && (key != NULL) && (cmdseq != NULL))
	{
		if(key->cmdseq != NULL)
			return strcmp(key->cmdseq->str, cmdseq) ? FALSE : TRUE;
		return FALSE;
	}
	return FALSE;
}

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

/* 1999-03-16 -	Just a simple hash callback to install a CtrlKey into a keyboard context. */
static void key_install(gpointer key, gpointer value, gpointer user)
{
	if(((CtrlKey *) value)->cmdseq != NULL)
		kbd_context_entry_add((KbdContext *) user, ((CtrlKey *) value)->keyname, KET_CMDSEQ, ((CtrlKey *) value)->cmdseq->str);
}

/* 1999-03-16 -	Install all <ctrl>'s key mappings as command-type keyboard commands in <ctx>.
**		Note that the set of which keyboard contexts a CtrlInfo has been installed in
**		is a retained property in the CtrlInfo.
*/
void ctrl_keys_install(CtrlInfo *ctrl, KbdContext *ctx)
{
	if((ctrl != NULL) && (ctx != NULL))
	{
		g_hash_table_foreach(ctrl->keys, key_install, ctx);
		ctrl->keys_inst = g_slist_prepend(ctrl->keys_inst, ctx);
	}
}

/* 1999-03-16 -	Callback for ctrl_keys_uninstall() below. */
static void key_uninstall(gpointer key, gpointer value, gpointer user)
{
	kbd_context_entry_remove(user, ((CtrlKey *) value)->keyname);
}

/* 1999-03-16 -	Uninstall all keys defined by <ctrl> from context <ctx>. If, in fact, the
**		ctrl has never been installed in the context, nothing happens. Does not
**		(ever) affect which mappings are contained in the ctrl.
*/
void ctrl_keys_uninstall(CtrlInfo *ctrl, KbdContext *ctx)
{
	if((ctrl != NULL) && (ctx != NULL))
	{
		if(g_slist_find(ctrl->keys_inst, ctx) != NULL)
		{
			g_hash_table_foreach(ctrl->keys, key_uninstall, ctx);
			ctrl->keys_inst = g_slist_remove(ctrl->keys_inst, ctx);
		}
	}
}

/* 1999-03-17 -	A foreach callback for ctrl_keys_uninstall_all() below. Does not just
**		call ctrl_keys_uninstall(), since that modofies the list which is a bad
**		idea when iterating it...
*/
static void keys_uninstall(gpointer data, gpointer user)
{
	g_hash_table_foreach(((CtrlInfo *) user)->keys, key_uninstall, data);
}

/* 1999-03-17 -	Uninstall given ctrlinfo from all of its keyboard contexts. Does not alter
**		the actual collection of key mappings in the ctrlinfo.
*/
void ctrl_keys_uninstall_all(CtrlInfo *ctrl)
{
	if(ctrl != NULL)
	{
		g_slist_foreach(ctrl->keys_inst, keys_uninstall, ctrl);
		g_slist_free(ctrl->keys_inst);
		ctrl->keys_inst = NULL;
	}
}

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

static gint cmp_string(gconstpointer a, gconstpointer b)
{
	return strcmp((char *) a, (char *) b);
}

static void key_insert(gpointer key, gpointer value, gpointer user)
{
	GSList	**list = user;

	*list = g_slist_insert_sorted(*list, value, cmp_string);
}

/* 1999-03-16 -	Return a list of all <ctrl>'s key mappings, sorted on the key names. */
GSList * ctrl_keys_get_list(CtrlInfo *ctrl)
{
	GSList	*list = NULL;

	if(ctrl != NULL)
		g_hash_table_foreach(ctrl->keys, key_insert, &list);

	return list;
}
