#include <config.h>
#include <math.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-util.h>
#include <libgnome/gnome-desktop-item.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "button-widget.h"
#include "panel-widget.h"
#include "basep-widget.h"
#include "panel-types.h"
#include "panel-util.h"
#include "panel-config-global.h"
#include "panel-marshal.h"

#undef BUTTON_WIDGET_DEBUG

#define BUTTON_WIDGET_DISPLACEMENT 2

extern GlobalConfig global_config;

static void     button_widget_class_init     (ButtonWidgetClass *klass);
static void     button_widget_instance_init  (ButtonWidget      *button);
static void     button_widget_size_request   (GtkWidget         *widget,
					      GtkRequisition    *requisition);
static void     button_widget_size_allocate  (GtkWidget         *widget,
					      GtkAllocation     *allocation);
static void     button_widget_realize        (GtkWidget         *widget);
static gboolean button_widget_expose         (GtkWidget         *widget,
					      GdkEventExpose    *event);
static void     button_widget_destroy        (GtkObject         *obj);
static gboolean button_widget_button_press   (GtkWidget         *widget,
					      GdkEventButton	*event);
static gboolean button_widget_enter_notify   (GtkWidget         *widget,
					      GdkEventCrossing  *event);
static gboolean button_widget_leave_notify   (GtkWidget         *widget,
					      GdkEventCrossing  *event);
static void     button_widget_button_pressed (GtkButton         *button);
static void     button_widget_button_released (GtkButton         *button);

static GtkButtonClass *button_widget_parent_class;

GType
button_widget_get_type (void)
{
	static GType object_type = 0;

	if (object_type == 0) {
		static const GTypeInfo object_info = {
			sizeof (ButtonWidgetClass),
                    	(GBaseInitFunc)         NULL,
                    	(GBaseFinalizeFunc)     NULL,
                    	(GClassInitFunc)        button_widget_class_init,
                    	NULL,                   /* class_finalize */
                    	NULL,                   /* class_data */
                    	sizeof (ButtonWidget),
                    	0,                      /* n_preallocs */
                    	(GInstanceInitFunc)     button_widget_instance_init

		};

		object_type = g_type_register_static (GTK_TYPE_BUTTON, "ButtonWidget", &object_info, 0);
		button_widget_parent_class = g_type_class_ref (GTK_TYPE_BUTTON);
	}

	return object_type;
}

static void
button_widget_class_init (ButtonWidgetClass *class)
{
	GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
	GtkButtonClass *button_class = GTK_BUTTON_CLASS (class);

	gtk_object_class->destroy = button_widget_destroy;
	  
	widget_class->realize = button_widget_realize;
	widget_class->size_allocate = button_widget_size_allocate;
	widget_class->size_request = button_widget_size_request;
	widget_class->button_press_event = button_widget_button_press;
	widget_class->enter_notify_event = button_widget_enter_notify;
	widget_class->leave_notify_event = button_widget_leave_notify;
	widget_class->expose_event = button_widget_expose;

	button_class->pressed = button_widget_button_pressed;
	button_class->released = button_widget_button_released;
}
 
static void
translate_to(GtkWidget *from, GtkWidget *to, int *x, int *y)
{
	while (from != to) {
		if (!GTK_WIDGET_NO_WINDOW (from)) {
			*x += from->allocation.x;
			*y += from->allocation.y;
		}
		from = from->parent;
	}
}

static GtkWidget *
get_frame(BasePWidget *basep)
{
	if (GTK_WIDGET_VISIBLE (basep->frame)) {
		return basep->frame;
	} else {
		return basep->innerebox;
	}
}

