/*
 *  Copyright (C) 2002  Ricardo Fernández Pascual
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* this file is based on work of Daniel Erat for galeon 1 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "galeon-embed-autoscroller.h"
#include "gul-gobject-misc.h"
#include "gul-general.h"
#include "galeon-debug.h"

#include <gtk/gtkimage.h>
#include <gtk/gtkwindow.h>
#include <gtk/gtkmain.h>
#include <math.h>

#include <sys/time.h>
#include <unistd.h>

/**
 * Private data
 */
struct _GaleonEmbedAutoscrollerPrivate {
	GaleonEmbed *embed;
	GtkWidget *widget;
	guint start_x;
	guint start_y;
	gfloat step_x;
	gfloat step_y;
	gfloat roundoff_error_x;
	gfloat roundoff_error_y;
	gint msecs;
	guint timeout_id;
	gboolean active;
};

static GtkWidget *autoscroll_icon = NULL;

/**
 * Private functions, only availble from this file
 */
static void		galeon_embed_autoscroller_class_init	(GaleonEmbedAutoscrollerClass *klass);
static void		galeon_embed_autoscroller_init		(GaleonEmbedAutoscroller *as);
static void		galeon_embed_autoscroller_finalize_impl	(GObject *o);
static gboolean		galeon_embed_autoscroller_motion_cb	(GtkWidget *widget, GdkEventMotion *e,
								 GaleonEmbedAutoscroller *as);
static gboolean		galeon_embed_autoscroller_mouse_press_cb (GtkWidget *widget, GdkEventButton *e,
								  GaleonEmbedAutoscroller *as);
static gboolean		galeon_embed_autoscroller_key_press_cb (GtkWidget *widget, GdkEventKey *e,
								GaleonEmbedAutoscroller *as);
static gint		galeon_embed_autoscroller_timeout_cb	(gpointer data);
static void		galeon_embed_autoscroller_stop		(GaleonEmbedAutoscroller *as);

static gpointer g_object_class;

/**
 * EmbedAutoscroller object
 */

MAKE_GET_TYPE (galeon_embed_autoscroller, "GaleonEmbedAutoscroller", 
	       GaleonEmbedAutoscroller, galeon_embed_autoscroller_class_init, 
	       galeon_embed_autoscroller_init, G_TYPE_OBJECT);

static void
galeon_embed_autoscroller_class_init (GaleonEmbedAutoscrollerClass *klass)
{
	gchar *icon_filename;
	GdkPixbuf *icon_pixbuf;
	GdkPixmap *icon_pixmap;
	GdkBitmap *icon_bitmap;
	GtkWidget *icon_img;

	G_OBJECT_CLASS (klass)->finalize = galeon_embed_autoscroller_finalize_impl;
	g_object_class = g_type_class_peek_parent (klass);

	/* initialize the autoscroll icon */

	icon_filename = gul_general_user_file ("autoscroll.xpm", TRUE);
	icon_pixbuf = gdk_pixbuf_new_from_file (icon_filename, NULL);
	g_free (icon_filename);
	g_return_if_fail (icon_pixbuf);
		
	gdk_pixbuf_render_pixmap_and_mask (icon_pixbuf, &icon_pixmap,
					   &icon_bitmap, 128);
	g_object_unref (icon_pixbuf);

	/*
	  gtk_widget_push_visual (gdk_rgb_get_visual ());
	  gtk_widget_push_colormap (gdk_rgb_get_cmap ());
	*/
	icon_img = gtk_image_new_from_pixmap (icon_pixmap, icon_bitmap);
	
	autoscroll_icon = gtk_window_new (GTK_WINDOW_POPUP);
	gtk_widget_realize (autoscroll_icon);
	gtk_container_add (GTK_CONTAINER (autoscroll_icon), icon_img);
	gtk_widget_shape_combine_mask (autoscroll_icon, icon_bitmap, 0, 0);

	/*
	  gtk_widget_pop_visual ();
	  gtk_widget_pop_colormap ();
	*/
		
	g_object_unref (icon_pixmap);
	g_object_unref (icon_bitmap);

	gtk_widget_show_all (icon_img);
}

static void 
galeon_embed_autoscroller_init (GaleonEmbedAutoscroller *e)
{
	GaleonEmbedAutoscrollerPrivate *p = g_new0 (GaleonEmbedAutoscrollerPrivate, 1);
	e->priv = p;

	p->active = FALSE;
	p->msecs = 33;
}

