/*
 * Copyright (C) 2010 Carlos Garcia Campos  <carlosgc@gnome.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include <gtk/gtk.h>
#include <cairo.h>

#include "selections.h"

typedef struct {
	PopplerDocument      *doc;

	/* Properties */
	gint                  page_index;
	gdouble               scale;

	GtkWidget            *swindow;
	GtkWidget            *darea;
	GtkWidget            *fg_color_button;
	GtkWidget            *bg_color_button;
	GtkWidget            *copy_button;

	PopplerPage          *page;
	cairo_surface_t      *surface;

	GdkPoint              start;
	GdkPoint              stop;
	PopplerRectangle      doc_area;
	cairo_surface_t      *selection_surface;
	PopplerSelectionStyle style;
	PopplerColor          glyph_color;
	PopplerColor          background_color;
	guint                 selections_idle;
	cairo_region_t       *selection_region;
	cairo_region_t       *selected_region;
	GdkCursorType         cursor;
	gchar                *selected_text;
} PgdSelectionsDemo;

static void
pgd_selections_clear_selections (PgdSelectionsDemo *demo)
{
	demo->start.x = -1;

	if (demo->selection_surface) {
		cairo_surface_destroy (demo->selection_surface);
		demo->selection_surface = NULL;
	}

	if (demo->selection_region) {
		cairo_region_destroy (demo->selection_region);
		demo->selection_region = NULL;
	}

	if (demo->selected_text) {
		g_free (demo->selected_text);
		demo->selected_text = NULL;
	}

	if (demo->selected_region) {
		cairo_region_destroy (demo->selected_region);
		demo->selected_region = NULL;
	}
}

static void
pgd_selections_free (PgdSelectionsDemo *demo)
{
	if (!demo)
		return;

	if (demo->selections_idle > 0) {
		g_source_remove (demo->selections_idle);
		demo->selections_idle = 0;
	}

	if (demo->doc) {
		g_object_unref (demo->doc);
		demo->doc = NULL;
	}

	if (demo->page) {
		g_object_unref (demo->page);
		demo->page = NULL;
	}

	if (demo->surface) {
		cairo_surface_destroy (demo->surface);
		demo->surface = NULL;
	}

	pgd_selections_clear_selections (demo);

	g_free (demo);
}

static void
pgd_selections_update_selection_region (PgdSelectionsDemo *demo)
{
	PopplerRectangle area = { 0, 0, 0, 0 };

	if (demo->selection_region)
		cairo_region_destroy (demo->selection_region);

	poppler_page_get_size (demo->page, &area.x2, &area.y2);
	demo->selection_region = poppler_page_get_selected_region (demo->page,
                                                                   1.0,
                                                                   POPPLER_SELECTION_GLYPH,
                                                                   &area);
}

static void
pgd_selections_update_selected_text (PgdSelectionsDemo *demo)
{
	gchar *text;

	if (demo->selected_region)
		cairo_region_destroy (demo->selected_region);
	demo->selected_region = poppler_page_get_selected_region (demo->page,
                                                                  1.0,
                                                                  demo->style,
                                                                  &demo->doc_area);
	if (demo->selected_text)
		g_free (demo->selected_text);
	demo->selected_text = NULL;

	text = poppler_page_get_selected_text (demo->page,
					       demo->style,
					       &demo->doc_area);
	if (text) {
		demo->selected_text = g_utf8_normalize (text, -1, G_NORMALIZE_NFKC);
		g_free (text);
		gtk_widget_set_sensitive(demo->copy_button, TRUE);
	}
}

static void
pgd_selections_update_cursor (PgdSelectionsDemo *demo,
			      GdkCursorType      cursor_type)
{
	GdkWindow *window = gtk_widget_get_window (demo->darea);
	GdkCursor *cursor = NULL;

	if (cursor_type == demo->cursor)
		return;

	if (cursor_type != GDK_LAST_CURSOR) {
		cursor = gdk_cursor_new_for_display (gtk_widget_get_display (demo->darea),
						     cursor_type);
	}

	demo->cursor = cursor_type;

	gdk_window_set_cursor (window, cursor);
	gdk_display_flush (gtk_widget_get_display (demo->darea));
	if (cursor)
		g_object_unref (cursor);
}

