/*
** 1998-09-25 -	This module contains code formerly in the 'commands.c' module. It deals
**		with the parsing and transformation of command arguments in string format
**		(e.g. "ls -l {fp}") to argv-style argument vectors. Very useful in command
**		execution code. All code was modified for the new command format, of course.
** 1999-02-25 -	Finished (?) some major rewrites. This should be a lot more flexible now, and
**		perhaps even a couple of small measures faster. Now does half-decent quoting.
** 1999-03-07 -	Changes for the new selection representation.
** 1999-05-08 -	Simplified main cpr_parse() interface slightly; now just takes a const gchar
**		string rather than a GString.
*/

#include "gentoo.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "dialog.h"
#include "dirpane.h"
#include "strutil.h"
#include "userinfo.h"
#include "cmdparse.h"

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

#define	INP_PATTERN     "\1" "INP%02d" "\2"
#define	INP_PAT_LEN	(7)			/* Guaranteed length produced by above. */

#define	DLG_MAX_INPUT	(16)			/* Maximum amount of individual inputs {I}s for a single command. */

typedef enum { CITP_STRING, CITP_MENU, CITP_COMBO, CITP_LABEL } CIType;

typedef struct {
	GtkWidget	*entry;
} CIString;

typedef struct {
	GList		*items;			/* List of item texts (dynamic). */
	int		current;		/* Index of currently selected item. */
} CIMenu;

typedef struct {
	GtkWidget	*combo;
} CICombo;

typedef struct {
	GtkWidget	*label;			/* This is either a GtkLabel or a HSeparator (from {It:-}). */
} CILabel;

typedef struct {
	CIType	type;
	union {
	CIString	string;
	CIMenu		menu;
	CICombo		combo;
	CILabel		label;
	} inp;
} CmdInp;

typedef struct {
	Dialog		*dlg;			/* The dialog window. */
	CmdInp		input[DLG_MAX_INPUT];
	GtkWidget	*body;			/* This holds the entire GUI. */
	GtkWidget	*table;			/* Input widgets and labels are added here. */
	gint		row;			/* Current row in table. */
	gchar		*wintitle;		/* Title to set on window (example: {Iw:"gentoo"}). */
	gint		str_count;		/* Counts number of string fields (for focus). */
	guint		str_focused;		/* Gets set as the string is first focused. */
} CmdDlg;

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

/* 1998-09-25 -	Parse a code beginning with 'f' or 'F'.
** 1998-10-26 -	Added the 'd' flag to get at the destination pane, rather than the source.
** 1999-02-24 -	Rewritten for new argument handling style. Quotes filenames now.
*/
static void parse_code_file(MainInfo *min, GString *cstr, char *code)
{
	DirPane		*dp = min->gui->cur_dir;
	const gchar	*name;
	gboolean	unsel = (strchr(code + 1, 'u') != NULL),
			path  = (strchr(code + 1, 'p') != NULL),
			dest  = (strchr(code + 1, 'd') != NULL),
			cnt = 0;
	GSList		*slist, *iter;

	if(dest)		/* User requested the destination pane? */
		dp = dp_mirror(min, dp);

	if((slist = dp_get_selection(dp)) == NULL)
		return;

	for(iter = slist; iter != NULL; iter = g_slist_next(iter))
	{
		name = dp_name_quoted(dp, DP_SEL_INDEX(dp, iter), path);
		g_string_append(cstr, name);
		g_string_append_c(cstr, ' ');
		cnt++;
		if(unsel)
			dp_unselect(dp, DP_SEL_INDEX(dp, iter));
		if(code[0] == 'f')		/* Only first? */
			break;
	}
	if(cnt > 0)
		g_string_truncate(cstr, cstr->len - 1);		/* Loose last space. */

	dp_free_selection(slist);
}