static void
calculate_overlay_geometry (PanelWidget *panel, GtkWidget *parent,
			    GtkWidget *applet, int *x, int *y, int *w, int *h)
{
	*x = applet->allocation.x;
	*y = applet->allocation.y;
	*w = applet->allocation.width;
	*h = applet->allocation.height;

	translate_to (GTK_WIDGET(panel), parent, x, y);

	/* when not shown, things are somewhat weird, and we try to put the
	 * window completely off as it can't be clickable anyway */
	/* XXX: These window thingies should really be unmapped in hidden
	 * case, or something like that, this is ugly, but who gives a fuck,
	 * this is all going to be rewritten soon (famous last words?) */
	if(BASEP_IS_WIDGET(parent) &&
	   BASEP_WIDGET(parent)->state != BASEP_SHOWN &&
	   BASEP_WIDGET(parent)->state != BASEP_AUTO_HIDDEN) {
		*x = parent->requisition.width + 1;
		*y = parent->requisition.height + 1;
		return;
	}

	if(panel->orient == GTK_ORIENTATION_HORIZONTAL) {
		if (applet->allocation.x > panel->size) {
			*x = parent->requisition.width + 1;
			*y = parent->requisition.height + 1;
			return;
		}

		*y = 0;
		/* we use the requisition, since allocation might have not
		   yet happened if we are inside the allocation, anyway
		   they are the same for basep */
		*h = parent->requisition.height;

		if ((*w + applet->allocation.x) > panel->size) {
			*w = panel->size - applet->allocation.x;
		}

		if ( ! BASEP_IS_WIDGET(parent)) {
			/*don't do the edge flushing on foobar*/
			return;
		}

		/* if on the edge (only if padding is 0)
		   then make the thing flush with the innerebox or frame
		   of the basep */
		if(applet->allocation.x == 0) {
			GtkWidget *frame = get_frame(BASEP_WIDGET(parent));
			*w += (*x - frame->allocation.x);
			*x = frame->allocation.x;
		} else if(applet->allocation.x + *w == panel->size) {
			GtkWidget *frame = get_frame(BASEP_WIDGET(parent));
			*w = frame->allocation.width + frame->allocation.x - *x;
		}
	} else {
		if (applet->allocation.y > panel->size) {
			*x = parent->requisition.width + 1;
			*y = parent->requisition.height + 1;
			return;
		}

		*x = 0;
		*w = parent->requisition.width;

		if ((*h + applet->allocation.y) > panel->size) {
			*h = panel->size - applet->allocation.y;
		}

		if ( ! BASEP_IS_WIDGET(parent)) {
			/*don't do the edge flushing on foobar*/
			return;
		}

		/* if on the edge (only if padding is 0)
		   then make the thing flush with the innerbox of frame
		   of the basep */
		if(applet->allocation.y == 0) {
			GtkWidget *frame = get_frame(BASEP_WIDGET(parent));
			*h += (*y - frame->allocation.y);
			*y = frame->allocation.y;
		} else if(applet->allocation.y + *h == panel->size) {
			GtkWidget *frame = get_frame(BASEP_WIDGET(parent));
			*h = frame->allocation.height + frame->allocation.y - *y;
		}
	}
}

static void
button_widget_realize(GtkWidget *widget)
{
	GdkWindowAttr attributes;
	gint attributes_mask;
	GtkButton *button;
	PanelWidget *panel;
	GtkWidget *parent;
	int x,y,w,h;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (BUTTON_IS_WIDGET (widget));

	panel = PANEL_WIDGET(widget->parent);
	parent = panel->panel_parent;

	calculate_overlay_geometry(panel, parent, widget, &x, &y, &w, &h);

	button = GTK_BUTTON (widget);

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

	attributes.window_type = GDK_WINDOW_CHILD;
	attributes.x = x;
	attributes.y = y;
	attributes.width = w;
	attributes.height = h;
	attributes.wclass = GDK_INPUT_ONLY;
	attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
				 GDK_BUTTON_RELEASE_MASK |
				 GDK_POINTER_MOTION_MASK |
				 GDK_POINTER_MOTION_HINT_MASK |
				 GDK_KEY_PRESS_MASK |
				 GDK_ENTER_NOTIFY_MASK |
				 GDK_LEAVE_NOTIFY_MASK);
	attributes_mask = GDK_WA_X | GDK_WA_Y;

	widget->window = gtk_widget_get_parent_window(widget);
	g_object_ref (G_OBJECT (widget->window));
      
	button->event_window = gdk_window_new (parent->window,
					       &attributes,
					       attributes_mask);
	gdk_window_set_user_data (button->event_window, widget);

	widget->style = gtk_style_attach (widget->style, widget->window);
}

