/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-

   eel-debug-drawing.c: Eel drawing debugging aids.
 
   Copyright (C) 2000 Eazel, Inc.
  
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
  
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.
  
   You should have received a copy of the GNU Library General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
  
   Author: Ramiro Estrugo <ramiro@eazel.com>
*/

#include <config.h>
#include "eel-debug-drawing.h"

#include "eel-art-gtk-extensions.h"
#include "eel-debug.h"
#include "eel-gdk-extensions.h"
#include "eel-gdk-extensions.h"
#include "eel-gdk-pixbuf-extensions.h"
#include "eel-gtk-extensions.h"
#include "eel-gtk-extensions.h"
#include "eel-gtk-macros.h"

#include <gtk/gtk.h>

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

/*
 * PixbufViewer is a very simple "private" widget that displays
 * a GdkPixbuf.  It is used by eel_debug_show_pixbuf() to
 * display a pixbuf in an in process pixbuf debugging window.
 *
 * We cant use EelImage for this because part of the reason
 * for pixbuf debugging is to debug EelImage itself.
 */

#define DEBUG_TYPE_PIXBUF_VIEWER debug_pixbuf_viewer_get_type()
#define DEBUG_PIXBUF_VIEWER(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST ((obj), DEBUG_TYPE_PIXBUF_VIEWER, DebugPixbufViewer))
#define DEBUG_IS_PIXBUF_VIEWER(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DEBUG_TYPE_PIXBUF_VIEWER))

typedef struct DebugPixbufViewer DebugPixbufViewer;
typedef struct DebugPixbufViewerClass DebugPixbufViewerClass;

static GType   debug_pixbuf_viewer_get_type   (void);
static void    debug_pixbuf_viewer_set_pixbuf (DebugPixbufViewer *viewer,
					       GdkPixbuf         *pixbuf);

struct DebugPixbufViewer
{
	GtkWidget widget;
	GdkPixbuf *pixbuf;
};

struct DebugPixbufViewerClass
{
	GtkWidgetClass parent_class;
};

/* GtkObjectClass methods */
static void debug_pixbuf_viewer_class_init (DebugPixbufViewerClass *pixbuf_viewer_class);
static void debug_pixbuf_viewer_init       (DebugPixbufViewer      *pixbuf_viewer);
static void debug_pixbuf_viewer_finalize         (GObject                *object);

/* GtkWidgetClass methods */
static void debug_pixbuf_viewer_size_request     (GtkWidget              *widget,
						  GtkRequisition         *requisition);
static int  debug_pixbuf_viewer_expose_event     (GtkWidget              *widget,
						  GdkEventExpose         *event);

EEL_CLASS_BOILERPLATE (DebugPixbufViewer, debug_pixbuf_viewer, GTK_TYPE_WIDGET)

static void
debug_pixbuf_viewer_class_init (DebugPixbufViewerClass *pixbuf_viewer_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (pixbuf_viewer_class);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (pixbuf_viewer_class);

	object_class->finalize = debug_pixbuf_viewer_finalize;
	widget_class->size_request = debug_pixbuf_viewer_size_request;
	widget_class->expose_event = debug_pixbuf_viewer_expose_event;
}

static void
debug_pixbuf_viewer_init (DebugPixbufViewer *viewer)
{
	gtk_widget_set_can_focus (GTK_WIDGET (viewer), FALSE);
	gtk_widget_set_has_window (GTK_WIDGET (viewer), FALSE);
}

static void
debug_pixbuf_viewer_finalize (GObject *object)
{
 	DebugPixbufViewer *viewer;

	viewer = DEBUG_PIXBUF_VIEWER (object);
	eel_gdk_pixbuf_unref_if_not_null (viewer->pixbuf);
	viewer->pixbuf = NULL;

	EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}

static void
debug_pixbuf_viewer_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
	DebugPixbufViewer *viewer;
	EelDimensions dimensions;

	g_assert (DEBUG_IS_PIXBUF_VIEWER (widget));
	g_assert (requisition != NULL);
	
	viewer = DEBUG_PIXBUF_VIEWER (widget);

	if (viewer->pixbuf != NULL) {
		dimensions = eel_gdk_pixbuf_get_dimensions (viewer->pixbuf);
	} else {
		dimensions = eel_dimensions_empty;
	}
	
   	requisition->width = MAX (2, dimensions.width);
   	requisition->height = MAX (2, dimensions.height);
}