static gboolean
pgd_selections_render_selections (PgdSelectionsDemo *demo)
{
	PopplerRectangle doc_area;
	gdouble page_width, page_height;
	cairo_t *cr;

	if (!demo->page || demo->start.x == -1) {
		demo->selections_idle = 0;

		return FALSE;
	}

	poppler_page_get_size (demo->page, &page_width, &page_height);
	page_width *= demo->scale;
	page_height *= demo->scale;

	doc_area.x1 = demo->start.x / demo->scale;
	doc_area.y1 = demo->start.y / demo->scale;
	doc_area.x2 = demo->stop.x / demo->scale;
	doc_area.y2 = demo->stop.y / demo->scale;

	if (demo->selection_surface)
		cairo_surface_destroy (demo->selection_surface);
	demo->selection_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
							      page_width, page_height);
	cr = cairo_create (demo->selection_surface);
	if (demo->scale != 1.0)
		cairo_scale (cr, demo->scale, demo->scale);
	poppler_page_render_selection (demo->page, cr,
				       &doc_area, &demo->doc_area,
				       demo->style,
				       &demo->glyph_color,
				       &demo->background_color);
	cairo_destroy (cr);

	demo->doc_area = doc_area;
	gtk_widget_queue_draw (demo->darea);

	demo->selections_idle = 0;

	return FALSE;
}

static gboolean
pgd_selections_drawing_area_draw (GtkWidget         *area,
                                  cairo_t           *cr,
                                  PgdSelectionsDemo *demo)
{
	if (!demo->surface)
		return FALSE;

	cairo_save (cr);
	cairo_set_source_surface (cr, demo->surface, 0, 0);
	cairo_paint (cr);
	cairo_restore (cr);

	if (demo->selection_surface) {
		cairo_set_source_surface (cr, demo->selection_surface, 0, 0);
		cairo_paint (cr);
	}

	return TRUE;
}

static gboolean
pgd_selections_drawing_area_button_press (GtkWidget         *area,
					  GdkEventButton    *event,
					  PgdSelectionsDemo *demo)
{
	if (!demo->page)
		return FALSE;

	if (event->button != 1)
		return FALSE;

	demo->start.x = event->x;
	demo->start.y = event->y;
	demo->stop = demo->start;

	switch (event->type) {
	case GDK_2BUTTON_PRESS:
		demo->style = POPPLER_SELECTION_WORD;
		break;
	case GDK_3BUTTON_PRESS:
		demo->style = POPPLER_SELECTION_LINE;
		break;
	default:
		demo->style = POPPLER_SELECTION_GLYPH;
	}

	pgd_selections_render_selections (demo);

	return TRUE;
}

static gboolean
pgd_selections_drawing_area_motion_notify (GtkWidget         *area,
					   GdkEventMotion    *event,
					   PgdSelectionsDemo *demo)
{
	if (!demo->page)
		return FALSE;

	if (demo->start.x != -1) {
		demo->stop.x = event->x;
		demo->stop.y = event->y;
		if (demo->selections_idle == 0) {
			demo->selections_idle =
				g_idle_add ((GSourceFunc)pgd_selections_render_selections,
					    demo);
		}
	} else {
		gboolean over_text;

		over_text = cairo_region_contains_point (demo->selection_region,
                                                         event->x / demo->scale,
                                                         event->y / demo->scale);
		pgd_selections_update_cursor (demo, over_text ? GDK_XTERM : GDK_LAST_CURSOR);
	}

	return TRUE;
}

static gboolean
pgd_selections_drawing_area_button_release (GtkWidget         *area,
					    GdkEventButton    *event,
					    PgdSelectionsDemo *demo)
{
	if (!demo->page)
		return FALSE;

	if (event->button != 1)
		return FALSE;

	if (demo->start.x != -1)
		pgd_selections_update_selected_text (demo);

	demo->start.x = -1;

	if (demo->selections_idle > 0) {
		g_source_remove (demo->selections_idle);
		demo->selections_idle = 0;
	}

	return TRUE;
}

