/*
 *  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 <glib/gi18n.h>
#include <gtk/gtkimagemenuitem.h>
#include <gtk/gtkseparatormenuitem.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkimage.h>
#include "bookmarks-gtk-menu.h"
#include "bookmarks-gtk-menu-item.h"
#include "gul-gobject-misc.h"
#include "galeon-marshal.h"
#include "gul-string.h"
#include "gul-gui.h"
#include "bookmarks-context-menu.h"
#include "galeon-debug.h"

/**
 * Private data
 */
struct _GbGtkMenuPrivate {
	GbFolder *root;
	GtkMenuShell *ms;
	guint rebuild_timeout;
	gboolean context_only;

	GbLocationSource *location_source;
	GSList *menuitems;
};

#define REBUILD_TIMEOUT 700

/**
 * Private functions, only availble from this file
 */
static void		gb_gtk_menu_class_init			(GbGtkMenuClass *klass);
static void		gb_gtk_menu_init			(GbGtkMenu *e);
static void		gb_gtk_menu_finalize_impl		(GObject *o);
static void		gb_gtk_menu_build_submenu		(GbGtkMenu *gm);
static void		gb_gtk_menu_build_bookmark		(GbGtkMenu *gm, GbBookmark *item, guint index);

static void		gb_gtk_menu_child_modied_cb		(GbFolder *f, GbBookmark *bm, 
								 GbGtkMenu *gm);
static void		gb_gtk_menu_child_added_cb		(GbFolder *p, GbBookmark *c, 
								 gint position, GbGtkMenu *gm);
static void		gb_gtk_menu_child_removed_cb	 	(GbFolder *p, GbBookmark *c, 
								 gint position, GbGtkMenu *gm);
static void		gb_gtk_menu_context_menu_cb		(GbBookmarkSet *set, GbGtkMenu *gm);
static void		gb_gtk_menu_rebuild			(GbGtkMenu *gm);
static void 		gb_gtk_menu_rebuild_with_timeout	(GbGtkMenu *gm);
static gboolean		gb_gtk_menu_rebuild_timeout_cb		(gpointer data);
static void		gb_gtk_menu_bookmark_activated_cb	(GObject *sender,
								 GbBookmarkEventActivated *ev,
								 GbGtkMenu *gm);

static gpointer g_object_class;

enum GbGtkMenuSignalsEnum {
	GB_GTK_MENU_BOOKMARK_ACTIVATED,
	GB_GTK_MENU_LAST_SIGNAL
};
static gint GbGtkMenuSignals[GB_GTK_MENU_LAST_SIGNAL];

/**
 * GtkMenu object
 */

MAKE_GET_TYPE (gb_gtk_menu, "GbGtkMenu", GbGtkMenu, gb_gtk_menu_class_init,
	       gb_gtk_menu_init, G_TYPE_OBJECT);

static void
gb_gtk_menu_class_init (GbGtkMenuClass *klass)
{
	G_OBJECT_CLASS (klass)->finalize = gb_gtk_menu_finalize_impl;
	g_object_class = g_type_class_peek_parent (klass);

	GbGtkMenuSignals[GB_GTK_MENU_BOOKMARK_ACTIVATED] = g_signal_new (
		"bookmark-activated", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbGtkMenuClass, gb_gtk_menu_bookmark_activated), 
		NULL, NULL, 
		g_cclosure_marshal_VOID__POINTER,
		G_TYPE_NONE, 1, G_TYPE_POINTER);
}

static void 
gb_gtk_menu_init (GbGtkMenu *m)
{
	GbGtkMenuPrivate *p = g_new0 (GbGtkMenuPrivate, 1);
	m->priv = p;
}