static int
debug_pixbuf_viewer_expose_event (GtkWidget *widget, GdkEventExpose *event)
{
 	DebugPixbufViewer *viewer;
	EelIRect clipped_dirty_area;
	EelIRect dirty_area;
	EelIRect bounds;
	GtkAllocation allocation;

	g_assert (DEBUG_IS_PIXBUF_VIEWER (widget));
	g_assert (event != NULL);
	g_assert (event->window == gtk_widget_get_window (widget));
	g_assert (gtk_widget_get_realized (widget));
	
 	viewer = DEBUG_PIXBUF_VIEWER (widget);

	if (viewer->pixbuf == NULL) {
		return TRUE;
	}

	gtk_widget_get_allocation (widget, &allocation);
	bounds.x0 = allocation.x + (allocation.width - gdk_pixbuf_get_width (viewer->pixbuf)) / 2;
	bounds.y0 = allocation.y + (allocation.height - gdk_pixbuf_get_height (viewer->pixbuf)) / 2;
	bounds.x1 = bounds.x0 + gdk_pixbuf_get_width (viewer->pixbuf);
	bounds.y1 = bounds.y0 + gdk_pixbuf_get_height (viewer->pixbuf);
	
	/* Clip the dirty area to the screen; bail if no work to do */
	dirty_area = eel_gdk_rectangle_to_eel_irect (event->area);
	clipped_dirty_area = eel_gdk_window_clip_dirty_area_to_screen (event->window,
								       dirty_area);
	if (!eel_irect_is_empty (&clipped_dirty_area)) {
		EelIRect clipped_bounds;

		eel_irect_intersect (&clipped_bounds, &bounds, &clipped_dirty_area);

		if (!eel_irect_is_empty (&clipped_bounds)) {
			g_assert (clipped_bounds.x0 >= bounds.x0);
			g_assert (clipped_bounds.y0 >= bounds.y0);
			
			eel_gdk_pixbuf_draw_to_drawable (viewer->pixbuf,
							 event->window,
							 gtk_widget_get_style (widget)->white_gc,
							 clipped_bounds.x0 - bounds.x0,
							 clipped_bounds.y0 - bounds.y0,
							 clipped_bounds,
							 GDK_RGB_DITHER_NONE,
							 GDK_PIXBUF_ALPHA_BILEVEL,
							 EEL_STANDARD_ALPHA_THRESHHOLD);
		}
	}

	bounds.x0 -= 1;
	bounds.y0 -= 1;
	bounds.x1 += 1;
	bounds.y1 += 1;

	return TRUE;
}

static void
debug_pixbuf_viewer_set_pixbuf (DebugPixbufViewer *viewer, GdkPixbuf *pixbuf)
{
	g_assert (DEBUG_IS_PIXBUF_VIEWER (viewer));
	
	if (pixbuf != viewer->pixbuf) {
		eel_gdk_pixbuf_unref_if_not_null (viewer->pixbuf);
		eel_gdk_pixbuf_ref_if_not_null (pixbuf);
		viewer->pixbuf = pixbuf;
		gtk_widget_queue_resize (GTK_WIDGET (viewer));
	}
}

/**
 * eel_debug_draw_rectangle_and_cross:
 * @rectangle: Rectangle bounding the rectangle.
 * @color: Color to use for the rectangle and cross.
 *
 * Draw a rectangle and cross.  Useful for debugging exposure events.
 */
