#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include "guiutils.h"
#include "csd.h"


typedef struct _csd_data_struct		csd_data_struct;
typedef struct _csd_color_button_struct	csd_color_button_struct;

/*
 *	Color Selection Dialog:
 */
struct _csd_data_struct {

	gboolean	initialized,
			map_state;

	GtkWidget	*csd;		/* GtkColorSelectionDialog */

	csd_color_struct current_color;

	gpointer	client_data;

	void		(*color_changed_cb)(
		gpointer,		/* Data */
		csd_color_struct *	/* Color */
	);

};
#define CSD_DATA(p)	((csd_data_struct *)(p))

/*
 *	Color Button structure:
 */
struct _csd_color_button_struct {

	GtkWidget	*toplevel,
			*label,
			*button,
			*frame,
			*drawing_area;
	gchar		*label_text;
	csd_color_struct	color;

};
#define CSD_COLOR_BUTTON(p)	((csd_color_button_struct *)(p))
#define CSD_COLOR_BUTTON_DATA_KEY	"csd_color_button_data"


static void CSDOKCB(GtkWidget *widget, gpointer data);
static void CSDCancelCB(GtkWidget *widget, gpointer data);
static gint CSDCloseCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void CSDColorChangeCB(GtkWidget *widget, gpointer data);

gint CSDInit(void);
void CSDSetStyle(GtkRcStyle *rc_style);
void CSDSetTransientFor(GtkWidget *w);
gboolean CSDIsQuery(void);
void CSDBreakQuery(void);
gboolean CSDGetResponse(
	const gchar *title,
	const gchar *ok_label, const gchar *cancel_label,
	csd_color_struct *start_color,
	csd_color_struct **color_rtn,
	gpointer client_data,
	void (*color_changed_cb)(gpointer, csd_color_struct *)
);
void CSDMap(void);
void CSDUnmap(void);
void CSDShutdown(void);

static void CSDColorButtonDataDestroyCB(gpointer data);
static void CSDColorButtonSelectCB(GtkWidget *widget, gpointer data);
GtkWidget *CSDColorButtonNew(
	const gchar *label, gint label_width,
	gpointer client_data,
	void (*func_cb)(GtkWidget *, gpointer)
);
GtkWidget *CSDColorButtonNewSimple(const gchar *label, gint label_width);
GtkWidget *CSDColorButtonGetButton(GtkWidget *w);
void CSDColorButtonGetColor(GtkWidget *w, csd_color_struct *c);
void CSDColorButtonSetColor(GtkWidget *w, const csd_color_struct *c);


static gint block_loop_level;
static csd_data_struct csd_data;
static gboolean csd_got_user_response;
static csd_color_struct response_color;


#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)


/*
 *	Ok button callback.
 */
static void CSDOKCB(GtkWidget *widget, gpointer data)
{
	csd_data_struct *csdd = (csd_data_struct *)data;
	if(csdd == NULL)
	    return;

	if(!csdd->initialized)
	    return;

	/* Set user response code to TRUE, indicating OK */
	csd_got_user_response = TRUE;

	/* Copy response color */
	memcpy(
	    &response_color, &csdd->current_color,
	    sizeof(csd_color_struct)
	);

	/* Unmap */
	CSDUnmap();

	/* Break out of blocking loop */
	gtk_main_quit();
	block_loop_level--;
}

/*
 *	Cancel callback.
 */
static void CSDCancelCB(GtkWidget *widget, gpointer data)
{
	csd_data_struct *csdd = (csd_data_struct *)data;
	if(csdd == NULL)
	    return;

	if(!csdd->initialized)
	    return;

	/* Set user response code to FALSE, indicating Cancel */
	csd_got_user_response = FALSE;

	/* Unmap */
	CSDUnmap();

	/* Break out of blocking loop */
	gtk_main_quit();
	block_loop_level--;
}           

/*
 *      Close callback.
 */
static gint CSDCloseCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	CSDCancelCB(widget, data);
	return(TRUE);
}

/*
 *	Color changed callback.
 */
static void CSDColorChangeCB(GtkWidget *widget, gpointer data)
{
	gdouble vd[3];
	GtkColorSelectionDialog *csd;
	csd_data_struct *csdd = (csd_data_struct *)data;
	if(csdd == NULL)
	    return;

	if(!csdd->initialized)
	    return;

	csd = (GtkColorSelectionDialog *)csdd->csd;
	if(csd == NULL)
	    return;

	/* Get current color */
	gtk_color_selection_get_color(
	    GTK_COLOR_SELECTION(csd->colorsel), vd
	);

	/* Copy current color values to csdd structure's
	 * csd_color_struct.
	 */
	csdd->current_color.r = (gfloat)vd[0];
	csdd->current_color.g = (gfloat)vd[1];
	csdd->current_color.b = (gfloat)vd[2];
	csdd->current_color.a = 1.0f;

	/* Call color changed callback */
	if(csdd->color_changed_cb != NULL)
	    csdd->color_changed_cb(
		csdd->client_data,
		&csdd->current_color
	    );
}


