/*
 *  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.
 */

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

#include "galeon-embed-manual-scroller.h"
#include "gul-gobject-misc.h"
#include "gul-general.h"
#include "galeon-debug.h"
#include <gtk/gtkwindow.h>
#include <gtk/gtkmain.h>
#include <math.h>

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

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

/**
 * Private functions, only availble from this file
 */
static void		galeon_embed_manual_scroller_class_init	(GaleonEmbedManualScrollerClass *klass);
static void		galeon_embed_manual_scroller_init	(GaleonEmbedManualScroller *as);
static void		galeon_embed_manual_scroller_finalize_impl (GObject *o);
static gboolean		galeon_embed_manual_scroller_motion_cb	(GtkWidget *widget, GdkEventMotion *e,
								 GaleonEmbedManualScroller *as);
static gboolean		galeon_embed_manual_scroller_mouse_release_cb (GtkWidget *widget, GdkEventButton *e,
								       GaleonEmbedManualScroller *as);
static gboolean		galeon_embed_manual_scroller_key_press_cb (GtkWidget *widget, GdkEventKey *e,
								   GaleonEmbedManualScroller *as);
static gint		galeon_embed_manual_scroller_timeout_cb	(gpointer data);
static void		galeon_embed_manual_scroller_stop	(GaleonEmbedManualScroller *as);

static gpointer g_object_class;

/**
 * EmbedManualScroller object
 */

MAKE_GET_TYPE (galeon_embed_manual_scroller, "GaleonEmbedManualScroller", 
	       GaleonEmbedManualScroller, galeon_embed_manual_scroller_class_init, 
	       galeon_embed_manual_scroller_init, G_TYPE_OBJECT);

static void
galeon_embed_manual_scroller_class_init (GaleonEmbedManualScrollerClass *klass)
{
	G_OBJECT_CLASS (klass)->finalize = galeon_embed_manual_scroller_finalize_impl;
	g_object_class = g_type_class_peek_parent (klass);
}

static void 
galeon_embed_manual_scroller_init (GaleonEmbedManualScroller *e)
{
	GaleonEmbedManualScrollerPrivate *p = g_new0 (GaleonEmbedManualScrollerPrivate, 1);
	e->priv = p;
	
	p->active = FALSE;
	p->msecs = 33;
}

static void
galeon_embed_manual_scroller_finalize_impl (GObject *o)
{
	GaleonEmbedManualScroller *as = GALEON_EMBED_MANUAL_SCROLLER (o);
	GaleonEmbedManualScrollerPrivate *p = as->priv;
	
	LOG ("in galeon_embed_manual_scroller_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);
}

GaleonEmbedManualScroller *
galeon_embed_manual_scroller_new (void)
{
	GaleonEmbedManualScroller *ret = g_object_new (GALEON_TYPE_EMBED_MANUAL_SCROLLER, NULL);
	return ret;
}

void
galeon_embed_manual_scroller_set_embed (GaleonEmbedManualScroller *as,
					GaleonEmbed *embed)
{
	GaleonEmbedManualScrollerPrivate *p = as->priv;
	
	if (p->embed)
	{
		g_object_unref (p->embed);
	}
	
	p->embed = g_object_ref (embed);
}

void
galeon_embed_manual_scroller_start_scroll (GaleonEmbedManualScroller *as,
					   GtkWidget *widget, gint x, gint y)
{
	static GdkCursor *cursor = NULL;
	GaleonEmbedManualScrollerPrivate *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);
	}

	/* set positions */
	p->start_x = x;
	p->start_y = y;
	p->step_x = 0;
	p->step_y = 0;
	
	/* attach signals */
	g_signal_connect (widget, "motion_notify_event",
			  G_CALLBACK (galeon_embed_manual_scroller_motion_cb), as);
	g_signal_connect (widget, "button_release_event",
			  G_CALLBACK (galeon_embed_manual_scroller_mouse_release_cb), as);
	g_signal_connect (widget, "key_press_event",
			  G_CALLBACK (galeon_embed_manual_scroller_key_press_cb), as);
	p->timeout_id =
		g_timeout_add (p->msecs,
			       galeon_embed_manual_scroller_timeout_cb, as);
	
	/* grab the pointer */
	gtk_grab_add (widget);
	gdk_pointer_grab (widget->window, FALSE,
			  GDK_POINTER_MOTION_MASK |
			  GDK_BUTTON_RELEASE_MASK,
			  NULL, cursor, GDK_CURRENT_TIME);
	gdk_keyboard_grab (widget->window, FALSE, GDK_CURRENT_TIME);
}

static gboolean
galeon_embed_manual_scroller_motion_cb (GtkWidget *widget, GdkEventMotion *e,
					GaleonEmbedManualScroller *as)
{
	GaleonEmbedManualScrollerPrivate *p = as->priv;
	gint x_dist, y_dist;
	
	if (!p->active)
	{
		return FALSE;
	}
	
	/* get distance between scroll center and cursor */
	x_dist = e->x_root - p->start_x;
	y_dist = e->y_root - p->start_y;
	
	p->step_x  += x_dist;
	p->step_y  += y_dist;

	p->start_y = e->y_root;
	p->start_x = e->x_root;

	return TRUE;
}

static gboolean
galeon_embed_manual_scroller_mouse_release_cb (GtkWidget *widget, GdkEventButton *e,
					       GaleonEmbedManualScroller *as)
{
	/* ungrab and disconnect */
	galeon_embed_manual_scroller_stop (as);
	
	return TRUE;
}

static gboolean
galeon_embed_manual_scroller_key_press_cb (GtkWidget *widget, GdkEventKey *e,
					   GaleonEmbedManualScroller *as)
{
	/* ungrab and disconnect */
	galeon_embed_manual_scroller_stop (as);
	
	return TRUE;
}

static gint
galeon_embed_manual_scroller_timeout_cb (gpointer data)
{
	struct timeval start_time, finish_time;
	long elapsed_msecs;
	GaleonEmbedManualScroller *as = data;
	GaleonEmbedManualScrollerPrivate *p;
	
	g_return_val_if_fail (GALEON_IS_EMBED_MANUAL_SCROLLER (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;
	}
	
	/* get the time before we tell the embed to scroll */
	gettimeofday (&start_time, NULL);

	/* 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, -p->step_x, -p->step_y);
	
	p->step_y = 0;
	p->step_x = 0;

	/* 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_manual_scroller_timeout_cb, as);
		
		/* kill the old timeout */
		return FALSE;
	}
	
	/* don't kill timeout */
	return TRUE;
}

static void
galeon_embed_manual_scroller_stop (GaleonEmbedManualScroller *as)
{
	GaleonEmbedManualScrollerPrivate *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);
	
	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);
}