static void
button_widget_destroy (GtkObject *obj)
{
	ButtonWidget *button = BUTTON_WIDGET(obj);

	if (button->pressed_timeout != 0)
		g_source_remove (button->pressed_timeout);
	button->pressed_timeout = 0;

	if (button->pixbuf)
		g_object_unref (G_OBJECT (button->pixbuf));
	button->pixbuf = NULL;
	
	if (button->scaled)
		g_object_unref (G_OBJECT (button->scaled));
	button->scaled = NULL;
	
	if (button->scaled_hc)
		g_object_unref (G_OBJECT (button->scaled_hc));
	button->scaled_hc = NULL;
	
	g_free (button->filename);
	button->filename = NULL;
	
	g_free (button->text);
	button->text = NULL;

	if (GTK_OBJECT_CLASS (button_widget_parent_class)->destroy)
		GTK_OBJECT_CLASS (button_widget_parent_class)->destroy (obj);
}

static GdkPixbuf *
loadup_file(const char *file)
{
	GdkPixbuf *pb = NULL;
	
	if (string_empty (file))
		return NULL;

	if ( ! g_path_is_absolute (file)) {
		char *full = gnome_desktop_item_find_icon (file,
							   48 /* desired size */,
							   0 /* flags */);

		if (full != NULL) {
			pb = gdk_pixbuf_new_from_file (full, NULL);
			g_free (full);
		}
	} else {
		pb = gdk_pixbuf_new_from_file (file, NULL);
	}
	return pb;
}

/* colorshift a pixbuf */
static void
do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
{
	gint i, j;
	gint width, height, has_alpha, srcrowstride, destrowstride;
	guchar *target_pixels;
	guchar *original_pixels;
	guchar *pixsrc;
	guchar *pixdest;
	int val;
	guchar r,g,b;

	has_alpha = gdk_pixbuf_get_has_alpha (src);
	width = gdk_pixbuf_get_width (src);
	height = gdk_pixbuf_get_height (src);
	srcrowstride = gdk_pixbuf_get_rowstride (src);
	destrowstride = gdk_pixbuf_get_rowstride (dest);
	target_pixels = gdk_pixbuf_get_pixels (dest);
	original_pixels = gdk_pixbuf_get_pixels (src);

	for (i = 0; i < height; i++) {
		pixdest = target_pixels + i*destrowstride;
		pixsrc = original_pixels + i*srcrowstride;
		for (j = 0; j < width; j++) {
			r = *(pixsrc++);
			g = *(pixsrc++);
			b = *(pixsrc++);
			val = r + shift;
			*(pixdest++) = CLAMP(val, 0, 255);
			val = g + shift;
			*(pixdest++) = CLAMP(val, 0, 255);
			val = b + shift;
			*(pixdest++) = CLAMP(val, 0, 255);
			if (has_alpha)
				*(pixdest++) = *(pixsrc++);
		}
	}
}



#define SCALE(x) (((x)*size)/48.0)