/*
 *	Initializes color selection dialog..
 */
gint CSDInit(void)
{
	GdkWindow *window;
	GtkWidget *w;
	GtkColorSelectionDialog *csd;
	csd_data_struct *csdd = &csd_data;


	/* Reset local globals */
	block_loop_level = 0;
	csd_got_user_response = FALSE;
	memset(&response_color, 0x00, sizeof(csd_color_struct));
	memset(csdd, 0x00, sizeof(csd_data_struct));

	/* Reset values */
	csdd->initialized = TRUE;
	csdd->map_state = FALSE;

	/* Create the color selection dialog */
	csdd->csd = w = gtk_color_selection_dialog_new("Select Color");
	csd = GTK_COLOR_SELECTION_DIALOG(w);
	if(!GTK_WIDGET_REALIZED(w))
	    gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    gdk_window_set_decorations(
		window,
		GDK_DECOR_BORDER | GDK_DECOR_TITLE
	    );
	    gdk_window_set_functions(
		window,
		GDK_FUNC_MOVE | GDK_FUNC_CLOSE
	    );
	}
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(CSDCloseCB), (gpointer)csdd
	);

	/* Get GtkColorSelection widget */
	w = csd->colorsel;

	/* Connect "color_changed" signal to the GtkColorSelection
	 * widget.
	 */
	gtk_signal_connect(
	    GTK_OBJECT(w), "color_changed",
	    GTK_SIGNAL_FUNC(CSDColorChangeCB), (gpointer)csdd
	);

	/* Ok button */
	gtk_signal_connect(
	    GTK_OBJECT(csd->ok_button), "clicked",
	    GTK_SIGNAL_FUNC(CSDOKCB), (gpointer)csdd
	);
	gtk_widget_show(csd->ok_button);

	/* Hide reset button */
	if(csd->reset_button != NULL)
	    gtk_widget_hide(csd->reset_button);

	/* Cancel button */
	gtk_signal_connect(
	    GTK_OBJECT(csd->cancel_button), "clicked",
	    GTK_SIGNAL_FUNC(CSDCancelCB), (gpointer)csdd
	);
	gtk_widget_show(csd->cancel_button);

	/* Hide help button */
	if(csd->help_button != NULL)
	    gtk_widget_hide(csd->help_button);

	return(0);
}

/*
 *      Sets the Dialog's style.
 */
void CSDSetStyle(GtkRcStyle *rc_style)
{
	GtkWidget *w;
	csd_data_struct *csdd = &csd_data;

	if(!csdd->initialized)
	    return;

	w = csdd->csd;
	if(w != NULL)
	{
	    if(rc_style != NULL)
	    {
		gtk_widget_modify_style(w, rc_style);
	    }
	    else
	    {
		rc_style = gtk_rc_style_new();
		gtk_widget_modify_style_recursive(w, rc_style);
		GTK_RC_STYLE_UNREF(rc_style)
	    }
	}
}

/*
 *      Sets the Dialog to be a transient for the given GtkWindow w.
 *
 *      If w is NULL then transient for will be unset.
 */
void CSDSetTransientFor(GtkWidget *w)
{
	csd_data_struct *csdd = &csd_data;

	if(!csdd->initialized)
	    return;

	if(csdd->csd != NULL)
	{
	    if(w != NULL)
	    {
		/* Given widget if not NULL, must be a window */
		if(!GTK_IS_WINDOW(GTK_OBJECT(w)))
		    return;

		gtk_window_set_modal(
		    GTK_WINDOW(csdd->csd), TRUE
		);
		gtk_window_set_transient_for(
		    GTK_WINDOW(csdd->csd), GTK_WINDOW(w)
		);
	    }
	    else
	    {
		gtk_window_set_modal(
		    GTK_WINDOW(csdd->csd), FALSE
		);
		gtk_window_set_transient_for(
		    GTK_WINDOW(csdd->csd), NULL
		);
	    }
	}
}


/*
 *      Returns TRUE if currently blocking for query.
 */
gboolean CSDIsQuery(void)
{
	if(block_loop_level > 0)
	    return(TRUE);
	else
	    return(FALSE);
}