static void
pgd_selections_drawing_area_realize (GtkWidget         *area,
				     PgdSelectionsDemo *demo)
{
	GtkStyleContext *style_context = gtk_widget_get_style_context (area);
        GdkRGBA rgba;

	gtk_widget_add_events (area,
			       GDK_POINTER_MOTION_HINT_MASK |
			       GDK_BUTTON1_MOTION_MASK |
			       GDK_BUTTON_PRESS_MASK |
			       GDK_BUTTON_RELEASE_MASK);
	g_object_set (area, "has-tooltip", TRUE, NULL);

        gtk_style_context_get_color (style_context, GTK_STATE_FLAG_SELECTED, &rgba);
	gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (demo->fg_color_button), &rgba);
	gtk_style_context_get (style_context, GTK_STATE_FLAG_SELECTED, "background-color", &rgba, NULL);
	gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (demo->bg_color_button), &rgba);
}

static gboolean
pgd_selections_drawing_area_query_tooltip (GtkWidget         *area,
					   gint               x,
					   gint               y,
					   gboolean           keyboard_mode,
					   GtkTooltip        *tooltip,
					   PgdSelectionsDemo *demo)
{
	gboolean over_selection;

	if (!demo->selected_text)
		return FALSE;

	over_selection = cairo_region_contains_point (demo->selected_region,
                                                      x / demo->scale,
                                                      y / demo->scale);

	if (over_selection) {
		GdkRectangle selection_area;

		cairo_region_get_extents (demo->selected_region,
                                          (cairo_rectangle_int_t *)&selection_area);
		selection_area.x *= demo->scale;
		selection_area.y *= demo->scale;
		selection_area.width *= demo->scale;
		selection_area.height *= demo->scale;

		gtk_tooltip_set_text (tooltip, demo->selected_text);
		gtk_tooltip_set_tip_area (tooltip, &selection_area);

		return TRUE;
	}

	return FALSE;
}

static void
pgd_selections_render (GtkButton         *button,
		       PgdSelectionsDemo *demo)
{
	gdouble  page_width, page_height;
	cairo_t *cr;

	if (!demo->page)
		demo->page = poppler_document_get_page (demo->doc, demo->page_index);

	if (!demo->page)
		return;

	pgd_selections_clear_selections (demo);
	pgd_selections_update_selection_region (demo);
        gtk_widget_set_sensitive (demo->copy_button, FALSE);

	if (demo->surface)
		cairo_surface_destroy (demo->surface);
	demo->surface = NULL;

	poppler_page_get_size (demo->page, &page_width, &page_height);
	page_width *= demo->scale;
	page_height *= demo->scale;

	demo->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
						    page_width, page_height);
	cr = cairo_create (demo->surface);

	cairo_save (cr);

	if (demo->scale != 1.0)
		cairo_scale (cr, demo->scale, demo->scale);

	poppler_page_render (demo->page, cr);
	cairo_restore (cr);

	cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER);
	cairo_set_source_rgb (cr, 1., 1., 1.);
	cairo_paint (cr);

	cairo_destroy (cr);

	gtk_widget_set_size_request (demo->darea, page_width, page_height);
	gtk_widget_queue_draw (demo->darea);
}

static void
pgd_selections_copy (GtkButton         *button,
		     PgdSelectionsDemo *demo)
{
	GtkClipboard *clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(),
								GDK_SELECTION_CLIPBOARD);
	gtk_clipboard_set_text (clipboard, demo->selected_text, -1);
}

static void
pgd_selections_page_selector_value_changed (GtkSpinButton     *spinbutton,
					    PgdSelectionsDemo *demo)
{
	demo->page_index = (gint)gtk_spin_button_get_value (spinbutton) - 1;
	if (demo->page)
		g_object_unref (demo->page);
	demo->page = NULL;
}

static void
pgd_selections_scale_selector_value_changed (GtkSpinButton     *spinbutton,
					     PgdSelectionsDemo *demo)
{
	demo->scale = gtk_spin_button_get_value (spinbutton);
}