static void
galeon_embed_autoscroller_finalize_impl (GObject *o)
{
	GaleonEmbedAutoscroller *as = GALEON_EMBED_AUTOSCROLLER (o);
	GaleonEmbedAutoscrollerPrivate *p = as->priv;

	LOG ("in galeon_embed_autoscroller_finalize_impl");

	if (p->embed)
	{
		g_object_unref (p->embed);
	}

	if (p->timeout_id)
	{
		g_source_remove (p->timeout_id);
		p->timeout_id = 0;
	}

	g_free (as->priv);

	G_OBJECT_CLASS (g_object_class)->finalize (o);
}

GaleonEmbedAutoscroller *
galeon_embed_autoscroller_new (void)
{
	GaleonEmbedAutoscroller *ret = g_object_new (GALEON_TYPE_EMBED_AUTOSCROLLER, NULL);
	return ret;
}

void
galeon_embed_autoscroller_set_embed (GaleonEmbedAutoscroller *as,
				     GaleonEmbed *embed)
{
	GaleonEmbedAutoscrollerPrivate *p = as->priv;

	if (p->embed)
	{
		g_object_unref (p->embed);
	}
	
	p->embed = g_object_ref (embed);
}

void
galeon_embed_autoscroller_start_scroll (GaleonEmbedAutoscroller *as,
					GtkWidget *widget, gint x, gint y)
{
	static GdkCursor *cursor = NULL;
	GaleonEmbedAutoscrollerPrivate *p = as->priv;

	g_return_if_fail (p->embed);
	
	if (p->active)
	{
		return;
	}
	p->active = TRUE;
	
	g_object_ref (as);

	p->widget = g_object_ref (widget);

	/* get a new cursor, if necessary */
	if (!cursor) cursor = gdk_cursor_new (GDK_FLEUR);

	/* show icon */
	gtk_window_move (GTK_WINDOW (autoscroll_icon), x - 12, y - 12);
	gtk_widget_show (autoscroll_icon);

	/* set positions */
	p->start_x = x;
	p->start_y = y;
	p->step_x = 0;
	p->step_y = 0;
	p->roundoff_error_x = 0;
	p->roundoff_error_y = 0;

	/* attach signals */
	g_signal_connect (widget, "motion_notify_event",
			    G_CALLBACK (galeon_embed_autoscroller_motion_cb), as);
	g_signal_connect (widget, "button_press_event",
			  G_CALLBACK (galeon_embed_autoscroller_mouse_press_cb), as);
	g_signal_connect (widget, "key_press_event",
			  G_CALLBACK (galeon_embed_autoscroller_key_press_cb), as);
	p->timeout_id =
		g_timeout_add (p->msecs,
			       galeon_embed_autoscroller_timeout_cb, as);

	/* grab the pointer */
	gtk_grab_add (widget);
	gdk_pointer_grab (widget->window, FALSE,
			  GDK_POINTER_MOTION_MASK |
			  GDK_BUTTON_PRESS_MASK,
			  NULL, cursor, GDK_CURRENT_TIME);
	gdk_keyboard_grab (widget->window, FALSE, GDK_CURRENT_TIME);
}

static gboolean
galeon_embed_autoscroller_motion_cb (GtkWidget *widget, GdkEventMotion *e,
				     GaleonEmbedAutoscroller *as)
{
	GaleonEmbedAutoscrollerPrivate *p = as->priv;
	gint x_dist, x_dist_abs, y_dist, y_dist_abs;

	if (!p->active)
	{
		return FALSE;
	}

	/* get distance between scroll center and cursor */
	x_dist = e->x_root - p->start_x;
	x_dist_abs = abs (x_dist);
	y_dist = e->y_root - p->start_y;
	y_dist_abs = abs (y_dist);

	/* calculate scroll step */
	if (y_dist_abs <= 48.0)
	{
		p->step_y = (float) (y_dist / 4) / 6.0;
	}
	else if (y_dist > 0)
	{
		p->step_y = (y_dist - 48.0) / 2.0 + 2.0;
	}
	else 
	{
		p->step_y = (y_dist + 48.0) / 2.0 - 2.0;
	}

	if (x_dist_abs <= 48.0)
	{
		p->step_x = (float) (x_dist / 4) / 6.0;
	}
	else if (x_dist > 0)
	{
		p->step_x = (x_dist - 48.0) / 2.0 + 2.0;
	}
	else 
	{
		p->step_x = (x_dist + 48.0) / 2.0 - 2.0;
	}

	return TRUE;
}

static gboolean
galeon_embed_autoscroller_mouse_press_cb (GtkWidget *widget, GdkEventButton *e,
				      GaleonEmbedAutoscroller *as)
{
	/* ungrab and disconnect */
	galeon_embed_autoscroller_stop (as);

	return TRUE;
}