void
eel_debug_draw_rectangle_and_cross (GdkDrawable *drawable,
				    EelIRect rectangle,
				    guint32 color,
				    gboolean draw_cross)
{
	GdkGC *gc;
	GdkColor color_gdk = { 0 };

	int width;
	int height;

	g_return_if_fail (drawable != NULL);
	g_return_if_fail (!eel_irect_is_empty (&rectangle));

	width = rectangle.x1 - rectangle.x0;
	height = rectangle.y1 - rectangle.y0;

 	gc = gdk_gc_new (drawable);
 	gdk_gc_set_function (gc, GDK_COPY);
	
	color_gdk.red   = ((color >> 16) & 0xff) << 8;
	color_gdk.green = ((color >>  8) & 0xff) << 8;
	color_gdk.blue  = ((color      ) & 0xff) << 8;
	gdk_colormap_alloc_color (
		gdk_drawable_get_colormap (drawable),
		&color_gdk, FALSE, FALSE);
	gdk_gc_set_rgb_fg_color (gc, &color_gdk);
	
	gdk_draw_rectangle (drawable,
			    gc,
			    FALSE,
			    rectangle.x0,
			    rectangle.y0,
			    width - 1,
			    height - 1);

	if (draw_cross) {
		gdk_draw_line (drawable,
			       gc,
			       rectangle.x0,
			       rectangle.y0,
			       rectangle.x0 + width - 1,
			       rectangle.y0 + height - 1);
		
		gdk_draw_line (drawable,
			       gc,
			       rectangle.x0 + width - 1,
			       rectangle.y0,
			       rectangle.x0,
			       rectangle.y0 + height - 1);
	}

	g_object_unref (gc);
}

/**
 * eel_debug_show_pixbuf_in_external_viewer:
 * @pixbuf: Pixbuf to show.
 * @viewer_name: Viewer name.
 *
 * Show the given pixbuf in an external out of process viewer.
 * This is very useful for debugging pixbuf stuff.
 *
 * Perhaps this function should be #ifdef DEBUG or something like that.
 */
void
eel_debug_show_pixbuf_in_external_viewer (const GdkPixbuf *pixbuf,
					  const char *viewer_name)
{
	char *command;
	char *file_name;
	gboolean save_result;
	int ignore;
	int fd;

	g_return_if_fail (pixbuf != NULL);
	g_return_if_fail (viewer_name != NULL);

	file_name = g_strdup ("/tmp/eel-debug-png-file-XXXXXX");

	fd = g_mkstemp (file_name); 
	if (fd == -1) {
		g_free (file_name);
		file_name = g_strdup_printf ("/tmp/isis-debug-png-file-%d", getpid ());
	} else {
		close (fd);
	}

	save_result = eel_gdk_pixbuf_save_to_file (pixbuf, file_name);

	if (save_result == FALSE) {
		g_warning ("Failed to save '%s'", file_name);
		g_free (file_name);
		return;
	}
	
	command = g_strdup_printf ("%s %s", viewer_name, file_name);

	ignore = system (command);
	g_free (command);
	remove (file_name);
	g_free (file_name);
}

static GtkWidget *debug_window = NULL;
static GtkWidget *debug_image = NULL;

static void
debug_delete_event (GtkWidget *widget, GdkEvent *event, gpointer callback_data)
{
	gtk_widget_hide (widget);
}

static void
destroy_debug_window (void)
{
	if (debug_window != NULL) {
		gtk_widget_destroy (debug_window);
		debug_window = NULL;
	}
}

/**
 * eel_debug_show_pixbuf_in:
 * @pixbuf: Pixbuf to show.
 *
 * Show the given pixbuf in an in process window.
 */
void
eel_debug_show_pixbuf (GdkPixbuf *pixbuf)
{
	if (debug_window == NULL) {
		GtkWidget *vbox;
		
		debug_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
		vbox = gtk_vbox_new (FALSE, 0);
		gtk_container_add (GTK_CONTAINER (debug_window), vbox);
		gtk_window_set_title (GTK_WINDOW (debug_window), "Pixbuf debugging");
		gtk_window_set_resizable (GTK_WINDOW (debug_window), TRUE);
		gtk_container_set_border_width (GTK_CONTAINER (debug_window), 10);
		g_signal_connect (debug_window, "delete_event", G_CALLBACK (debug_delete_event), NULL);
		
		debug_image = gtk_widget_new (debug_pixbuf_viewer_get_type (), NULL);
		
		gtk_box_pack_start (GTK_BOX (vbox), debug_image, TRUE, TRUE, 0);

		eel_gtk_widget_set_background_color (debug_window, "white");

		eel_debug_call_at_shutdown (destroy_debug_window);
		
		gtk_widget_show (debug_image);
		gtk_widget_show (vbox);
	}

	gtk_widget_show (debug_window);
	debug_pixbuf_viewer_set_pixbuf (DEBUG_PIXBUF_VIEWER (debug_image), pixbuf);

	gdk_window_clear_area_e (gtk_widget_get_window (debug_window), 0, 0, -1, -1);
}