/* 1998-09-25 -	Parse a code beginning with 'P', resulting in a path.
** 1999-02-24 -	Touched up to comply with the new argument handling style.
*/
static void parse_code_path(MainInfo *min, GString *cstr, char *code)
{
	char	*path = NULL;
	DirPane	*src = min->gui->cur_dir, *dst = dp_mirror(min, src);

	if(code[1] == '\0')			/* Nothing but the 'P'? */
		path = src->dir.path;
	else
	{
		if(code[1] == 's')
			path = src->dir.path;
		else if(code[1] == 'd')
			path = dst->dir.path;
		else if(code[1] == 'l')
			path = min->gui->pane[0].dir.path;
		else if(code[1] == 'r')
			path = min->gui->pane[1].dir.path;
		else if(code[1] == 'h')
			path = getenv("HOME");
		else
			fprintf(stderr, "**CMDPARSE: Illegal path code '%s'\n", code + 1);
	}
	if(path != NULL)
		g_string_append(cstr, path);
}

/* 1998-09-25 -	Parse an environment variable lookup-code, {$...}.
** 1999-02-24 -	Touched up for the new argument handling style.
*/
static void parse_code_env(MainInfo *min, GString *cstr, char *code)
{
	char	*val;

	if((val = getenv(code + 1)) != NULL)
		g_string_append(cstr, val);
}

/* 1998-09-25 -	Parse a PID-lookup code. Currently that's just {#}, and gives the main
**		program's PID.
** 1999-02-24 -	Upgraded for new style. Not very much to change. :)
*/
static void parse_code_pid(MainInfo *min, GString *cstr, char *code)
{
	g_string_sprintfa(cstr, "%d", (int) getpid());
}

/* 1998-12-23 -	Parse a {~...} code, used to look after user's home directories.
** 1999-02-24 -	Upgraded argument handling.
*/
static void parse_code_home(MainInfo *min, GString *cstr, char *code)
{
	const char	*uhome;

	if((uhome = usr_lookup_uhome(code + 1)) != NULL)
		g_string_append(cstr, uhome);
}

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

/* 1998-09-25 -	Initialize the command input dialog, by simply constructing the relevant
**		GTK+ widgets and connecting them together. GIMP-ified in this reimplementation,
**		to keep it simpler.
*/
static void init_input_dialog(CmdDlg *dlg)
{
	dlg->body = gtk_vbox_new(FALSE, 0);
	dlg->table = gtk_table_new(DLG_MAX_INPUT, 2, FALSE);
	gtk_table_set_col_spacing(GTK_TABLE(dlg->table), 0, 5);
	gtk_box_pack_start(GTK_BOX(dlg->body), dlg->table, TRUE, TRUE, 0);
	gtk_widget_show(dlg->table);
}

/* 1999-02-27 -	A little helper routine to pack widgets into the table forming the command input
**		dialog. <pos> can be either 0 (left column), 1 (right column) or 2 (both columns).
**		The <padding> is vertical, and best left at 0 for non-separator widgets. Also shows
**		the widget.
*/
static void pack_input_widget(CmdDlg *dlg, GtkWidget *wid, gint pos, gint padding)
{
	if(dlg != NULL && wid != NULL)
	{
		gint	left = 0, right = 2;		/* Assume <pos> will be 2. */

		if(pos == 0)
			right--;
		else if(pos == 1)
			left++;

		gtk_table_attach(GTK_TABLE(dlg->table), wid, left, right, dlg->row, dlg->row + 1,
				 GTK_FILL|GTK_EXPAND, 0, 0, padding);
		gtk_widget_show(wid);
	}
}

/* 1999-02-23 -	Since we can't focus the widget until it actually is visible, and the root
**		gtk_widget_show() call is done inside the dialog module, this gross event
**		hack is used instead. Note how a special flag is used to avoid doing this
**		more than once during the lifetime of the dialog.
*/
static gint evt_input_string_visible(GtkWidget *wid, gpointer user)
{
	guint	*flag = (guint *) user;

	if(*flag == 0)
	{
		gtk_widget_grab_focus(wid);
		*flag = 1;
	}
	return TRUE;
}

