#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>

#include "../../include/string.h"

#include "../guiutils.h"
#include "../cdialog.h"
#include "../config.h"


/*
 *	Usage message:
 */
#ifdef PROG_USAGE_MESG
# undef PROG_USAGE_MESG
#endif
#define PROG_USAGE_MESG		"\
Usage: confirmation_dialog [options] [message]\n\
\n\
    Where [options] can be any of the following:\n\
\n\
	-t <title>              - Set <title>.\n\
	-i <icon>               - Icon (see --help icons).\n\
	-b <button...>          - Buttons (see --help buttons).\n\
	-d <button>             - Default button (see --help buttons).\n\
	-p <type>               - Use preset <type> to specify icon and\n\
				  buttons (see --help types).\n\
	-w <column>		- Wrap <column>.\n\
	-en			- Do not parse escape sequences.\n\
	-q                      - Quiet, do not print response to stdout.\n\
	-os                     - Print response to stdout as string value.\n\
	-oi                     - Print response to stdout as integer value\n\
				  (default).\n\
	-n			- Do not print newline in stdout.\n\
	--help                  - Print help.\n\
	--help [topic]          - Print help on the specified [topic].\n\
	--version               - Print version.\n\
\n\
    Where [message] is the message that you want to display.\n\
\n\
    Returns values:\n\
\n\
	0                       - No.\n\
	1                       - Yes.\n\
	2                       - Yes to all.\n\
	3                       - Cancel.\n\
	4                       - OK.\n\
\n"


/*
 *	Output codes:
 */
typedef enum {
	OUTPUT_NONE,
	OUTPUT_STRING,
	OUTPUT_INTEGER
} output_type;


/*
 *	Icons list:
 */
#define ICONS_LIST	{				\
{ "info",		CDIALOG_ICON_INFO },		\
{ "warning",		CDIALOG_ICON_WARNING },		\
{ "error",		CDIALOG_ICON_ERROR },		\
{ "question",		CDIALOG_ICON_QUESTION },	\
{ "help",		CDIALOG_ICON_HELP },		\
{ "wizard",		CDIALOG_ICON_WIZARD },		\
{ "search",		CDIALOG_ICON_SEARCH },		\
{ "security",		CDIALOG_ICON_SECURITY },	\
{ "printer",		CDIALOG_ICON_PRINTER },		\
{ "speaker",		CDIALOG_ICON_SPEAKER },		\
{ "bulb",		CDIALOG_ICON_BULB },		\
{ "power",		CDIALOG_ICON_POWER },		\
{ "os",			CDIALOG_ICON_OS },		\
{ "terminal",		CDIALOG_ICON_TERMINAL },	\
{ "settings",		CDIALOG_ICON_SETTINGS },	\
{ "tools",		CDIALOG_ICON_TOOLS },		\
{ "monitor",		CDIALOG_ICON_MONITOR },		\
{ "clipboard_empty",	CDIALOG_ICON_CLIPBOARD_EMPTY },	\
{ "clipboard_full",	CDIALOG_ICON_CLIPBOARD_FULL },	\
{ "install",		CDIALOG_ICON_INSTALL },		\
{ "uninstall",		CDIALOG_ICON_UNINSTALL },	\
{ "cpu",		CDIALOG_ICON_CPU },		\
							\
{ "file",		CDIALOG_ICON_FILE },		\
{ "folder_closed",	CDIALOG_ICON_FOLDER_CLOSER },	\
{ "folder_opened",	CDIALOG_ICON_FOLDER_OPENED },	\
{ "link",		CDIALOG_ICON_LINK },		\
{ "pipe",		CDIALOG_ICON_PIPE },		\
{ "device",		CDIALOG_ICON_DEVICE },		\
{ "device_block",	CDIALOG_ICON_DEVICE_BLOCK },	\
{ "device_character",	CDIALOG_ICON_DEVICE_CHARACTER },\
{ "socket",		CDIALOG_ICON_SOCKET },		\
							\
{ "move",		CDIALOG_ICON_FILE_MOVE },	\
{ "copy",		CDIALOG_ICON_FILE_COPY },	\
							\
{ "planet",		CDIALOG_ICON_PLANET },		\
{ "ftp",		CDIALOG_ICON_FTP },		\
{ "chat",		CDIALOG_ICON_CHAT },		\
{ "file_www",		CDIALOG_ICON_FILE_WWW }		\
}
typedef struct {
	gchar *name;
	gint icon;
} icon_struct;


/*
 *	Buttons list:
 */
