/* -*- 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., 51 Franklin St, Fifth Floor,
   Boston, MA 02110-1301, 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 <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;
};

G_DEFINE_TYPE (DebugPixbufViewer, debug_pixbuf_viewer, GTK_TYPE_WIDGET)

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;

    G_OBJECT_CLASS (debug_pixbuf_viewer_parent_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);
}

#if GTK_CHECK_VERSION (3, 0, 0)
static void
debug_pixbuf_viewer_get_preferred_width (GtkWidget *widget,
                                        gint *minimum_width,
                                        gint *natural_width)
{
    GtkRequisition req;
    debug_pixbuf_viewer_size_request (widget, &req);
    *minimum_width = *natural_width = req.width;
}

static void
debug_pixbuf_viewer_get_preferred_height (GtkWidget *widget,
                                         gint *minimum_height,
                                         gint *natural_height)
{
    GtkRequisition req;
    debug_pixbuf_viewer_size_request (widget, &req);
    *minimum_height = *natural_height = req.height;
}
#endif

static int
#if GTK_CHECK_VERSION (3, 0, 0)
debug_pixbuf_viewer_draw (GtkWidget *widget, cairo_t *cr)
#else
debug_pixbuf_viewer_expose_event (GtkWidget *widget, GdkEventExpose *event)
#endif
{
    DebugPixbufViewer *viewer;
    EelIRect clipped_dirty_area;
    EelIRect dirty_area;
    EelIRect bounds;
    GtkAllocation allocation;

    g_assert (DEBUG_IS_PIXBUF_VIEWER (widget));
#if GTK_CHECK_VERSION (3, 0, 0)
    g_assert (cr != NULL);
#else
    g_assert (event != NULL);
    g_assert (event->window == gtk_widget_get_window (widget));
#endif
    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 */
#if GTK_CHECK_VERSION (3, 0, 0)
    clipped_dirty_area = eel_gdk_window_clip_dirty_area_to_screen (gtk_widget_get_window (widget),
                         dirty_area);
#else
    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);
#endif

    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,
#if GTK_CHECK_VERSION (3, 0, 0)
                                             cr,
#else
                                             event->window,
#endif
                                             clipped_bounds.x0 - bounds.x0,
                                             clipped_bounds.y0 - bounds.y0,
                                             clipped_bounds);
        }
    }

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

    return TRUE;
}

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;
#if GTK_CHECK_VERSION (3, 0, 0)
    widget_class->get_preferred_width = debug_pixbuf_viewer_get_preferred_width;
    widget_class->get_preferred_height = debug_pixbuf_viewer_get_preferred_height;
    widget_class->draw = debug_pixbuf_viewer_draw;
#else
    widget_class->size_request = debug_pixbuf_viewer_size_request;
    widget_class->expose_event = debug_pixbuf_viewer_expose_event;
#endif
}

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.
 */
#if !GTK_CHECK_VERSION (3, 0, 0)
void
eel_debug_draw_rectangle_and_cross (GdkDrawable *drawable,
                                    EelIRect rectangle,
                                    guint32 color,
                                    gboolean draw_cross)
{
    cairo_t *cr;
    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;

    cr = gdk_cairo_create (drawable);

    color_gdk.red   = ((color >> 16) & 0xff) << 8;
    color_gdk.green = ((color >>  8) & 0xff) << 8;
    color_gdk.blue  = ((color      ) & 0xff) << 8;
    gdk_cairo_set_source_color (cr, &color_gdk);
    cairo_set_line_width (cr, 1.0);

    cairo_rectangle (cr,
                     rectangle.x0 + 0.5,
                     rectangle.y0 + 0.5,
                     width,
                     height);

    if (draw_cross)
    {
        cairo_move_to (cr, rectangle.x0, rectangle.y0);
        cairo_line_to (cr, rectangle.x0 + width, rectangle.y0 + height);

        cairo_move_to (cr, rectangle.x0 + width, rectangle.y0);
        cairo_line_to (cr, rectangle.x0, rectangle.y0 + height);
    }

    cairo_stroke (cr);

    cairo_destroy (cr);
}
#endif

/**
 * 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_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);
}