static gboolean
galeon_embed_autoscroller_key_press_cb (GtkWidget *widget, GdkEventKey *e,
					GaleonEmbedAutoscroller *as)
{
	/* ungrab and disconnect */
	galeon_embed_autoscroller_stop (as);

	return TRUE;
}

static gint
galeon_embed_autoscroller_timeout_cb (gpointer data)
{
	struct timeval start_time, finish_time;
	long elapsed_msecs;
	GaleonEmbedAutoscroller *as = data;
	GaleonEmbedAutoscrollerPrivate *p;
	gfloat scroll_step_y_adj;
	gint scroll_step_y_int;
	gfloat scroll_step_x_adj;
	gint scroll_step_x_int;

	g_return_val_if_fail (GALEON_IS_EMBED_AUTOSCROLLER (as), FALSE);
	p = as->priv;
	g_return_val_if_fail (IS_GALEON_EMBED (p->embed), FALSE);

	/* return if we're not supposed to scroll */
	if (!p->step_y && !p->step_x)
	{
		return TRUE;
	}

	/* calculate the number of pixels to scroll */
	scroll_step_y_adj = p->step_y * p->msecs / 33;
	scroll_step_y_int = scroll_step_y_adj;
	p->roundoff_error_y += (scroll_step_y_adj - scroll_step_y_int);

	if (fabs (p->roundoff_error_y) >= 1.0)
	{
		scroll_step_y_int += p->roundoff_error_y;
		p->roundoff_error_y -= (gint) p->roundoff_error_y;
	}

	scroll_step_x_adj = p->step_x * p->msecs / 33;
	scroll_step_x_int = scroll_step_x_adj;
	p->roundoff_error_x += (scroll_step_x_adj - scroll_step_x_int);

	if (fabs (p->roundoff_error_x) >= 1.0)
	{
		scroll_step_x_int += p->roundoff_error_x;
		p->roundoff_error_x -= (gint) p->roundoff_error_x;
	}

	/* exit if we're not supposed to scroll yet */
	if (!scroll_step_y_int && !scroll_step_x_int) return TRUE;
	
	/* get the time before we tell the embed to scroll */
	gettimeofday (&start_time, NULL);

	/* do scrolling, moving at a constart speed regardless of the
	 * scrolling delay */

	/* FIXME: if mozilla is able to do diagonal scrolling in a
	 * reasonable manner at some point, this should be changed to
	 * calculate x and pass both values instead of just y */
	galeon_embed_fine_scroll (p->embed, scroll_step_x_int, scroll_step_y_int);

	/* find out how long the scroll took */
	gettimeofday (&finish_time, NULL);
	elapsed_msecs = (1000000L * finish_time.tv_sec + finish_time.tv_usec -
			 1000000L * start_time.tv_sec - start_time.tv_usec) /
			1000;

	/* check if we should update the scroll delay */
	if ((elapsed_msecs >= p->msecs + 5) ||
	    ((p->msecs > 20) && (elapsed_msecs < p->msecs - 10)))
	{
		/* update the scrolling delay, with a
		 * minimum delay of 20 ms */
		p->msecs = (elapsed_msecs + 10 >= 20) ? elapsed_msecs + 10 : 20;

		/* create new timeout with adjusted delay */
		p->timeout_id =	g_timeout_add (p->msecs, galeon_embed_autoscroller_timeout_cb, as);

		/* kill the old timeout */
		return FALSE;
	}

	/* don't kill timeout */
	return TRUE;
}

static void
galeon_embed_autoscroller_stop (GaleonEmbedAutoscroller *as)
{
	GaleonEmbedAutoscrollerPrivate *p = as->priv;

	/* ungrab the pointer if it's grabbed */
	if (gdk_pointer_is_grabbed ())
	{
		gdk_pointer_ungrab (GDK_CURRENT_TIME);
	}

	gdk_keyboard_ungrab (GDK_CURRENT_TIME);

	/* hide the icon */
	gtk_widget_hide (autoscroll_icon);

	g_return_if_fail (p->widget);
	
	gtk_grab_remove (p->widget);

	/* disconnect all of the signals */
	g_signal_handlers_disconnect_matched (p->widget, G_SIGNAL_MATCH_DATA, 0, 0, 
					      NULL, NULL, as);
	if (p->timeout_id)
	{
		g_source_remove (p->timeout_id);
		p->timeout_id = 0;
	}

	g_object_unref (p->widget);
	p->widget = NULL;

	p->active = FALSE;
	g_object_unref (as);
}