static void
gb_gtk_menu_finalize_impl (GObject *o)
{
	GbGtkMenu *gm = GB_GTK_MENU (o);
	GbGtkMenuPrivate *p = gm->priv;
	GSList *li;

	LOG ("Finalizing GbGtkMenu");

	gb_gtk_menu_set_location_source (gm, NULL);

	g_signal_handlers_disconnect_matched (p->root, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, gm);
	if (GB_BOOKMARK (p->root)->set)
	{
		g_signal_handlers_disconnect_matched (GB_BOOKMARK (p->root)->set, 
						      G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, gm);
	}

	if (p->rebuild_timeout)
	{
		g_source_remove (p->rebuild_timeout);
	}

	for (li = p->menuitems; li; li = li->next)
	{
		g_object_unref (li->data);
	}
	g_slist_free (p->menuitems);

	g_object_unref (G_OBJECT (p->root));
	g_object_unref (G_OBJECT (p->ms));
	g_free (p);

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

static GbGtkMenu *
gb_gtk_menu_new_internal (GbFolder *root, GtkMenuShell *ms, gboolean context_only)
{
	GbGtkMenu *ret = g_object_new (GB_TYPE_GTK_MENU, NULL);
	GbGtkMenuPrivate *p = ret->priv;

	p->root = root;
	g_object_ref (root);

	p->context_only = context_only;

	if (p->context_only)
	{
		g_signal_connect (GB_BOOKMARK (root)->set, "context-menu",
				  G_CALLBACK (gb_gtk_menu_context_menu_cb), ret);
	}
	else
	{
		g_signal_connect (root, "child-modified", 
				  G_CALLBACK (gb_gtk_menu_child_modied_cb), ret);
		g_signal_connect (root, "child-added", 
				  G_CALLBACK (gb_gtk_menu_child_added_cb), ret);
		g_signal_connect (root, "child-removed", 
				  G_CALLBACK (gb_gtk_menu_child_removed_cb), ret);
	}

	p->ms = ms;
	g_object_ref (G_OBJECT (ms));

	p->menuitems = NULL;
	gb_gtk_menu_rebuild (ret);

	return ret;
}

GbGtkMenu *
gb_gtk_menu_new (GbFolder *root, GtkMenuShell *ms)
{
	GbGtkMenu *ret = gb_gtk_menu_new_internal (root, ms, FALSE);
	return ret;
}

GbGtkMenu *
gb_gtk_menu_new_context_only (GbBookmarkSet *set, GtkMenuShell *ms)
{
	GbGtkMenu *ret = gb_gtk_menu_new_internal (set->root, ms, TRUE);
	return ret;
}

static void
gb_gtk_menu_build_submenu (GbGtkMenu *gm)
{
	GbGtkMenuPrivate *p = gm->priv;
	guint index = 0;
	GSList *items;
	GSList *li;

	if (p->context_only)
	{
		items = g_slist_copy ((GSList *) gb_bookmark_set_get_context_bookmarks (GB_BOOKMARK (p->root)->set));
		if (g_slist_length(items) != 0)
			gul_gui_add_separator_conditional (p->ms);
		else
			gul_gui_remove_separator_conditional (p->ms);
	}
	else
	{
		gul_gui_setup_tearoff (p->ms);
		items = gb_folder_list_children (p->root);
	}

	for (li = items; li != NULL; li = li->next)
	{
		GbBookmark *i = li->data;
		
		if  (GB_IS_FOLDER (i) || GB_IS_SITE (i))
		{
			gb_gtk_menu_build_bookmark (gm, i, index);
		}
		else if (GB_IS_SEPARATOR (i))
		{
			GtkWidget *sep = gtk_separator_menu_item_new ();
			gtk_container_add (GTK_CONTAINER (p->ms), sep);
			gtk_widget_show (sep);
			p->menuitems = g_slist_prepend (p->menuitems, g_object_ref (sep));
		}
		else
		{
			g_warning ("something skipped when building bookmarks menu");
		}
		index++;
	}
	
	g_slist_free (items);
}

static void
gb_gtk_menu_build_bookmark (GbGtkMenu *gm, GbBookmark *item, guint index)
{
	GbGtkMenuPrivate *p = gm->priv;
	GbGtkMenuItem *w;

	w = gb_gtk_menu_item_new (item);
	gb_gtk_menu_item_set_location_source (w, p->location_source);
	g_signal_connect (w, "bookmark-activated", 
			  G_CALLBACK (gb_gtk_menu_bookmark_activated_cb), gm);

	p->menuitems = g_slist_prepend (p->menuitems, g_object_ref (w));

	gtk_widget_show (GTK_WIDGET (w));
	gtk_container_add (GTK_CONTAINER (p->ms), GTK_WIDGET (w));
}


void
gb_gtk_menu_set_location_source (GbGtkMenu *gm, GbLocationSource *src)
{
	GbGtkMenuPrivate *p = gm->priv;
	GSList *li;

	if (p->location_source)
	{
		g_object_remove_weak_pointer (G_OBJECT (p->location_source),
					      (gpointer *) &p->location_source);
	}

	p->location_source = src;

	if (p->location_source)
	{
		g_object_add_weak_pointer (G_OBJECT (p->location_source), 
					   (gpointer *) &p->location_source);
	}

	for (li = p->menuitems; li; li = li->next)
	{
		if (GB_IS_GTK_MENU_ITEM (li->data))
		{
			gb_gtk_menu_item_set_location_source (li->data, src);
		}
	}
}

static gboolean
gb_gtk_menu_rebuild_timeout_cb (gpointer data)
{
	GbGtkMenu *gm = data;
	GbGtkMenuPrivate *p = gm->priv;
	LOG ("GbGtkMenu rebuild timeout");

	p->rebuild_timeout = 0;

	gb_gtk_menu_rebuild (data);
	return FALSE;
}

static void 
gb_gtk_menu_rebuild_with_timeout (GbGtkMenu *gm) 
{
	GbGtkMenuPrivate *p = gm->priv;

	if (p->rebuild_timeout)
	{
		g_source_remove (p->rebuild_timeout);
	}
	
	p->rebuild_timeout = g_timeout_add (REBUILD_TIMEOUT, 
					    gb_gtk_menu_rebuild_timeout_cb, gm);
}

static void
gb_gtk_menu_rebuild (GbGtkMenu *gm)
{
	GbGtkMenuPrivate *p = gm->priv;
	GSList *sli;

	LOG ("gb_gtk_menu_rebuild called");
	
	/* this could be more fine grained. For now, just destroy everything and 
	   build it again */
	
	/* remove already created menuitems*/
	for (sli = p->menuitems; sli; sli = sli->next)
	{
		g_assert (GTK_IS_WIDGET (sli->data) && GTK_WIDGET (sli->data)->parent == GTK_WIDGET (p->ms));
		gtk_container_remove (GTK_CONTAINER (p->ms), sli->data);
	}

	/* free the list of menuitems */
	for (sli = p->menuitems; sli; sli = sli->next)
	{
		g_object_unref (sli->data);
	}
	g_slist_free (p->menuitems);
	p->menuitems = NULL;

	gb_gtk_menu_build_submenu (gm);
}

static void
gb_gtk_menu_child_modied_cb (GbFolder *f, GbBookmark *bm, GbGtkMenu *gm)
{
	gb_gtk_menu_rebuild_with_timeout (gm);
}

static void
gb_gtk_menu_child_added_cb (GbFolder *p, GbBookmark *c, gint position, GbGtkMenu *gm)
{
	gb_gtk_menu_rebuild_with_timeout (gm);
}

static void
gb_gtk_menu_child_removed_cb (GbFolder *p, GbBookmark *c, gint position, GbGtkMenu *gm)
{
	gb_gtk_menu_rebuild_with_timeout (gm);
}

static void
gb_gtk_menu_context_menu_cb (GbBookmarkSet *set, GbGtkMenu *gm)
{
	g_return_if_fail (GB_IS_GTK_MENU (gm));
	g_return_if_fail (GB_IS_BOOKMARK_SET (set));
	g_return_if_fail (GB_BOOKMARK (gm->priv->root)->set == set);

	gb_gtk_menu_rebuild_with_timeout (gm);
}

static void
gb_gtk_menu_bookmark_activated_cb (GObject *sender,
				   GbBookmarkEventActivated *ev,
				   GbGtkMenu *gm)
{
	g_signal_emit (gm, GbGtkMenuSignals[GB_GTK_MENU_BOOKMARK_ACTIVATED], 0, ev);
}

void
gb_gtk_menu_fill_children_submenus (GbGtkMenu *gm)
{
	GbGtkMenuPrivate *p = gm->priv;
	GSList *li;

	for (li = p->menuitems; li; li = li->next)
	{
		if (GB_IS_GTK_MENU_ITEM (li->data))
		{
			gb_gtk_menu_item_fill_submenu (li->data, FALSE);
		}
	}
}

