/*
 *  Copyright (C) 2003 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-action-navigation.h"
#include "galeon-shell.h"
#include "galeon-favicon-cache.h"
#include "gul-gobject-misc.h"
#include "gul-toolbutton.h"
#include "gul-string.h"

#include <gtk/gtkstock.h>
#include <gtk/gtkimage.h>

#include <glib/gi18n.h>
#include <string.h>

enum {
	PROP_0,
	PROP_SHOW_ARROW,
	PROP_DIRECTION,
};

#define MENU_ITEM_MAX_LENGTH 40

static void		galeon_action_navigation_init			(GaleonActionNavigation *action);
static void		galeon_action_navigation_class_init		(GaleonActionNavigationClass *class);
static void		galeon_action_navigation_set_property		(GObject *object,
									 guint prop_id,
									 const GValue *value,
									 GParamSpec *pspec);
static void		galeon_action_navigation_get_property	 	(GObject *object,
									 guint prop_id,
									 GValue *value,
									 GParamSpec *pspec);
static void		galeon_action_navigation_connect_proxy		(GtkAction *action, GtkWidget *proxy);
static void		galeon_action_navigation_update			(GaleonAction *action);
static void		galeon_action_navigation_current_embed_changed	(GaleonAction *action, 
									 GaleonEmbed *old_embed, GaleonEmbed *new_embed);
static void             galeon_action_navigation_activate               (GtkAction *action);

static GtkActionClass *parent_class = NULL;

GType
galeon_action_navigation_direcion_get_type (void)
{
	static GType etype = 0;
	if (etype == 0)
	{
		static const GEnumValue values[] = {
			{ GALEON_NAVIGATION_DIRECTION_UP, "GALEON_NAVIGATION_DIRECTION_UP", "up" },
			{ GALEON_NAVIGATION_DIRECTION_BACK, "GALEON_NAVIGATION_DIRECTION_BACK", "back" },
			{ GALEON_NAVIGATION_DIRECTION_FORWARD, "GALEON_NAVIGATION_DIRECTION_FORWARD", "forward" },
			{ 0, NULL, NULL }
		};
		etype = g_enum_register_static ("GaleonActionNavigationDirection", values);
	}
	return etype;
}

MAKE_GET_TYPE (galeon_action_navigation, "GaleonActionNavigation", GaleonActionNavigation,
	       galeon_action_navigation_class_init, 
	       galeon_action_navigation_init, GALEON_TYPE_ACTION);


static void
galeon_action_navigation_class_init (GaleonActionNavigationClass *class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (class);
	GtkActionClass *action_class = GTK_ACTION_CLASS (class);
	GaleonActionClass *galeon_action_class = GALEON_ACTION_CLASS (class);

	object_class->set_property = galeon_action_navigation_set_property;
	object_class->get_property = galeon_action_navigation_get_property;

	action_class->menu_item_type    = GTK_TYPE_IMAGE_MENU_ITEM;
	action_class->toolbar_item_type = GUL_TYPE_TOOLBUTTON;
	action_class->connect_proxy = galeon_action_navigation_connect_proxy;
	action_class->activate      = galeon_action_navigation_activate;

	galeon_action_class->update = galeon_action_navigation_update;
	galeon_action_class->current_embed_changed = galeon_action_navigation_current_embed_changed;
	
	g_object_class_install_property (object_class,
					 PROP_SHOW_ARROW,
					 g_param_spec_boolean ("show_arrow",
							       _("Show Arrow"),
							       _("Show the dropdown arrow."),
							       FALSE,
							       G_PARAM_READWRITE));

	g_object_class_install_property (object_class,
					 PROP_DIRECTION,
					 g_param_spec_enum ("direction",
							    _("Direction"),
							    _("Direction of the navigation."),
							    GALEON_TYPE_ACTION_NAVIGATION_DIRECTION,
							    GALEON_NAVIGATION_DIRECTION_BACK,
							    G_PARAM_READWRITE));
	
	parent_class = g_type_class_peek_parent (class);
}

static void
galeon_action_navigation_init (GaleonActionNavigation *action)
{
}

static void
galeon_action_navigation_set_property (GObject *object,
			    guint prop_id,
			    const GValue *value,
			    GParamSpec *pspec)
{
	GaleonActionNavigation *a = GALEON_ACTION_NAVIGATION (object);

	switch (prop_id)
	{
	case PROP_SHOW_ARROW:
		{
			gboolean v = g_value_get_boolean (value);
			galeon_action_navigation_set_show_arrow (a, v);
		}
		break;
	case PROP_DIRECTION:
		{
			GaleonActionNavigationDirection v = g_value_get_enum (value);
			galeon_action_navigation_set_direction (a, v);
		}
	default: 
		break;
	}
}

static void
galeon_action_navigation_get_property (GObject *object,
				       guint prop_id,
				       GValue *value,
				       GParamSpec *pspec)
{
	GaleonActionNavigation *a = GALEON_ACTION_NAVIGATION (object);
	
	switch (prop_id)
	{
	case PROP_SHOW_ARROW:
		g_value_set_boolean (value, a->show_arrow);
		break;
	case PROP_DIRECTION:
		g_value_set_enum (value, a->direction);
		break;
	default: 
		break;
	}
}

static void
galeon_action_navigation_update (GaleonAction *action)
{
	gboolean sensitive;
	GaleonEmbed *embed = galeon_action_get_embed (action);

	if (embed)
	{
		char *url = NULL, *title = NULL;
		char *tooltip = NULL;

		switch (GALEON_ACTION_NAVIGATION (action)->direction)
		{
		case GALEON_NAVIGATION_DIRECTION_BACK:
			sensitive = (galeon_embed_can_go_back (embed) == G_OK);
			if (sensitive)
			{
				galeon_embed_shistory_get_nth (embed, -1, TRUE, &url, &title);
				tooltip = g_strdup_printf (_("Go back to \"%s\""), title);
			}
			break;
		case GALEON_NAVIGATION_DIRECTION_FORWARD:
			sensitive = (galeon_embed_can_go_forward (embed) == G_OK);
			if (sensitive)
			{
				galeon_embed_shistory_get_nth (embed, +1, TRUE, &url, &title);
				tooltip = g_strdup_printf (_("Go forward to \"%s\""), title);
			}
			break;
		case GALEON_NAVIGATION_DIRECTION_UP:
			sensitive = (galeon_embed_can_go_up (embed) == G_OK);
			break;
		default:
			sensitive = FALSE;
			g_assert_not_reached ();
		}

		if (tooltip != NULL)
		{
			g_object_set (action, "tooltip", tooltip, NULL);
		}

		g_free (url);
		g_free (title);
		g_free (tooltip);
	}
	else
	{
		sensitive = FALSE;
	}

	g_object_set (G_OBJECT (action), "sensitive", sensitive, NULL);
}

static void
galeon_action_navigation_location_cb (GaleonEmbed *embed, GaleonAction *action)
{
	galeon_action_navigation_update (action);
}

static void
galeon_action_navigation_current_embed_changed (GaleonAction *action, 
					      GaleonEmbed *old_embed, GaleonEmbed *new_embed)
{
	if (old_embed)
	{
		g_signal_handlers_disconnect_matched (old_embed, G_SIGNAL_MATCH_DATA, 
						      0, 0, NULL, NULL, action);
	}

	if (new_embed)
	{
		g_signal_connect_object (new_embed, "ge-location", 
					 G_CALLBACK (galeon_action_navigation_location_cb),
					 action, 0);
	}

	galeon_action_navigation_update (action);
}

static void
galeon_action_navigation_activate (GtkAction *action)
{
	GaleonActionNavigation *a = GALEON_ACTION_NAVIGATION (action);
	GaleonWindow *window = galeon_action_get_window (GALEON_ACTION (a));
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));

	g_return_if_fail (window != NULL);
	g_return_if_fail (embed != NULL);

	switch (a->direction)
	{
	case GALEON_NAVIGATION_DIRECTION_UP:
		galeon_embed_go_up (embed);
		break;
	case GALEON_NAVIGATION_DIRECTION_BACK:
		galeon_embed_go_back (embed);
		break;
	case GALEON_NAVIGATION_DIRECTION_FORWARD:
		galeon_embed_go_forward (embed);
		break;
	default:
		g_assert_not_reached ();
		break;
	}
}

static GtkWidget *
new_num_accel_menu_item (gint num, const gchar *origtext, const gchar *url)
{
        gchar *shortened, *escaped, *text;
        GtkWidget *item;

	/* shorten, escape, add accel */
	shortened = gul_string_shorten (origtext, MENU_ITEM_MAX_LENGTH);
	escaped   = gul_string_double_underscores (shortened);
	text      = gul_string_new_num_accel (num, escaped, TRUE);

	g_free (shortened);
	g_free (escaped);

	if (text == NULL) return NULL;
	item = gtk_image_menu_item_new_with_mnemonic (text);
	gtk_widget_show (item);
	g_free (text);

	if (url)
	{
		GaleonFaviconCache *cache = galeon_shell_get_favicon_cache (galeon_shell);
		GdkPixbuf *icon = galeon_favicon_cache_lookup (cache, url);

		if (icon)
		{
			GtkWidget *image = gtk_image_new_from_pixbuf (icon);
			
			gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
						       GTK_WIDGET (image));
			gtk_widget_show (GTK_WIDGET (image));
		}
	}

        return item;
}