static void
draw_arrow(GdkPoint *points, PanelOrient orient, int size)
{
	switch(orient) {
	case PANEL_ORIENT_UP:
		points[0].x = SCALE(48-12);
		points[0].y = SCALE(10);
		points[1].x = SCALE(48-4);
		points[1].y = SCALE(10);
		points[2].x = SCALE(48-8);
		points[2].y = SCALE(3);
		break;
	case PANEL_ORIENT_DOWN:
		points[0].x = SCALE(4);
		points[0].y = SCALE(48 - 10);
		points[1].x = SCALE(12);
		points[1].y = SCALE(48 - 10);
		points[2].x = SCALE(8);
		points[2].y = SCALE(48 - 3);
		break;
	case PANEL_ORIENT_LEFT:
		points[0].x = SCALE(10);
		points[0].y = SCALE(4);
		points[1].x = SCALE(10);
		points[1].y = SCALE(12);
		points[2].x = SCALE(3);
		points[2].y = SCALE(8);
		break;
	case PANEL_ORIENT_RIGHT:
		points[0].x = SCALE(48 - 10);
		points[0].y = SCALE(48 - 12);
		points[1].x = SCALE(48 - 10);
		points[1].y = SCALE(48 - 4);
		points[2].x = SCALE(48 - 3);
		points[2].y = SCALE(48 - 8);
		break;
	}
}

void
button_widget_set_dnd_highlight(ButtonWidget *button, gboolean highlight)
{
	g_return_if_fail (button != NULL);
	g_return_if_fail (BUTTON_IS_WIDGET (button));

	if(button->dnd_highlight != highlight) {
		button->dnd_highlight = highlight;
		gtk_widget_queue_draw (GTK_WIDGET (button));
	}
}

static gboolean
button_widget_expose (GtkWidget         *widget,
		      GdkEventExpose    *event)
{
	ButtonWidget *button_widget;
	GtkButton *button;
	GdkRectangle area, image_bound;
	int off, size;
	int x, y, w, h;
	GdkPixbuf *pb = NULL;
  
	g_return_val_if_fail (BUTTON_IS_WIDGET (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	button_widget = BUTTON_WIDGET (widget);
	button = GTK_BUTTON (widget);
	
	if (!GTK_WIDGET_VISIBLE (widget) || !GTK_WIDGET_MAPPED (widget)) {
		return FALSE;
	}

	size = widget->allocation.height;
	/* offset for pressed buttons */
	off = (button->in_button && button->button_down) ?
		SCALE(BUTTON_WIDGET_DISPLACEMENT) : 0;
	
	if (!global_config.highlight_when_over || !button->in_button) {
		pb = button_widget->scaled;
	} else {
		pb = button_widget->scaled_hc;
	}
	
	w = gdk_pixbuf_get_width (pb);
	h = gdk_pixbuf_get_height (pb);
	x = widget->allocation.x + off + (widget->allocation.width - w)/2;
	y = widget->allocation.y + off + (widget->allocation.height - h)/2;
	
	image_bound.x = x;
	image_bound.y = y;      
	image_bound.width = w;
	image_bound.height = h;
	
	area = event->area;
	
	if (gdk_rectangle_intersect (&area, &widget->allocation, &area) &&
	    gdk_rectangle_intersect (&image_bound, &area, &image_bound))  {
		gdk_pixbuf_render_to_drawable_alpha (pb,
						     widget->window,
						     image_bound.x - x, image_bound.y - y,
						     image_bound.x, image_bound.y,
						     image_bound.width, image_bound.height,
						     GDK_PIXBUF_ALPHA_FULL,
						     128,
						     GDK_RGB_DITHER_NORMAL,
						     0, 0);
	}
	
	if(button_widget->arrow) {
		int i;
		GdkPoint points[3];
		draw_arrow (points, button_widget->orient, widget->allocation.height);
		for (i = 0; i < 3; i++) {
			points[i].x += off + widget->allocation.x;
			points[i].y += off + widget->allocation.y;
		}
		gdk_draw_polygon (widget->window, widget->style->white_gc, TRUE, points, 3);
		gdk_draw_polygon (widget->window, widget->style->black_gc, FALSE, points, 3);
	}

	if (button_widget->dnd_highlight) {
		gdk_draw_rectangle(widget->window, widget->style->black_gc, FALSE,
				   widget->allocation.x, widget->allocation.y,
				   widget->allocation.width - 1,
				   widget->allocation.height - 1);
	}

	if (GTK_WIDGET_HAS_FOCUS (widget)) {
		gtk_paint_focus (widget->style, widget->window,
				 GTK_WIDGET_STATE (widget),
				 &event->area, widget, "button",
				 widget->allocation.x + 1,
				 widget->allocation.y + 1,
				 widget->allocation.width - 3,
				 widget->allocation.height - 3);
	}
	
	return FALSE;
}

static GdkPixbuf *
make_hc_pixbuf(GdkPixbuf *pb)
{
	GdkPixbuf *new;
	
	if(!pb)
		return NULL;
#ifdef BUTTON_WIDGET_DEBUG
	printf ("Creating highlight pixbuf\n");
#endif
	new = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pb),
			      gdk_pixbuf_get_has_alpha (pb),
			      gdk_pixbuf_get_bits_per_sample (pb),
			      gdk_pixbuf_get_width (pb),
			      gdk_pixbuf_get_height (pb));
	do_colorshift (new, pb, 30);

	return new;
}