#define BUTTONS_LIST	{				\
{ "ok",			CDIALOG_BTNFLAG_OK },		\
{ "yes",		CDIALOG_BTNFLAG_YES },		\
{ "yes_to_all",		CDIALOG_BTNFLAG_YES_TO_ALL },	\
{ "no",			CDIALOG_BTNFLAG_NO },		\
{ "cancel",		CDIALOG_BTNFLAG_CANCEL },	\
{ "ignore",		CDIALOG_BTNFLAG_IGNORE },	\
{ "retry",		CDIALOG_BTNFLAG_RETRY },	\
{ "abort",		CDIALOG_BTNFLAG_ABORT },	\
{ "help",		CDIALOG_BTNFLAG_HELP }		\
}
typedef struct {
	gchar *name;
	guint button;
} button_struct;


/*
 *	Response codes list:
 */
#define RESPONSE_CODES_LIST	{			\
{ "not_available",	CDIALOG_RESPONSE_NOT_AVAILABLE },\
{ "no",			CDIALOG_RESPONSE_NO },		\
{ "yes",		CDIALOG_RESPONSE_YES },		\
{ "yes_to_all",		CDIALOG_RESPONSE_YES_TO_ALL },	\
{ "cancel",		CDIALOG_RESPONSE_CANCEL },	\
{ "ok",			CDIALOG_RESPONSE_OK },		\
{ "help",		CDIALOG_RESPONSE_HELP }		\
}
typedef struct {
	gchar *name;
	gint code;
} response_code_struct;


/*
 *	Preset types:
 */
#define TYPES_LIST	{				\
{ "info",		CDIALOG_ICON_INFO,		\
  CDIALOG_BTNFLAG_OK,					\
  CDIALOG_BTNFLAG_OK					\
},							\
{ "question",		CDIALOG_ICON_QUESTION,		\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,		\
  CDIALOG_BTNFLAG_YES					\
},							\
{ "question_cancel",	CDIALOG_ICON_QUESTION,		\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |		\
  CDIALOG_BTNFLAG_CANCEL,				\
  CDIALOG_BTNFLAG_YES					\
},							\
{ "warning",		CDIALOG_ICON_WARNING,		\
  CDIALOG_BTNFLAG_OK,					\
  CDIALOG_BTNFLAG_OK					\
},							\
{ "warning_question",	CDIALOG_ICON_WARNING,		\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,		\
  CDIALOG_BTNFLAG_YES					\
},							\
{ "warning_question_cancel",	CDIALOG_ICON_WARNING,	\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO |		\
  CDIALOG_BTNFLAG_CANCEL,				\
  CDIALOG_BTNFLAG_YES					\
},							\
{ "error",		CDIALOG_ICON_ERROR,		\
  CDIALOG_BTNFLAG_OK,					\
  CDIALOG_BTNFLAG_OK					\
},							\
							\
{ "install",		CDIALOG_ICON_INSTALL,		\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,		\
  CDIALOG_BTNFLAG_YES					\
},							\
{ "uninstall",		CDIALOG_ICON_UNINSTALL,		\
  CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,		\
  CDIALOG_BTNFLAG_NO					\
}							\
}
typedef struct {
	gchar *name;
	gint icon;
	guint buttons;
	guint default_button;
} type_struct;


void print_help_topic(const gchar *topic);
gint get_icon_from_arg(const gchar *arg);
guint get_button_from_arg(const gchar *arg);
gboolean get_type_from_arg(
	const gchar *arg,
	gint *icon,
	guint *buttons,
	guint *default_button
);
gchar *get_response_code_name(gint response);
gchar *strdup_sanitize(
	const gchar *s,
	gint wrap_column,
	gboolean parse_escapes
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)



/*
 *	Prints the help topic.
 */
void print_help_topic(const gchar *topic)
{
	if(STRISEMPTY(topic))
	    return;

	/* Icons */
	if(!g_strcasecmp(topic, "icons") ||
	   !g_strcasecmp(topic, "icon")
	)
	{
	    gint i;
	    icon_struct list[] = ICONS_LIST;
	    g_print("Available icons:\n");
	    for(i = 0; i < (sizeof(list) / sizeof(icon_struct)); i++)
	    {
		g_print("%s\n", list[i].name);
	    }
	}
	/* Buttons */
	else if(!g_strcasecmp(topic, "buttons") ||
		!g_strcasecmp(topic, "button")
	)
	{
	    gint i;
	    button_struct list[] = BUTTONS_LIST;
	    g_print("Available buttons:\n");
	    for(i = 0; i < (sizeof(list) / sizeof(button_struct)); i++)
	    {
		g_print("%s\n", list[i].name);
	    }
	}
	/* Types */
	else if(!g_strcasecmp(topic, "types") ||
		!g_strcasecmp(topic, "type")
	)
	{
	    gint i;
	    type_struct list[] = TYPES_LIST;
	    g_print("Available styles:\n");
	    for(i = 0; i < (sizeof(list) / sizeof(type_struct)); i++)
	    {
		g_print("%s\n", list[i].name);
	    }
	}
	else
	{
	    g_print(
"No such help topic \"%s\" (see --help for list of topics)\n",
		topic
	    );
	}
}