/*
 *	Ends query if any and returns a not available response.
 */
void CSDBreakQuery(void)
{
	csd_got_user_response = FALSE;

	/* Break out of an additional blocking loops */
	while(block_loop_level > 0)
	{
	    gtk_main_quit();
	    block_loop_level--;
	}
	block_loop_level = 0;
}

/*
 *	Maps the color selection dialog and sets up the inital values.
 *
 *	Returns TRUE if a color was sselected or FALSE if user canceled.
 *
 *	For most values that are set NULL, the value is left unchanged.
 *	All given values are coppied.
 *
 *	All returned pointer values should be considered statically
 *	allocated, do not deallocate them.
 */
gboolean CSDGetResponse(
	const gchar *title,
	const gchar *ok_label, const gchar *cancel_label,
	csd_color_struct *start_color,
	csd_color_struct **color_rtn,
	gpointer client_data,
	void (*color_changed_cb)(gpointer, csd_color_struct *)
)
{
	GtkWidget *w;
	GtkColorSelectionDialog *csd;
	csd_data_struct *csdd = &csd_data;


	/* Do not handle response if already waiting for a response,
	 * return with a not available response code.
	 */
	if(block_loop_level > 0)
	{
	    if(color_rtn != NULL)
		*color_rtn = NULL;

	    return(FALSE);
	}

	/* Reset global responses values */
	csd_got_user_response = FALSE;
	memset(&response_color, 0x00, sizeof(csd_color_struct));

	/* Reset returns */
	if(color_rtn != NULL)
	    (*color_rtn) = NULL;


	/* Color selection dialog must be initialized */
	if(!csdd->initialized)
	    return(csd_got_user_response);

	/* Get pointer to color selection dialog widget */
	w = csdd->csd;
	if(w == NULL)
	    return(csd_got_user_response);
	csd = GTK_COLOR_SELECTION_DIALOG(w);

	/* Update title if specified */
	if(title != NULL)
	    gtk_window_set_title(GTK_WINDOW(w), title);

	/* Update button labels */
	if((ok_label != NULL) && (csd->ok_button != NULL))
	{
	    GtkButton *button = (GtkButton *)csd->ok_button;
	    GtkWidget *w2;

	    w2 = GTK_BIN(button)->child;
	    if((w2 != NULL) ? GTK_IS_LABEL(w2) : 0)
		gtk_label_set_text(GTK_LABEL(w2), ok_label);
	}
	if((cancel_label != NULL) && (csd->cancel_button != NULL))
	{
	    GtkButton *button = (GtkButton *)csd->cancel_button;
	    GtkWidget *w2;

	    w2 = GTK_BIN(button)->child;
	    if((w2 != NULL) ? GTK_IS_LABEL(w2) : 0)
		gtk_label_set_text(GTK_LABEL(w2), cancel_label);
	}


	/* Update initial start color if specified */
	if(start_color != NULL)
	{
	    gdouble vd[3];
	    GtkColorSelection *colorsel;

	    colorsel = GTK_COLOR_SELECTION(
		GTK_COLOR_SELECTION_DIALOG(w)->colorsel
	    );

	    vd[0] = start_color->r;
	    vd[1] = start_color->g;
	    vd[2] = start_color->b;
/*		  = start_color->a; */

	    gtk_color_selection_set_color(colorsel, vd);

	    csdd->current_color.r = start_color->r;
	    csdd->current_color.g = start_color->g;
	    csdd->current_color.b = start_color->b;
	    csdd->current_color.a = start_color->a;
	}

	/* Set up callbacks */
	csdd->client_data = client_data;
	csdd->color_changed_cb = color_changed_cb;

	/* Map color selection dialog as needed */
	CSDMap();

	/* Block GUI untill response */
	block_loop_level++;
	gtk_main();

	/* Unmap color selection dialog just in case it was not unmapped
	 * from any of the callbacks.
	 */
	CSDUnmap();

	/* Break out of an additional blocking loops */
	while(block_loop_level > 0)
	{
	    gtk_main_quit();
	    block_loop_level--;
	}
	block_loop_level = 0;


	/* Begin setting returns */

	/* Color return */
	if(color_rtn != NULL)
	    *color_rtn = &response_color;

	return(csd_got_user_response);
}


/*
 *	Maps the color selection dialog as needed.
 */
void CSDMap(void)
{
	GtkWidget *w;
	csd_data_struct *csdd = &csd_data;


	if(csdd == NULL)
	    return;

	if(!csdd->initialized)
	    return;

	w = csdd->csd;
	gtk_widget_show_raise(w);
	csdd->map_state = TRUE;
}