/* 1998-09-25 -	Build a string widget for an input dialog. Cut-down version.
** 1999-02-27 -	Modified for the new packing scheme.
*/
static gint build_input_string(CmdDlg *dlg, char *def, CmdInp *inp)
{
	GtkWidget	*ent;
	gchar		*end;

	inp->type = CITP_STRING;
	ent = gtk_entry_new();
	if(*def == '=' && def[1] == '"')
	{
		if((end = strchr(def + 2, '"')) != NULL)
		{
			*end = '\0';
			gtk_entry_set_text(GTK_ENTRY(ent), def + 2);
			gtk_entry_select_region(GTK_ENTRY(ent), 0, -1);
		}
	}
	pack_input_widget(dlg, inp->inp.string.entry = ent, 1, 0);

	return 1;
}

/* 1998-10-07 -	User selected an option in an option menu widget (created by {Im...}).
**		Store the index in the menu.
*/
static gint evt_menu_activated(GtkWidget *wid, gpointer data)
{
	CmdInp	*inp;

	if((inp = gtk_object_get_user_data(GTK_OBJECT(wid))) != NULL)
		inp->inp.menu.current = (int) data;

	return TRUE;
}

/* 1998-10-07 -	Build an input menu widget, as defined by the {Im...} code.
** 1999-02-27 -	Modified for new packing scheme.
*/
static gint build_input_menu(CmdDlg *dlg, char *def, CmdInp *inp)
{
	GtkWidget	*obtn, *menu, *item;
	char		*itext, *colon;
	int		index = 0;

	if(*def != '=')
		return 0;
	if((obtn = gtk_option_menu_new()) != NULL)
	{
		if((menu = gtk_menu_new()) != NULL)
		{
			inp->type = CITP_MENU;
			inp->inp.menu.items = NULL;
			inp->inp.menu.current = 0;
			while((def = str_scan_string(def, &itext)) != NULL)
			{
				if((colon = strchr(itext, ':')) != NULL)
				{
					char	tmp[2048];

					str_strncpy(tmp, itext, sizeof tmp);
					tmp[colon - itext] = '\0';
					item = gtk_menu_item_new_with_label(tmp);
				}
				else
					item = gtk_menu_item_new_with_label(itext);

				gtk_object_set_user_data(GTK_OBJECT(item), (gpointer) inp);
				inp->inp.menu.items = g_list_append(inp->inp.menu.items, (gpointer) itext);
				gtk_signal_connect(GTK_OBJECT(item), "activate", GTK_SIGNAL_FUNC(evt_menu_activated), (gpointer) index++);
				gtk_menu_append(GTK_MENU(menu), item);
				gtk_widget_show(item);
			}
			gtk_option_menu_set_menu(GTK_OPTION_MENU(obtn), menu);
			pack_input_widget(dlg, obtn, 1, 0);
			return 1;
		}
		gtk_widget_destroy(obtn);
	}
	return 0;
}

/* 1998-11-29 -	Build an input combo box widget, defined by a {Ic...} code.
** 1999-02-27 -	New packing scheme fixes.
*/
static gint build_input_combo(CmdDlg *dlg, char *def, CmdInp *inp)
{
	GtkWidget	*combo;
	char		*itext;
	GList		*item = NULL, *iter;

	if(*def != '=')
		return 0;

	if((combo = gtk_combo_new()) != NULL)
	{
		/* Configure the combo to work the way I like it to. */
		gtk_combo_set_value_in_list(GTK_COMBO(combo), FALSE, TRUE);
		gtk_combo_set_use_arrows_always(GTK_COMBO(combo), TRUE);
		while((def = str_scan_string(def, &itext)) != NULL)
			item = g_list_append(item, (gpointer) itext);
		if(item != NULL)
		{
			inp->type = CITP_COMBO;
			gtk_combo_set_popdown_strings(GTK_COMBO(combo), item);
			for(iter = item; iter != NULL; iter = g_list_next(iter))
				free(iter->data);
			g_list_free(item);
			pack_input_widget(dlg, inp->inp.combo.combo = combo, 1, 0);
			return 1;
		}
		gtk_widget_destroy(combo);
	}
	return 0;
}