/*
 *	Returns the icon code from the given argument.
 */
gint get_icon_from_arg(const gchar *arg)
{
	gint i;
	icon_struct list[] = ICONS_LIST;

	if(arg == NULL)
	    return(CDIALOG_ICON_INFO);

	for(i = 0; i < (sizeof(list) / sizeof(icon_struct)); i++)
	{
	    if(!g_strcasecmp(list[i].name, arg))
		return(list[i].icon);
	}

	return(CDIALOG_ICON_INFO);
}

/*
 *	Returnst he button flag from the given argument.
 */
guint get_button_from_arg(const gchar *arg)
{
	gint i;
	button_struct list[] = BUTTONS_LIST;

	if(arg == NULL)
	    return(0);

	for(i = 0; i < (sizeof(list) / sizeof(button_struct)); i++)
	{
	    if(!g_strcasecmp(list[i].name, arg))
		return(list[i].button);
	}

	return(0);
}

/*
 *	Returns the icon, buttons, and default button for the specified
 *	type.
 *
 *	Returns TRUE if a valid type was matched withthe given arg.
 */
gboolean get_type_from_arg(
	const gchar *arg,
	gint *icon,
	guint *buttons,
	guint *default_button
)
{
	gint i;
	type_struct list[] = TYPES_LIST;

	if(arg == NULL)
	    return(FALSE);

	for(i = 0; i < (sizeof(list) / sizeof(type_struct)); i++)
	{
	    if(!g_strcasecmp(list[i].name, arg))
	    {
		if(icon != NULL)
		    *icon = list[i].icon;
		if(buttons != NULL)
		    *buttons = list[i].buttons;
		if(default_button != NULL)
		    *default_button = list[i].default_button;
		return(TRUE);
	    }
	}

	return(FALSE);
}

/*
 *	Returns a string describing the given response code.
 *
 *	The returned string must not be modified or deleted.
 */
gchar *get_response_code_name(gint response)
{
	gint i;
	response_code_struct list[] = RESPONSE_CODES_LIST;
	static gchar name[80];

	*name = '\0';

	for(i = 0; i < (sizeof(list) / sizeof(response_code_struct)); i++)
	{
	    if(list[i].code == response)
	    {
		strncpy(name, list[i].name, sizeof(name));
		break;
	    }
	}

	return(name);
}


/*
 *	Makes a copy of the given string and sanities it.
 *
 *	Returns the coppied string or NULL on error.
 */
gchar *strdup_sanitize(
	const gchar *s,
	gint wrap_column,
	gboolean parse_escapes
)
{
	gint i;
	gchar *s2;

	if(s == NULL)
	    return(NULL);

	s2 = STRDUP(s);

	/* Convert escape sequences */
	if(parse_escapes)
	{
	    gchar *sp;

	    /* Iterate through string */
	    for(sp = s2; *sp != '\0'; sp++)
	    {
		/* Is escape character? */
		if(*sp == '\\')
		{
		    /* Delete current character */
		    i = sp - s2;
		    s2 = strdelchr(s2, i);
		    sp = s2 + i;

		    /* Replace escaped character */
		    switch(*sp)
		    {
		      case '\0':
		        sp--;
		        break;
		      case 'n':
		        *sp = '\n';
		        break;
		      case 'r':
			*sp = '\r';
			break;
		      case 't':
			*sp = '\t';
			break;
		      case 'f':
			*sp = '\f';
			break;
		      case 'e':
			*sp = '\e';
			break;
		    }
		}
	    }
	}

	/* Fit to column */
	if(wrap_column > 0)
	{
	    gchar *sp, *last_blank = NULL;
	    gint column = 0;

	    /* Iterate through string */
	    for(sp = s2; *sp != '\0'; sp++)
	    {
		/* Exceeded wrap column? */
		if(column >= wrap_column)
		{
		    /* Seek back to last blank character (if any) */
		    if(last_blank != NULL)
		    {
			sp = last_blank;
			while((sp >= s2) ? ISBLANK(*sp) : FALSE)
			    sp--;
			sp++;
		    }

		    /* Insert newline (some pointers may now be invalid) */
		    i = sp - s2;
		    s2 = strinschr(s2, i, '\n');
		    sp = s2 + i;

		    /* If there was blank character, seek past any
		     * additional blanks
		     */
		    if(last_blank != NULL)
		    {
			sp++;		/* Seek past new line */
			while(ISBLANK(*sp))
			{
			    i = sp - s2;
			    s2 = strdelchr(s2, i);
			    sp = s2 + i;
			}
			sp--;
		    }

		    column = 0;
		    last_blank = NULL;
		}
		/* New line? */
		else if(*sp == '\n')
		{
		    column = 0;
		    last_blank = NULL;
		}
		/* Other character */
		else
		{
		    if(ISBLANK(*sp))
			last_blank = sp;
		    column++;
		}
	    }
	}

	return(s2);
}