static void
activate_back_or_forward_menu_item_cb (GtkWidget *menu, GaleonActionNavigation *a)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	int go_nth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu), "go_nth"));
	
	g_return_if_fail (embed != NULL);

	galeon_embed_shistory_go_nth (embed, go_nth);
}

static void
open_back_or_forward_in_new_tab (GaleonActionNavigation *a, int go_nth)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	gchar *url, *title;
	GaleonWindow *window;
	GaleonTab *tab;

	g_return_if_fail (embed != NULL);
		
	if (galeon_embed_shistory_get_nth (embed, go_nth, FALSE, &url, &title) != G_OK)
	{
		return;
	}
	g_free (title);

	if (!url) return;

	window = galeon_action_get_window (GALEON_ACTION (a));
	tab = galeon_window_get_active_tab (window);
	galeon_shell_new_tab (galeon_shell, window, tab, url, GALEON_NEW_TAB_JUMP);
	g_free (url);
}

static gboolean
button_press_event_back_or_forward_menu_item_cb  (GtkWidget *widget, GdkEventButton *event, 
						  GaleonActionNavigation *a)
{
	GaleonWindow *window = galeon_action_get_window (GALEON_ACTION (a));
	if (event->button == 2 && IS_GALEON_WINDOW (window))
	{
		int go_nth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "go_nth"));
		open_back_or_forward_in_new_tab (a, go_nth);
		return TRUE;
	}
	return FALSE;
}