/* 1999-02-27 -	Build a label for the input window. This replaces the old single label, which was
**		only *set* by {It...}, not created. Now {It...} creates and packs another label,
**		or even a separator (if the code is {It:-}).
*/
static gint build_input_label(CmdDlg *dlg, char *def, CmdInp *inp)
{
	if(def != NULL)
	{
		if(strcmp(def, "-") == 0)	/* Is the label a single hyphen? */
		{
			inp->type = CITP_LABEL;
			pack_input_widget(dlg, inp->inp.label.label = gtk_hseparator_new(), 2, 5);
		}
		else
		{
			if(*def == '-')		/* Skip first hyphen. To get a single visible one, use {It:"--"}. */
				def++;
			inp->type = CITP_LABEL;
			pack_input_widget(dlg, inp->inp.label.label = gtk_label_new(def), 2, 0);
		}
	}
	return def != NULL;
}

/* 1998-09-25 -	Parse a code dealing with an input dialog {I...}. Tough stuff.
** 1999-02-24 -	Updated to use new argument storage/handling format.
*/
static void parse_code_input(MainInfo *min, GString *cstr, char *code, CmdDlg *dlg)
{
	char		*ltxt = NULL, *end, *def = code + 1;
	gint		succ = 0;
	GtkWidget	*label;

	if(dlg->body == NULL)
		init_input_dialog(dlg);

	if(strlen(code) >= 5)		/* Enough room for a (possibly empty) label? */
	{
		if(code[2] == ':' && code[3] == '"')	/* Colon followed by quote? */
		{
			ltxt = code + 4;
			if((end = strchr(ltxt, '"')) != NULL)
			{
				*end = '\0';
				def = end + 1;
			}
			else
				ltxt = NULL;
		}
	}

	switch(code[1])
	{
		case 's':
			succ = build_input_string(dlg, def, &dlg->input[dlg->row]);
			if(dlg->str_count++ == 0)
				gtk_signal_connect(GTK_OBJECT(dlg->input[dlg->row].inp.string.entry), "realize", GTK_SIGNAL_FUNC(evt_input_string_visible), &dlg->str_focused);
			break;
		case 'm':
			succ = build_input_menu(dlg, def, &dlg->input[dlg->row]);
			break;
		case 'c':
			succ = build_input_combo(dlg, def, &dlg->input[dlg->row]);
			break;
		case 't':
			succ = build_input_label(dlg, ltxt, &dlg->input[dlg->row]);
			break;
		case 'w':
			if(dlg->wintitle != NULL)
				g_free(dlg->wintitle);
			dlg->wintitle = g_strdup(ltxt);
			break;
	}

	if(succ)	/* Input widget built successfully? */
	{
		/* Don't do standard label and INPxx-field for label-type fields. */
		if(dlg->input[dlg->row].type != CITP_LABEL)
		{
			label = gtk_label_new(ltxt ? ltxt : "Input");
			gtk_table_attach(GTK_TABLE(dlg->table), label, 0, 1, dlg->row, dlg->row + 1, GTK_FILL,0,0,0);
			gtk_widget_show(label);
			g_string_sprintfa(cstr, INP_PATTERN, dlg->row++);
		}
		dlg->row++;
	}
}