int main(int argc, char *argv[])
{
	gint i, response;
	const gchar	*title = "Confirmation Dialog";
	gchar *dmessage;
	gint		icon = CDIALOG_ICON_INFO;
	guint		buttons = CDIALOG_BTNFLAG_OK,
			default_button = CDIALOG_BTNFLAG_OK;
	gint		wrap_column = 72;
	output_type	output = OUTPUT_STRING;
	gboolean	output_newline = TRUE,
			parse_escapes = TRUE;
	const gchar *arg;
	const gchar *message = NULL;
	GdkRectangle *geometry = NULL;

	if(argc < 2)
	{
	    g_print(PROG_USAGE_MESG);
	    return(0);
	}

	/* Parse arguments */
	for(i = 1; i < argc; i++)
	{
	    arg = argv[i];
	    if(arg == NULL)
		continue;

	    /* Help */
	    if(!g_strcasecmp(arg, "--help") ||
	       !g_strcasecmp(arg, "-help") ||
	       !g_strcasecmp(arg, "--h") ||
	       !g_strcasecmp(arg, "-h")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		if(arg != NULL)
		    print_help_topic(arg);
		else
		    g_print(PROG_USAGE_MESG);
		return(0);
	    }
	    /* Version */
	    else if(!g_strcasecmp(arg, "--version") ||
	            !g_strcasecmp(arg, "-version")
	    )
	    {
		g_print(
		    PROG_NAME " Confirmation Dialog " PROG_VERSION "\n"
		    PROG_COPYRIGHT
		);
		return(0);
	    }
	    /* Title */
	    else if(!g_strcasecmp(arg, "--title") ||
		    !g_strcasecmp(arg, "-title") ||
		    !g_strcasecmp(arg, "--t") ||
		    !g_strcasecmp(arg, "-t")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		title = arg;
	    }
	    /* Icon */
	    else if(!g_strcasecmp(arg, "--icon") ||
		    !g_strcasecmp(arg, "-icon") ||
		    !g_strcasecmp(arg, "--i") ||
		    !g_strcasecmp(arg, "-i")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		icon = get_icon_from_arg(arg);
	    }
	    /* Buttons */
	    else if(!g_strcasecmp(arg, "--buttons") ||
		    !g_strcasecmp(arg, "-buttons") ||
		    !g_strcasecmp(arg, "--b") ||
		    !g_strcasecmp(arg, "-b")
	    )
	    {
		for(buttons = 0, i++; i < (argc - 1); i++)
		{
		    arg = argv[i];
		    if(arg == NULL)
			continue;

		    if(*arg == '-')
			break;

		    buttons |= get_button_from_arg(arg);
		}
		i--;
	    }
	    /* Default Button */
	    else if(!g_strcasecmp(arg, "--default-button") ||
		    !g_strcasecmp(arg, "-default-button") ||
		    !g_strcasecmp(arg, "--d") ||
		    !g_strcasecmp(arg, "-d")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		default_button = get_button_from_arg(arg);
	    }
	    /* Preset Type */
	    else if(!g_strcasecmp(arg, "--preset") ||
		    !g_strcasecmp(arg, "-preset") ||
		    !g_strcasecmp(arg, "--p") ||
		    !g_strcasecmp(arg, "-p")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		if(!get_type_from_arg(
		    arg, &icon, &buttons, &default_button
		))
		    g_printerr(
"%s: No such preset type \"%s\" (see \"--help types\" for list).\n",
			argv[i - 1], arg
		    );
	    }
	    /* Wrap Column */
	    else if(!g_strcasecmp(arg, "--wrap-column") ||
		    !g_strcasecmp(arg, "-wrap-column") ||
		    !g_strcasecmp(arg, "--w") ||
		    !g_strcasecmp(arg, "-w")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		wrap_column = ATOI(arg);
	    }
	    /* Do not parse escape sequences */
	    else if(!g_strcasecmp(arg, "--escape-no") ||
		    !g_strcasecmp(arg, "-escape-no") ||
		    !g_strcasecmp(arg, "--en") ||
		    !g_strcasecmp(arg, "-en")
	    )
	    {
		parse_escapes = FALSE;
	    }
	    /* Quiet */
	    else if(!g_strcasecmp(arg, "--quiet") ||
		    !g_strcasecmp(arg, "-quiet") ||
		    !g_strcasecmp(arg, "--q") ||
		    !g_strcasecmp(arg, "-q")
	    )
	    {
		output = OUTPUT_NONE;
	    }
	    /* Output String */
	    else if(!g_strcasecmp(arg, "--output-string") ||
		    !g_strcasecmp(arg, "-output-string") ||
		    !g_strcasecmp(arg, "--os") ||
		    !g_strcasecmp(arg, "-os")
	    )
	    {
		output = OUTPUT_STRING;
	    }
	    /* Output Integer */
	    else if(!g_strcasecmp(arg, "--output-integer") ||
		    !g_strcasecmp(arg, "-output-integer") ||
		    !g_strcasecmp(arg, "--oi") ||
		    !g_strcasecmp(arg, "-oi")
	    )
	    {
		output = OUTPUT_INTEGER;
	    }
	    /* Do not output newline */
	    else if(!g_strcasecmp(arg, "--no-newline") ||
		    !g_strcasecmp(arg, "-no-newline") ||
		    !g_strcasecmp(arg, "--n") ||
		    !g_strcasecmp(arg, "-n")
	    )
	    {
		output_newline = FALSE;
	    }

	    /* Geometry */
	    else if(!g_strcasecmp(arg, "--geometry") ||
		    !g_strcasecmp(arg, "-geometry")
	    )
	    {
		i++;
		arg = (i < argc) ? argv[i] : NULL;
		if(!STRISEMPTY(arg))
		{
		    gint x, y, width, height;
		    g_free(geometry);
		    geometry = (GdkRectangle *)g_malloc0(
			sizeof(GdkRectangle)
		    );
		    gdk_parse_geometry(
			arg, &x, &y, &width, &height
		    );
		    geometry->x = x;
		    geometry->y = y;
		    geometry->width = width;
		    geometry->height = height;
		}
	    }
	    else if(arg[0] != '-')
	    {
		message = arg;
	    }
	}

	/* No default button set? */
	if(!(buttons & default_button))
	{
/* TODO */
	}

	/* Use default message? */
	if(message == NULL)
	{
	    message = "No message available";
	}

	/* Make a copy of the message and substitute characters */
	dmessage = strdup_sanitize(
	    message,
	    wrap_column,
	    parse_escapes
	);


	/* Initnialize GTK */
	gtk_set_locale();
	gtk_init(&argc, &argv);
	gdk_rgb_init();

	/* Initialize dialogs */
	CDialogInit();


	/* Set dialog geometry */
	if(geometry != NULL)
	{
	    GtkWidget *w = CDialogGetToplevel();
	    if(w != NULL)
	    {
		gtk_widget_set_uposition(w, geometry->x, geometry->y);
	    }
	}
	else
	{
	    /* No geometry set, so just center the dialog */
	    gint	root_width = gdk_screen_width(),
			root_height = gdk_screen_height();
	    GtkWidget *w = CDialogGetToplevel();
	    if(w != NULL)
	    {
		gint	width = w->allocation.width,
			height = w->allocation.height;

		gtk_widget_set_uposition(
		    w,
		    (root_width / 2) - (width / 2),
		    (root_height / 2) - (height / 2)
		);
	    }
	}

	/* Map confirmation dialog */
	response = CDialogGetResponse(
	    title,
	    dmessage,
	    NULL,
	    icon,
	    buttons,
	    default_button
	);

	/* Delete coppied message */
	g_free(dmessage);

	/* Handle output */
	switch(output)
	{
	  case OUTPUT_INTEGER:
	    g_print(
		"%i%c",
		response,
		(output_newline) ? '\n' : '\0'
	    );
	    break;
	  case OUTPUT_STRING:
	    g_print(
		"%s%c",
		get_response_code_name(response),
		(output_newline) ? '\n' : '\0'
	    );
	    break;
	  case OUTPUT_NONE:
	    break;
	}

	/* Shutdown dialogs */
	CDialogShutdown();

	g_free(geometry);


	return(response);
}