static void
button_widget_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
	PanelWidget *panel = PANEL_WIDGET(widget->parent);
	requisition->width = requisition->height = panel->sz;
}

static void
button_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
	ButtonWidget *button_widget;
	GtkButton *button;
	int w, h;
	double scale, scale_x, scale_y;

	g_return_if_fail (BUTTON_IS_WIDGET (widget));

	button_widget = BUTTON_WIDGET (widget);
	button = GTK_BUTTON (widget);

	widget->allocation = *allocation;

	w = gdk_pixbuf_get_width (button_widget->pixbuf);
	h = gdk_pixbuf_get_height (button_widget->pixbuf);

	scale_x = (double) allocation->width / w;
	scale_y = (double) allocation->height / h;

	scale = MIN (scale_x, scale_y);

	w *= scale;
	h *= scale;

	if (button_widget->scaled) {
		g_object_unref (button_widget->scaled);
	}

	/* TODO: Don't scale if already the same size */
	button_widget->scaled = gdk_pixbuf_scale_simple (button_widget->pixbuf,
							 w, h, GDK_INTERP_BILINEAR);
	if (button_widget->scaled_hc) {
		g_object_unref (button_widget->scaled_hc);
	}
	
	button_widget->scaled_hc = make_hc_pixbuf (button_widget->scaled);
	
	if (GTK_WIDGET_REALIZED (widget)) {
		PanelWidget *panel;
		int x,y,w,h;

		panel = PANEL_WIDGET(widget->parent);

		calculate_overlay_geometry (panel, panel->panel_parent,
					    widget, &x, &y, &w, &h);
		gdk_window_move_resize (button->event_window, x, y, w, h);
	}
}

static void
button_widget_instance_init (ButtonWidget *button)
{
	button->pixbuf = NULL;
	
	button->arrow = 0;
	button->orient = PANEL_ORIENT_UP;
	
	button->ignore_leave = FALSE;
	button->dnd_highlight = FALSE;

	button->pressed_timeout = 0;
}

static gboolean
pressed_timeout_func(gpointer data)
{
	ButtonWidget *button;

	g_return_val_if_fail (BUTTON_IS_WIDGET (data), FALSE);

	button = BUTTON_WIDGET (data);

	button->pressed_timeout = 0;

	return FALSE;
}