/* 1998-09-25 -	Get a pointer to a string representation of row <row> in dialog <dlg>. */
static char * eval_input(CmdDlg *dlg, int row)
{
	GList	*data;

	if(row >= 0 && row < dlg->row)
	{
		switch(dlg->input[row].type)
		{
			case CITP_STRING:
				return gtk_entry_get_text(GTK_ENTRY(dlg->input[row].inp.string.entry));
			case CITP_MENU:
				if((data = g_list_nth(dlg->input[row].inp.menu.items, dlg->input[row].inp.menu.current)) != NULL)
				{
					char	*colon;

					if((colon = strchr((char *) data->data, ':')) != NULL)
						return colon + 1;
					return (char *) data->data;
				}
				return NULL;
			case CITP_COMBO:
				return gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(dlg->input[row].inp.combo.combo)->entry));
			case CITP_LABEL:
				;		/* Just to keep the compiler happy. Labels don't generate INPxx's. */
		}
	}
	return NULL;
}

/* 1999-02-24 -	Interpolate any INP-fields into <cstr>, by grabbing data from <dlg>.
**		For simplicity, done in two steps with an intermediate scratch string.
*/
static void interpolate_input(GString *cstr, CmdDlg *dlg)
{
	gchar	*ptr;
	gint	row;
	GString	*scratch;

	scratch = g_string_new(NULL);
	for(ptr = cstr->str; *ptr;)
	{
		if(sscanf(ptr, INP_PATTERN, &row) == 1)
		{
			g_string_append(scratch, eval_input(dlg, row));
			ptr += INP_PAT_LEN;
		}
		else
			g_string_append_c(scratch, *ptr++);
	}
	g_string_assign(cstr, scratch->str);
	g_string_free(scratch, TRUE);
}

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

/* 1998-10-07 -	Free a string from a menu item list. */
static void free_menu(gpointer a, gpointer b)
{
	free(a);
}

/* 1998-10-07 -	Destroy any extra data kept by input fields. */
static void destroy_input(CmdDlg *dlg)
{
	guint	i;

	for(i = 0; i < sizeof dlg->input / sizeof dlg->input[0]; i++)
	{
		switch(dlg->input[i].type)
		{
			case CITP_STRING:
				break;
			case CITP_MENU:
				g_list_foreach(dlg->input[i].inp.menu.items, free_menu, NULL);
				g_list_free(dlg->input[i].inp.menu.items);
				break;
			case CITP_COMBO:
				break;
			case CITP_LABEL:		/* Just to keep compiler happy. */
				break;
		}
	}
	if(dlg->wintitle != NULL)
		g_free(dlg->wintitle);
}

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

/* 1998-09-25 -	Parse a word that contains 1 or more special "brace-codes". The codes need
**		to be replaced by a value, or an input-dialog place holder.
*/
static void parse_code(MainInfo *min, GString *cstr, char *code, CmdDlg *dlg)
{
	if(code == NULL || cstr == NULL || dlg == NULL)
		return;

	while(code && *code)
	{
		if(*code == '{')		/* Code here? */
		{
			char	*end;

			code++;
			if((end = strchr(code, '}')) != NULL)
			{
				*end = '\0';		/* This is in GString, so OK. */
				if(code[0] == 'f' || code[0] == 'F')
					parse_code_file(min, cstr, code);
				else if(code[0] == 'P')
					parse_code_path(min, cstr, code);
				else if(code[0] == '$')
					parse_code_env(min, cstr, code);
				else if(code[0] == '#')
					parse_code_pid(min, cstr, code);
				else if(code[0] == '~')
					parse_code_home(min, cstr, code);
				else if(code[0] == 'I')
					parse_code_input(min, cstr, code, dlg);
				code = end + 1;
			}
		}
		else
			g_string_append_c(cstr, *code++);
	}
}

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

/* 1999-02-25 -	Return a pointer to the character *after* the closing brace matching the
**		one at <ptr>. Returns NULL if no such brace could be found.
*/
static const gchar * code_end(const gchar *ptr)
{
	if(ptr != NULL)
	{
		gint	count;

		for(count = 1, ptr++; *ptr && count > 0; ptr++)
		{
			if(*ptr == '{' && *(ptr - 1) != '\\')
				count++;
			else if(*ptr == '}' && *(ptr - 1) != '\\')
				count--;
		}
		return ptr;
	}
	return NULL;
}