/*
 *	Unmaps the color selection dialog as needed.
 */
void CSDUnmap(void)
{
	csd_data_struct *csdd = &csd_data;


	if(csdd == NULL)
	    return;

	if(!csdd->initialized)
	    return;

	if(csdd->map_state)
	{
	    GtkWidget *w = csdd->csd;

	    if(w != NULL)
		gtk_widget_hide(w);

	    csdd->map_state = FALSE;
	}
}

/*
 *	Deallocates color selection dialog resources.
 */
void CSDShutdown(void)
{
	GtkWidget **w;
	csd_data_struct *csdd = &csd_data;


	/* Reset local globals */
	csd_got_user_response = FALSE; 
	memset(&response_color, 0x00, sizeof(csd_color_struct));

	/* Break out of an additional blocking loops */
	while(block_loop_level > 0)
	{
	    gtk_main_quit();
	    block_loop_level--;
	}
	block_loop_level = 0;


	/* Is color selection dialog initialized? */
	if(csdd->initialized)
	{
#define DO_DESTROY_WIDGET       \
{ if(*w != NULL) { gtk_widget_destroy(*w); *w = NULL; } }

	    /* Begin destroying file browser widgets */
	    w = &csdd->csd;
	    DO_DESTROY_WIDGET

#undef DO_DESTROY_WIDGET
	}

	/* Clear color selection dialog structure */
	memset(csdd, 0x00, sizeof(csd_data_struct));
}


/*
 *	Color Button GtkObject data "destroy" signal callback.
 */
static void CSDColorButtonDataDestroyCB(gpointer data)
{
	csd_color_button_struct *b = CSD_COLOR_BUTTON(data);
	if(b == NULL)
	    return;

	g_free(b->label_text);
	g_free(b);
}

/*
 *	Color Button select signal callback.
 */
static void CSDColorButtonSelectCB(GtkWidget *widget, gpointer data)
{
	gboolean status;
	GtkWidget *w;
	csd_color_struct c, *c_rtn = NULL;
	void (*color_changed_cb)(gpointer, csd_color_struct *) =
	    (gpointer)CSDColorButtonSetColor;
	csd_color_button_struct *b = CSD_COLOR_BUTTON(data);
	if((b == NULL) || CSDIsQuery())
	    return;

	/* Get toplevel for color button */
	w = b->toplevel;

	/* Record current color */
	memcpy(&c, &b->color, sizeof(csd_color_struct));

	/* Get response */
	CSDSetTransientFor(gtk_widget_get_toplevel(w));
	status = CSDGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Escoja El Color",
	    "Escoja", "Cancele",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Couleur Privilégiée",
	    "Privilégié", "Annuler",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Wahlen Sie Farbe Aus",
	    "Wahlen", "Heben",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Scegliere Il Colore",
	    "Sceglier", "Annullar",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Selecteer De Kleur",
	    "Selecter", "Annuler",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Selecione Cor",
	    "Selecion", "Cancela",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Velg Ut Farge",
	    "Velg", "Kanseler",
#else
	    "Select Color",
	    "Select", "Cancel",
#endif
	    &c, &c_rtn,
	    w, color_changed_cb
	);
	CSDSetTransientFor(NULL);

	/* Got response? */
	if(status && (c_rtn != NULL))
	{
	    CSDColorButtonSetColor(w, c_rtn);
	}
	else
	{
	    CSDColorButtonSetColor(w, &c);
	}
}

/*
 *	Creates a new color button.
 */