static void
pgd_selections_fg_color_changed (GtkColorButton    *button,
				 GParamSpec        *pspec,
				 PgdSelectionsDemo *demo)
{
	GdkRGBA color;

	gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
	demo->glyph_color.red = CLAMP ((guint) (color.red * 65535), 0, 65535);
	demo->glyph_color.green = CLAMP ((guint) (color.green * 65535), 0, 65535);
	demo->glyph_color.blue = CLAMP ((guint) (color.blue * 65535), 0, 65535);
}

static void
pgd_selections_bg_color_changed (GtkColorButton    *button,
				 GParamSpec        *pspec,
				 PgdSelectionsDemo *demo)
{
	GdkRGBA color;

	gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
	demo->background_color.red = CLAMP ((guint) (color.red * 65535), 0, 65535);
	demo->background_color.green = CLAMP ((guint) (color.green * 65535), 0, 65535);
	demo->background_color.blue = CLAMP ((guint) (color.blue * 65535), 0, 65535);
}

GtkWidget *
pgd_selections_properties_selector_create (PgdSelectionsDemo *demo)
{
	GtkWidget *hbox, *vbox;
	GtkWidget *label;
	GtkWidget *page_hbox, *page_selector;
	GtkWidget *scale_hbox, *scale_selector;
	GtkWidget *rotate_hbox, *rotate_selector;
	GtkWidget *color_hbox;
	GtkWidget *button;
	gint       n_pages;
	gchar     *str;

	n_pages = poppler_document_get_n_pages (demo->doc);

	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);

	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show (hbox);

	page_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

	label = gtk_label_new ("Page:");
	gtk_box_pack_start (GTK_BOX (page_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	page_selector = gtk_spin_button_new_with_range (1, n_pages, 1);
	g_signal_connect (G_OBJECT (page_selector), "value-changed",
			  G_CALLBACK (pgd_selections_page_selector_value_changed),
			  (gpointer)demo);
	gtk_box_pack_start (GTK_BOX (page_hbox), page_selector, TRUE, TRUE, 0);
	gtk_widget_show (page_selector);

	str = g_strdup_printf ("of %d", n_pages);
	label = gtk_label_new (str);
	gtk_box_pack_start (GTK_BOX (page_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);
	g_free (str);

	gtk_box_pack_start (GTK_BOX (hbox), page_hbox, FALSE, TRUE, 0);
	gtk_widget_show (page_hbox);

	scale_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

	label = gtk_label_new ("Scale:");
	gtk_box_pack_start (GTK_BOX (scale_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	scale_selector = gtk_spin_button_new_with_range (0, 10.0, 0.1);
	gtk_spin_button_set_value (GTK_SPIN_BUTTON (scale_selector), 1.0);
	g_signal_connect (G_OBJECT (scale_selector), "value-changed",
			  G_CALLBACK (pgd_selections_scale_selector_value_changed),
			  (gpointer)demo);
	gtk_box_pack_start (GTK_BOX (scale_hbox), scale_selector, TRUE, TRUE, 0);
	gtk_widget_show (scale_selector);

	gtk_box_pack_start (GTK_BOX (hbox), scale_hbox, FALSE, TRUE, 0);
	gtk_widget_show (scale_hbox);

	rotate_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

	label = gtk_label_new ("Rotate:");
	gtk_box_pack_start (GTK_BOX (rotate_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	rotate_selector = gtk_combo_box_text_new ();
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (rotate_selector), "0");
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (rotate_selector), "90");
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (rotate_selector), "180");
	gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (rotate_selector), "270");
	gtk_combo_box_set_active (GTK_COMBO_BOX (rotate_selector), 0);
#if 0
	g_signal_connect (G_OBJECT (rotate_selector), "changed",
			  G_CALLBACK (pgd_selections_rotate_selector_changed),
			  (gpointer)demo);
#endif
	gtk_box_pack_start (GTK_BOX (rotate_hbox), rotate_selector, TRUE, TRUE, 0);
	gtk_widget_show (rotate_selector);

	gtk_box_pack_start (GTK_BOX (hbox), rotate_hbox, FALSE, TRUE, 0);
	gtk_widget_show (rotate_hbox);

	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show (hbox);

	color_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

	label = gtk_label_new ("Foreground Color:");
	gtk_box_pack_start (GTK_BOX (color_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	demo->fg_color_button = gtk_color_button_new ();
	g_signal_connect (demo->fg_color_button, "notify::color",
			  G_CALLBACK (pgd_selections_fg_color_changed),
			  (gpointer)demo);
	gtk_box_pack_start (GTK_BOX (color_hbox), demo->fg_color_button, TRUE, TRUE, 0);
	gtk_widget_show (demo->fg_color_button);

	gtk_box_pack_start (GTK_BOX (hbox), color_hbox, FALSE, TRUE, 0);
	gtk_widget_show (color_hbox);

	color_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);

	label = gtk_label_new ("Background Color:");
	gtk_box_pack_start (GTK_BOX (color_hbox), label, TRUE, TRUE, 0);
	gtk_widget_show (label);

	demo->bg_color_button = gtk_color_button_new ();
	g_signal_connect (demo->bg_color_button, "notify::color",
			  G_CALLBACK (pgd_selections_bg_color_changed),
			  (gpointer)demo);
	gtk_box_pack_start (GTK_BOX (color_hbox), demo->bg_color_button, TRUE, TRUE, 0);
	gtk_widget_show (demo->bg_color_button);

	gtk_box_pack_start (GTK_BOX (hbox), color_hbox, FALSE, TRUE, 0);
	gtk_widget_show (color_hbox);

	demo->copy_button = gtk_button_new_with_label ("Copy");
	g_signal_connect (G_OBJECT (demo->copy_button), "clicked",
			  G_CALLBACK (pgd_selections_copy),
			  (gpointer)demo);
	gtk_box_pack_end (GTK_BOX (hbox), demo->copy_button, FALSE, TRUE, 0);
	gtk_widget_set_sensitive(demo->copy_button, FALSE);
	gtk_widget_show (demo->copy_button);

	button = gtk_button_new_with_label ("Render");
	g_signal_connect (G_OBJECT (button), "clicked",
			  G_CALLBACK (pgd_selections_render),
			  (gpointer)demo);
	gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, TRUE, 0);
	gtk_widget_show (button);

	return vbox;
}

GtkWidget *
pgd_selections_create_widget (PopplerDocument *document)
{
	PgdSelectionsDemo *demo;
	GtkWidget         *vbox, *hbox;

	demo = g_new0 (PgdSelectionsDemo, 1);

	demo->doc = g_object_ref (document);
	demo->scale = 1.0;
	demo->cursor = GDK_LAST_CURSOR;

	pgd_selections_clear_selections (demo);

	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);

	hbox = pgd_selections_properties_selector_create (demo);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 6);
	gtk_widget_show (hbox);

	demo->darea = gtk_drawing_area_new ();
	g_signal_connect (demo->darea, "realize",
			  G_CALLBACK (pgd_selections_drawing_area_realize),
			  (gpointer)demo);
	g_signal_connect (demo->darea, "draw",
			  G_CALLBACK (pgd_selections_drawing_area_draw),
			  (gpointer)demo);
	g_signal_connect (demo->darea, "button_press_event",
			  G_CALLBACK (pgd_selections_drawing_area_button_press),
			  (gpointer)demo);
	g_signal_connect (demo->darea, "motion_notify_event",
			  G_CALLBACK (pgd_selections_drawing_area_motion_notify),
			  (gpointer)demo);
	g_signal_connect (demo->darea, "button_release_event",
			  G_CALLBACK (pgd_selections_drawing_area_button_release),
			  (gpointer)demo);
	g_signal_connect (demo->darea, "query_tooltip",
			  G_CALLBACK (pgd_selections_drawing_area_query_tooltip),
			  (gpointer)demo);
	demo->swindow = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (demo->swindow),
					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
#if GTK_CHECK_VERSION(3, 7, 8)
	gtk_container_add(GTK_CONTAINER(demo->swindow), demo->darea);
#else
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (demo->swindow),
					       demo->darea);
#endif
	gtk_widget_show (demo->darea);

	gtk_box_pack_start (GTK_BOX (vbox), demo->swindow, TRUE, TRUE, 0);
	gtk_widget_show (demo->swindow);

	g_object_weak_ref (G_OBJECT (demo->swindow),
			   (GWeakNotify)pgd_selections_free,
			   (gpointer)demo);

	return vbox;
}