/* 1999-02-25 -	Interpolate {}-codes in <str>, replacing them with whatever they should
**		evaluate to. For input codes, this means the placeholder "INPxx" string.
**		Returns new version of string. Doesn't touch <str> at all. Note that
**		quotes don't suppress {}-code interpolation; the only way to do so is to
**		escape the initial brace with a backslash.
*/
static GString * interpolate_brace_codes(MainInfo *min, const gchar *str, CmdDlg *dlg)
{
	const gchar	*base, *ptr;
	GString		*tmp;

	tmp = g_string_new(NULL);

	for(base = ptr = str; base != NULL && ptr != NULL;)
	{
		if((ptr = strchr(base, '{')) != NULL)		/* Get next code. */
		{
			if(ptr == base || (*(ptr - 1) != '\\'))	/* Genuine? */
			{
				const gchar	*end;

				if((end = code_end(ptr)) != NULL)
				{
					GString	*code;

					for(; base < ptr;)
						g_string_append_c(tmp, *base++);
					code = g_string_new(ptr);
					g_string_truncate(code, end - ptr);
					parse_code(min, tmp, code->str, dlg);
					g_string_free(code, TRUE);
					base = end;
				}
			}
			else
			{
				for(; base <= ptr;)
					g_string_append_c(tmp, *base++);
			}
		}
		else
			g_string_append(tmp, base);
	}
	return tmp;
}

/* 1998-09-25 -	Rewritten from scratch. This function parses the command definition in <def>, and returns
**		an argv-style vector of the "words" contained therein. It also does {}-code substitution,
**		and is generally cool. The returned vector is dynamically allocated and MUST be freed
**		when you're done with it - first word by word, then the entire vector!
**		The major change in this rewrite is that it is self-contained; the caller need not bother
**		with the dialog any longer - it's all internal. We just return the argument vector, with
**		any dialog-related stuff *already there*, or NULL on failure (or dialog cancel). Great!
** 1999-02-24 -	Semi-rewritten. In the old version, each {}-code except for {f} resulted in exactly ONE
**		argument, regardless of the number of words emitted. This has changed, now spaces separate
**		arguments, and quoting has to be used to create arguments with spaces in them. This is
**		more like the shell, a lot more flexible, and generally the right thing. I think.
*/
char ** cpr_parse(MainInfo *min, const gchar *def)
{
	GString		*cstr;
	static CmdDlg	dlg;

	if(min == NULL || def == NULL)
		return NULL;

	dlg.body = NULL;
	dlg.row = 0;
	dlg.wintitle = NULL;
	dlg.str_count = 0;
	dlg.str_focused = 0;

	cstr = interpolate_brace_codes(min, def, &dlg);

	if(dlg.body != NULL)
	{
		if((dlg.dlg = dlg_dialog(dlg.body, dlg.wintitle, "OK|Cancel", NULL, NULL)) != NULL)
		{
			dlg_dialog_set_keep(dlg.dlg, TRUE);
			if(dlg_dialog_wait(dlg.dlg) == DLG_POSITIVE)		/* OK? */
				interpolate_input(cstr, &dlg);
			else
			{
				g_string_free(cstr, TRUE);
				cstr = NULL;
			}
			destroy_input(&dlg);
			dlg_dialog_destroy(dlg.dlg);
		}
	}
	if(cstr != NULL)			/* Did we get any arguments? */
	{
		char	**argv;

		argv = str_split_args(cstr->str);
		g_string_free(cstr, TRUE);
		return argv;
	}
	return NULL;
}

/* 1998-09-26 -	Free an argument vector as returned by cpr_parse(). Not complex, but more than
**		one line - so well worth a function of its own.
** 1999-02-24 -	Thanks to the allocation intelligence of the new str_split_args() routine, this
**		has considerably less to do. Fine by me.
*/
void cpr_free(char **argv)
{
	if(argv != NULL)
		g_free(argv);
}