GtkWidget *CSDColorButtonNew(
	const gchar *label, gint label_width,
	gpointer client_data,
	void (*func_cb)(GtkWidget *, gpointer)
)
{
	const gint border_minor = 2;
	GtkWidget *w;
	csd_color_button_struct *b = CSD_COLOR_BUTTON(g_malloc0(
	    sizeof(csd_color_button_struct)
	));
	b->toplevel = w = gtk_hbox_new(FALSE, border_minor);
	gtk_object_set_data_full(
	    GTK_OBJECT(w), CSD_COLOR_BUTTON_DATA_KEY,
	    b, CSDColorButtonDataDestroyCB
	);
	if(!STRISEMPTY(label))
	{
	    b->label = w = gtk_label_new(label);
	    b->label_text = STRDUP(label);
	    gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	    gtk_widget_set_usize(w, label_width, -1);
	    gtk_box_pack_start(GTK_BOX(b->toplevel), w, FALSE, FALSE, 0);
	    gtk_widget_show(w);
	}
	b->button = w = gtk_button_new();
	gtk_box_pack_start(GTK_BOX(b->toplevel), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	b->frame = w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_widget_set_usize(w, 20, 20);
	gtk_container_add(GTK_CONTAINER(b->button), w);
	gtk_widget_show(w);
	b->drawing_area = w = gtk_drawing_area_new();
	gtk_widget_add_events(
	    w,
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
	    GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK
	);
	gtk_container_add(GTK_CONTAINER(b->frame), w);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"El clic para escoger el color"
#elif defined(PROG_LANGUAGE_FRENCH)
"Le déclic pour choisir la couleur"
#elif defined(PROG_LANGUAGE_GERMAN)
"Klicken, Farbe auszuwählen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Lo scatto di scegliere il colore"
#elif defined(PROG_LANGUAGE_DUTCH)
"Klik kleur te selecteren"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O estalido selecionar cor"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Klikk velge ut farge"
#else
"Click to select color"
#endif
	);
	gtk_widget_show(w);

	if(func_cb != NULL)
	    gtk_signal_connect(
		GTK_OBJECT(b->button), "clicked",
		GTK_SIGNAL_FUNC(func_cb), client_data
	    );

	return(b->toplevel);
}

/*
 *	Creates a new color button with "clicked" signal already
 *	connected to a default callback which prompts for a new color
 *	using the color selection dialog and sets the new color for
 *	the color button as appropriate.
 */
GtkWidget *CSDColorButtonNewSimple(const gchar *label, gint label_width)
{
	GtkWidget *w = CSDColorButtonNew(
	    label, label_width, NULL, NULL
	);
	csd_color_button_struct *b = CSD_COLOR_BUTTON(
	    GTK_OBJECT_GET_DATA(w, CSD_COLOR_BUTTON_DATA_KEY)
	);
	if(b->button != NULL)
	    gtk_signal_connect(
		GTK_OBJECT(b->button), "clicked",
		GTK_SIGNAL_FUNC(CSDColorButtonSelectCB), b
	    );
	return(w);
}

/*
 *	Returns the GtkButton of the given color button.
 */
GtkWidget *CSDColorButtonGetButton(GtkWidget *w)
{
	csd_color_button_struct *b = CSD_COLOR_BUTTON(
	    GTK_OBJECT_GET_DATA(w, CSD_COLOR_BUTTON_DATA_KEY)
	);
	return((b != NULL) ? b->button : NULL);
}

/*
 *	Gets the color button's current color.
 */
void CSDColorButtonGetColor(GtkWidget *w, csd_color_struct *c)
{
	csd_color_button_struct *b = CSD_COLOR_BUTTON(
	    GTK_OBJECT_GET_DATA(w, CSD_COLOR_BUTTON_DATA_KEY)
	);
	if((b == NULL) || (c == NULL))
	    return;

	if(c != &b->color)
	    memcpy(c, &b->color, sizeof(csd_color_struct));
}

/*
 *	Sets the color button's color.
 */
void CSDColorButtonSetColor(GtkWidget *w, const csd_color_struct *c)
{
	GdkColor c2;
	GdkColormap *colormap;
	GdkWindow *window;
	csd_color_button_struct *b = CSD_COLOR_BUTTON(
	    GTK_OBJECT_GET_DATA(w, CSD_COLOR_BUTTON_DATA_KEY)
	);
	if((b == NULL) || (c == NULL))
	    return;

	/* Record newly set color */
	if(c != &b->color)
	    memcpy(&b->color, c, sizeof(csd_color_struct));

	/* Get GtkDrawingArea and realize it */
	w = b->drawing_area;
	if(w == NULL)
	    return;
	if(!GTK_WIDGET_REALIZED(w))
	    gtk_widget_realize(w);
	if(!GTK_WIDGET_REALIZED(w))
	    return;

	/* Get the GtkDrawingArea's GdkWindow */
	window = w->window;
	if(window == NULL)
	    return;

	/* Get colormap from window and allocate a new GdkColor for the
	 * setting of the GdkWindow's background color
	 */
	colormap = gdk_window_get_colormap(window);
	if(colormap == NULL)
	    return;

	c2.red		= (gushort)(c->r * (gushort)-1);
	c2.green	= (gushort)(c->g * (gushort)-1);
	c2.blue		= (gushort)(c->b * (gushort)-1);
	GDK_COLORMAP_ALLOC_COLOR(colormap, &c2)
	gdk_window_set_background(window, &c2);
	GDK_COLORMAP_FREE_COLOR(colormap, &c2)
	gdk_window_clear(window);
}