static void
activate_up_menu_item_cb (GtkWidget *menu, GaleonActionNavigation *a)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	int go_nth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu), "go_nth"));
	GSList *l;
	gchar *url;
	
	g_return_if_fail (embed != NULL);

	galeon_embed_get_go_up_list (embed, &l);

	url = g_slist_nth_data (l, go_nth);
	if (url)
	{
		galeon_embed_load_url (embed, url);
	}

	g_slist_foreach (l, (GFunc) g_free, NULL);
	g_slist_free (l);
}

static void
open_up_in_tab (GaleonActionNavigation *a, int go_nth)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	gchar *url;
	GSList *l;

	g_return_if_fail (embed != NULL);
		
	galeon_embed_get_go_up_list (embed, &l);
	url = g_slist_nth_data (l, go_nth);

	if (url)
	{
		GaleonWindow *window = galeon_action_get_window (GALEON_ACTION (a));
		GaleonTab *tab = galeon_window_get_active_tab (window);
		galeon_shell_new_tab (galeon_shell, window, tab, url, GALEON_NEW_TAB_JUMP);
	}

	g_slist_foreach (l, (GFunc) g_free, NULL);
	g_slist_free (l);
}


static gboolean
button_press_event_up_menu_item_cb  (GtkWidget *widget, GdkEventButton *event, 
				     GaleonActionNavigation *a)
{
	GaleonWindow *window = galeon_action_get_window (GALEON_ACTION (a));
	if (event->button == 2 && IS_GALEON_WINDOW (window))
	{
		int go_nth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "go_nth"));
		open_up_in_tab (a, go_nth);
		return TRUE;
	}
	return FALSE;
}