void
eel_debug_pixbuf_draw_point (GdkPixbuf *pixbuf,
			     int x,
			     int y,
			     guint32 color,
			     int opacity)
{
	EelDimensions dimensions;
	guchar *pixels;
	gboolean has_alpha;
	guint pixel_offset;
	guint rowstride;
	guchar red;
	guchar green;
	guchar blue;
	guchar alpha;
	guchar *offset;

	g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf));
	g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT);
	g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE);

	dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf);

	g_return_if_fail (x >= 0 && x < dimensions.width);
	g_return_if_fail (y >= 0 && y < dimensions.height);

	pixels = gdk_pixbuf_get_pixels (pixbuf);
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
	pixel_offset = has_alpha ? 4 : 3;

	red = EEL_RGBA_COLOR_GET_R (color);
	green = EEL_RGBA_COLOR_GET_G (color);
	blue = EEL_RGBA_COLOR_GET_B (color);
	alpha = (guchar) opacity;

	offset = pixels + y * rowstride + x * pixel_offset;

	*(offset + 0) = red;
	*(offset + 1) = green;
	*(offset + 2) = blue;
	
	if (has_alpha) {
		*(offset + 3) = alpha;
	}
}

void
eel_debug_pixbuf_draw_rectangle (GdkPixbuf *pixbuf,
				 gboolean filled,
				 int x0,
				 int y0,
				 int x1,
				 int y1,
				 guint32 color,
				 int opacity)
{
	EelDimensions dimensions;
	int x;
	int y;

	g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf));
	g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT);
	g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE);

	dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf);

	if (x0 == -1) {
		x0 = 0;
	}

	if (y0 == -1) {
		y0 = 0;
	}

	if (x1 == -1) {
		x1 = dimensions.width - 1;
	}

	if (y1 == -1) {
		y1 = dimensions.height - 1;
	}

	g_return_if_fail (x1 > x0);
	g_return_if_fail (y1 > y0);
	g_return_if_fail (x0 >= 0 && x0 < dimensions.width);
	g_return_if_fail (y0 >= 0 && y0 < dimensions.height);
	g_return_if_fail (x1 >= 0 && x1 < dimensions.width);
	g_return_if_fail (y1 >= 0 && y1 < dimensions.height);

	if (filled) {
		for (y = y0; y <= y1; y++) {
			for (x = x0; x <= x1; x++) {
				eel_debug_pixbuf_draw_point (pixbuf, x, y, color, opacity);
			}
		}
	} else {
		/* Top / Bottom */
		for (x = x0; x <= x1; x++) {
			eel_debug_pixbuf_draw_point (pixbuf, x, y0, color, opacity);
			eel_debug_pixbuf_draw_point (pixbuf, x, y1, color, opacity);
		}
		
		/* Left / Right */
		for (y = y0; y <= y1; y++) {
			eel_debug_pixbuf_draw_point (pixbuf, x0, y, color, opacity);
			eel_debug_pixbuf_draw_point (pixbuf, x1, y, color, opacity);
		}
	}
}

void
eel_debug_pixbuf_draw_rectangle_inset (GdkPixbuf *pixbuf,
				       gboolean filled,
				       int x0,
				       int y0,
				       int x1,
				       int y1,
				       guint32 color,
				       int opacity,
				       int inset)
{
	EelDimensions dimensions;
	
	g_return_if_fail (eel_gdk_pixbuf_is_valid (pixbuf));
	g_return_if_fail (opacity >= EEL_OPACITY_FULLY_TRANSPARENT);
	g_return_if_fail (opacity <= EEL_OPACITY_FULLY_OPAQUE);

	dimensions = eel_gdk_pixbuf_get_dimensions (pixbuf);

	if (x0 == -1) {
		x0 = 0;
	}

	if (y0 == -1) {
		y0 = 0;
	}

	if (x1 == -1) {
		x1 = dimensions.width - 1;
	}

	if (y1 == -1) {
		y1 = dimensions.height - 1;
	}

	x0 += inset;
	y0 += inset;
	x1 -= inset;
	y1 -= inset;

	g_return_if_fail (x1 > x0);
	g_return_if_fail (y1 > y0);

	eel_debug_pixbuf_draw_rectangle (pixbuf, filled, x0, y0, x1, y1, color, opacity);
}