static gboolean
button_widget_button_press (GtkWidget *widget, GdkEventButton *event)
{
 	ButtonWidget *button;

	g_return_val_if_fail (BUTTON_IS_WIDGET (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	button = BUTTON_WIDGET (widget);

	if (button->pressed_timeout)
		return TRUE;

	return GTK_WIDGET_CLASS (button_widget_parent_class)->button_press_event (widget, event);
}

static gboolean
button_widget_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
{
	g_return_val_if_fail (BUTTON_IS_WIDGET (widget), FALSE);

	GTK_WIDGET_CLASS (button_widget_parent_class)->enter_notify_event (widget, event);
	if (GTK_BUTTON (widget)->in_button)
		gtk_widget_queue_draw (widget);

	return FALSE;
}

static gboolean
button_widget_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
{
	GtkWidget *event_widget;
	ButtonWidget *button_widget;
	GtkButton *button;

	g_return_val_if_fail (BUTTON_IS_WIDGET (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	event_widget = gtk_get_event_widget ((GdkEvent*) event);

	button_widget = BUTTON_WIDGET (widget);
	button = GTK_BUTTON (widget);

	if ((event_widget == widget) &&
	    (event->detail != GDK_NOTIFY_INFERIOR) &&
	    (!button_widget->ignore_leave)) {
		button->in_button = FALSE;
		if (global_config.highlight_when_over) {
			gtk_widget_queue_draw (widget);
		}
	}

	return FALSE;
}

static void
button_widget_button_pressed (GtkButton *button)
{
	ButtonWidget *button_widget;

	g_return_if_fail (BUTTON_IS_WIDGET (button));

	button_widget_parent_class->pressed (button);

	button_widget = BUTTON_WIDGET (button);
	button_widget->pressed_timeout =
		g_timeout_add (400, pressed_timeout_func, button_widget);
        gtk_widget_queue_draw (GTK_WIDGET (button));
}

static void
button_widget_button_released (GtkButton *button)
{
	g_return_if_fail (BUTTON_IS_WIDGET (button));

	button_widget_parent_class->released (button);
        gtk_widget_queue_draw (GTK_WIDGET (button));
}

GtkWidget*
button_widget_new(const char *filename,
		  int size,
		  gboolean arrow,
		  PanelOrient orient,
		  const char *text)
{
	ButtonWidget *button;

	button = BUTTON_WIDGET (g_object_new (button_widget_get_type (), NULL));
	
	button->pixbuf = loadup_file (filename);
	button->scaled = NULL;
	button->scaled_hc = NULL;
	button->filename = g_strdup (filename);
	button->size = size;
	button->arrow = arrow ? 1 : 0;
	button->orient = orient;
	button->text = text ? g_strdup (text) : NULL;

	return GTK_WIDGET(button);
}

gboolean
button_widget_set_pixmap(ButtonWidget *button, const char *pixmap, int size)
{
	g_return_val_if_fail(BUTTON_IS_WIDGET(button), FALSE);

	if (size < 0)
		size = PANEL_WIDGET(GTK_WIDGET(button)->parent)->sz;
	
	if (button->pixbuf != NULL)
		g_object_unref (G_OBJECT (button->pixbuf));

	button->pixbuf = loadup_file(pixmap);

	
	g_free(button->filename);
	button->filename = g_strdup(pixmap);
	button->size = size;

	/* TODO: Is this guaranteed to trigger the size_allocate call to
	   update scale + scale_hc???? */
	gtk_widget_queue_resize (GTK_WIDGET (button));
	gtk_widget_queue_draw (GTK_WIDGET (button));

	if (button->pixbuf == NULL)
		return FALSE;
	
	return TRUE;
}

void
button_widget_set_text(ButtonWidget *button, const char *text)
{
	g_return_if_fail(BUTTON_IS_WIDGET(button));

	g_free(button->text);
	button->text = text?g_strdup(text):NULL;

	gtk_widget_queue_draw (GTK_WIDGET (button));
}

void
button_widget_set_params(ButtonWidget *button,
			 gboolean arrow,
			 PanelOrient orient)
{
	g_return_if_fail(BUTTON_IS_WIDGET(button));

	button->arrow = arrow ? 1 : 0;
	button->orient = orient;

	gtk_widget_queue_draw (GTK_WIDGET (button));
}