static gboolean
galeon_action_navigation_button_press_event_cb (GtkWidget *widget, 
						GdkEventButton *event, 
						GaleonActionNavigation *a)
{
	GaleonWindow *window = galeon_action_get_window (GALEON_ACTION (a));
	GaleonEmbed *embed;

	if (event->button != 2 || ! IS_GALEON_WINDOW (window))
	{
		return FALSE;
	}

	embed = galeon_action_get_embed (GALEON_ACTION (a));
	g_return_val_if_fail (embed != NULL, FALSE);
	
	if (a->direction == GALEON_NAVIGATION_DIRECTION_UP)
	{
		open_up_in_tab (a, 0);
	}
	else
	{
		int pos;
		galeon_embed_shistory_get_pos (embed, &pos);
		pos = (a->direction == GALEON_NAVIGATION_DIRECTION_BACK) ? pos-1 : pos+1;
		open_back_or_forward_in_new_tab (a, pos);
		return TRUE;
	}
	return FALSE;
}

static void
setup_back_or_forward_menu (GaleonActionNavigation *a, GtkMenuShell *ms, 
			    GaleonActionNavigationDirection dir)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	int pos, count;
	int start, end, accell_count = 0;

	g_return_if_fail (embed != NULL);
	
	galeon_embed_shistory_get_pos (embed, &pos);
	galeon_embed_shistory_count (embed, &count);

	if (count == 0) return;
	
	if (dir == GALEON_NAVIGATION_DIRECTION_BACK)
	{
		start = pos - 1;
		end = -1;
	}
	else
	{
		start = pos + 1;
		end = count;
	}

	while (start != end)
	{
		char *title, *url;
		GtkWidget *item;

		galeon_embed_shistory_get_nth (embed, start, FALSE, &url, &title);

		item = new_num_accel_menu_item (accell_count, title, url);
		g_free (title);
		g_free (url);

		if (item == NULL) break;

        	gtk_menu_shell_append (ms, item);
		g_object_set_data (G_OBJECT (item), "go_nth", GINT_TO_POINTER (start));
		g_signal_connect (item, "activate",
                                  G_CALLBACK (activate_back_or_forward_menu_item_cb), a);
		g_signal_connect (item, "button-press-event",
                                  G_CALLBACK (button_press_event_back_or_forward_menu_item_cb), a);
		gtk_widget_show_all (item);

		accell_count++;
		if (start < end)
		{
			start++;
		}
		else
		{
			start--;
		}
	}
}

static void
setup_up_menu (GaleonActionNavigation *a, GtkMenuShell *ms)
{
	GaleonEmbed *embed = galeon_action_get_embed (GALEON_ACTION (a));
	int accell_count = 0;
	GSList *l;
	GSList *li;

	g_return_if_fail (embed != NULL);
	
	galeon_embed_get_go_up_list (embed, &l);
	
	for (li = l; li; li = li->next)
	{
		char *url = li->data;
		GtkWidget *item;

		item = new_num_accel_menu_item (accell_count, url, url);
		if (item == NULL) break;

        	gtk_menu_shell_append (ms, item);
		g_object_set_data (G_OBJECT(item), "go_nth", GINT_TO_POINTER (accell_count));
		g_signal_connect (item, "activate",
                                  G_CALLBACK (activate_up_menu_item_cb), a);
		g_signal_connect (item, "button-press-event",
                                  G_CALLBACK (button_press_event_up_menu_item_cb), a);
		gtk_widget_show_all (item);

		accell_count++;
	}
	
	g_slist_foreach (l, (GFunc) g_free, NULL);
	g_slist_free (l);
}

static void
galeon_action_navigation_menu_activated_cb (GulToolbutton *w, GaleonActionNavigation *a)
{
	GtkMenuShell *ms = gul_toolbutton_get_menu (w);
	GList *children;
	GList *li;

	children = gtk_container_get_children (GTK_CONTAINER (ms));
	for (li = children; li; li = li->next)
	{
		gtk_container_remove (GTK_CONTAINER (ms), li->data);
	}
	g_list_free (children);
	
	switch (a->direction)
	{
	case GALEON_NAVIGATION_DIRECTION_UP:
		setup_up_menu (a, ms);
		break;
	case GALEON_NAVIGATION_DIRECTION_FORWARD:
	case GALEON_NAVIGATION_DIRECTION_BACK:
		setup_back_or_forward_menu (a, ms, a->direction);
		break;
	default:
		g_assert_not_reached ();
		break;
	}
}

static void
galeon_action_navigation_connect_proxy (GtkAction *a, GtkWidget *proxy)
{
	GaleonActionNavigation *action = GALEON_ACTION_NAVIGATION (a);
	GtkWidget *widget = proxy;

	if (GUL_IS_TOOLBUTTON (proxy))
	{
		widget = gul_toolbutton_get_button (GUL_TOOLBUTTON (proxy));

		g_signal_connect (proxy, "menu-activated", 
				  G_CALLBACK (galeon_action_navigation_menu_activated_cb), a);

		gul_toolbutton_set_show_arrow (GUL_TOOLBUTTON (proxy), action->show_arrow);

	}

	g_signal_connect (widget, "button-press-event",
			  G_CALLBACK (galeon_action_navigation_button_press_event_cb), a);

	(* GTK_ACTION_CLASS (parent_class)->connect_proxy) (a, proxy);
}

gboolean
galeon_action_navigation_get_show_arrow (GaleonActionNavigation *a)
{
	return a->show_arrow;
}

void
galeon_action_navigation_set_show_arrow (GaleonActionNavigation *a, gboolean value)
{
	GSList *sli;
	a->show_arrow = value;
	for (sli = gtk_action_get_proxies (GTK_ACTION (a)); sli; sli = sli->next)
	{
		GulToolbutton *b = GUL_TOOLBUTTON (sli->data);
		gul_toolbutton_set_show_arrow (b, a->show_arrow);
	}

	galeon_action_navigation_update (GALEON_ACTION (a));
}

GaleonActionNavigationDirection
galeon_action_navigation_get_direction (GaleonActionNavigation *a)
{
	return a->direction;
}

void
galeon_action_navigation_set_direction (GaleonActionNavigation *a,
					GaleonActionNavigationDirection value)
{
	g_return_if_fail (value == GALEON_NAVIGATION_DIRECTION_BACK 
			  || value == GALEON_NAVIGATION_DIRECTION_FORWARD
			  || value == GALEON_NAVIGATION_DIRECTION_UP);
	
	a->direction = value;
	switch (a->direction)
	{
	case GALEON_NAVIGATION_DIRECTION_UP:
		g_object_set (G_OBJECT (a), "stock_id", GTK_STOCK_GO_UP, NULL);
		g_object_set (G_OBJECT (a), "tooltip", _("Go up"), NULL);
		g_object_set (G_OBJECT (a), "is_important", FALSE, NULL);
		break;
	case GALEON_NAVIGATION_DIRECTION_FORWARD:
		g_object_set (G_OBJECT (a), "stock_id", GTK_STOCK_GO_FORWARD, NULL);
		g_object_set (G_OBJECT (a), "tooltip", _("Go forward"), NULL);
		g_object_set (G_OBJECT (a), "is_important", FALSE, NULL);
		break;
	case GALEON_NAVIGATION_DIRECTION_BACK:
		g_object_set (G_OBJECT (a), "stock_id", GTK_STOCK_GO_BACK, NULL);
		g_object_set (G_OBJECT (a), "tooltip", _("Go back"), NULL);
		g_object_set (G_OBJECT (a), "is_important", TRUE, NULL);
		break;
	}

	galeon_action_navigation_update (GALEON_ACTION (a));
}

