/*
 *  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 "bookmarks-tree-view.h"
#include "bookmarks-context-menu.h"
#include "bookmarks-context-menu-several.h"
#include "bookmarks-single-editor.h"
#include "bookmarks-util.h"
#include "gul-gobject-misc.h"
#include "galeon-marshal.h"
#include "gul-string.h"
#include "galeon-debug.h"

#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkcellrenderertoggle.h>
#include <gtk/gtkcellrendererpixbuf.h>
#include <gtk/gtktreeview.h>
#include <gtk/gtktreeselection.h>

/**
 * Private data
 */
struct _GbTreeViewPrivate 
{
	GSList *selected;
	GbTreeViewAction double_click_action;
	GbLocationSource *location_source;
	GtkTreeViewColumn *location_column;
	GtkTreeViewColumn *check_column;
	gboolean location_visible;
	gboolean check_visible;
	gboolean autoexpand_roots;
};

/**
 * Private functions, only availble from this file
 */
static void		gb_tree_view_class_init			(GbTreeViewClass *klass);
static void		gb_tree_view_init			(GbTreeView *e);
static void		gb_tree_view_finalize_impl		(GObject *o);
static void		gb_tree_view_selection_changed_cb	(GtkTreeSelection *treeselection, GbTreeView *e);
static void		gb_tree_view_selection_add_selected	(GtkTreeModel *model, GtkTreePath *path, 
								 GtkTreeIter *iter, gpointer data);

static gboolean 	gb_tree_view_treeview_button_press_event_cb (GtkWidget *widget, 
								     GdkEventButton *event, 
								     GbTreeView *e);
static void 		gb_tree_view_ensure_selected_path	(GtkTreeSelection *sel, 
								 GtkTreePath *path);


enum
{
	TARGET_GTK_TREE_MODEL_ROW
};
static GtkTargetEntry tree_view_row_targets[] = {
	{ "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, TARGET_GTK_TREE_MODEL_ROW }
};

static gpointer gtk_tree_view_class;

/* signals enums and ids */
enum GbTreeviewSignalsEnum {
	GB_TREE_VIEW_BOOKMARK_ACTIVATED,
	GB_TREE_VIEW_BOOKMARK_DOUBLE_CLICKED,
	GB_TREE_VIEW_BOOKMARK_CHECK_TOGGLED,
	GB_TREE_VIEW_LAST_SIGNAL
};
static gint GbTreeViewSignals[GB_TREE_VIEW_LAST_SIGNAL];

/**
 * TreeView object
 */

MAKE_GET_TYPE (gb_tree_view, "GbTreeView", GbTreeView, gb_tree_view_class_init, 
	       gb_tree_view_init, GTK_TYPE_TREE_VIEW);

static void
gb_tree_view_class_init (GbTreeViewClass *klass)
{
	G_OBJECT_CLASS (klass)->finalize = gb_tree_view_finalize_impl;

	GbTreeViewSignals[GB_TREE_VIEW_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 (GbTreeViewClass, bookmark_activated), 
		NULL, NULL, 
		g_cclosure_marshal_VOID__POINTER,
		G_TYPE_NONE, 1, G_TYPE_POINTER);
	GbTreeViewSignals[GB_TREE_VIEW_BOOKMARK_DOUBLE_CLICKED] = g_signal_new (
		"bookmark-double-clicked", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (GbTreeViewClass, bookmark_double_clicked), 
		gul_boolean_handled_accumulator, NULL, 
		galeon_marshal_BOOLEAN__OBJECT,
		G_TYPE_BOOLEAN, 1, GB_TYPE_BOOKMARK);
	GbTreeViewSignals[GB_TREE_VIEW_BOOKMARK_CHECK_TOGGLED] = g_signal_new (
		"bookmark-check-toggled", G_OBJECT_CLASS_TYPE (klass),  
		G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST | G_SIGNAL_RUN_CLEANUP,
                G_STRUCT_OFFSET (GbTreeViewClass, bookmark_check_toggled), 
		NULL, NULL, 
		galeon_marshal_VOID__OBJECT,
		G_TYPE_NONE, 1, GB_TYPE_BOOKMARK);

	gtk_tree_view_class = g_type_class_peek_parent (klass);
}

static void
gb_tree_view_check_toggled_cb (GtkCellRendererToggle *cell_renderer_toggle, const gchar *spath, GbTreeView *tv)
{
	GtkTreePath *path = gtk_tree_path_new_from_string (spath);
	GtkTreeIter iter;
	GbTreeModel *model = (GbTreeModel *) gtk_tree_view_get_model (GTK_TREE_VIEW (tv));

	g_return_if_fail (model && GB_IS_TREE_MODEL (model));

	LOG ("Toggled %s", spath);

	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
	{
		GbBookmark *b = gb_tree_model_bookmark_from_iter (model, &iter);
		if (b)
		{
			g_signal_emit (tv, GbTreeViewSignals[GB_TREE_VIEW_BOOKMARK_CHECK_TOGGLED], 
				       0, b, 0);
		}
	}
	gtk_tree_path_free (path);
}

static  void
gb_tree_view_treeview_row_activated_cb (GtkTreeView *tree_view,
					GtkTreePath *path,
					GtkTreeViewColumn *column,
					GbTreeView *tv)
{
	GbTreeViewPrivate *p = tv->priv;
	GtkTreeSelection *sel = gtk_tree_view_get_selection (tree_view);
	GSList *selected;
	GSList *li;
	
	gb_tree_view_ensure_selected_path (sel, path);

	selected = g_slist_copy (p->selected);

	for (li = selected; li; li = li->next)
	{
		GbBookmark *b = li->data;
		gboolean return_val = FALSE;
		
		g_signal_emit (tv, GbTreeViewSignals[GB_TREE_VIEW_BOOKMARK_DOUBLE_CLICKED], 
			       0, b, &return_val);
				
		if (return_val)
		{
			continue;
		}
		
		if (p->double_click_action == GB_TV_EDIT)
		{
			GbSingleEditor *e = gb_single_editor_new ();
					gb_single_editor_set_bookmark (e, b);
					gb_single_editor_show (e);
		}
		else if (p->double_click_action == GB_TV_ACTIVATE)
		{
			const gchar *url = GB_IS_SITE (b) ? GB_SITE (b)->url : NULL;
			gb_activated_activate (tv, b, url,
					       GB_BAF_DEFAULT);
		}
		else if (p->double_click_action == GB_TV_ACTIVATE_NEW_WINDOW)
		{
			const gchar *url = GB_IS_SITE (b) ? GB_SITE (b)->url : NULL;
			gb_activated_activate (tv, b, url,
					       GB_BAF_NEW_WINDOW);
		}
	}
	g_slist_free (selected);
}

static void 
gb_tree_view_init (GbTreeView *tv)
{
	GbTreeViewPrivate *p = g_new0 (GbTreeViewPrivate, 1);
	GtkTreeSelection *s;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;

	tv->priv = p;

	p->location_visible = TRUE;
	p->check_visible = FALSE;
	p->autoexpand_roots = TRUE;
	
	s = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
	gtk_tree_selection_set_mode (s, GTK_SELECTION_MULTIPLE);
	g_signal_connect (s, "changed", G_CALLBACK (gb_tree_view_selection_changed_cb), tv);
	
	g_signal_connect (tv, "button_press_event",
			  G_CALLBACK (gb_tree_view_treeview_button_press_event_cb), tv);

	g_signal_connect (tv, "row_activated",
			  G_CALLBACK (gb_tree_view_treeview_row_activated_cb), tv);

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_toggle_new ();
	g_signal_connect (renderer, "toggled", 
			  G_CALLBACK (gb_tree_view_check_toggled_cb), tv);
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_set_attributes (column, renderer,
                                             "active", GB_TREE_MODEL_COL_CHECK, 
					     NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
	p->check_column = g_object_ref (column);

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_pixbuf_new ();
	
	gtk_tree_view_column_pack_start (column, renderer, FALSE);
	gtk_tree_view_column_set_attributes (column, renderer,
                                             "pixbuf", GB_TREE_MODEL_COL_ICON, 
					     NULL);
	
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_set_attributes (column, renderer,
                                             "text", GB_TREE_MODEL_COL_TITLE, 
					     NULL);
	gtk_tree_view_column_set_title (column,  _("Title"));
	gtk_tree_view_column_set_resizable (column, TRUE);
	gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (tv), column);
	
	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_set_attributes (column, renderer,
                                             "text", GB_TREE_MODEL_COL_URL, 
					     NULL);
	gtk_tree_view_column_set_title (column,  _("Location"));
        gtk_tree_view_column_set_resizable (column, TRUE);
	gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
	p->location_column = g_object_ref (column);

	gb_tree_view_set_location_visible (tv, p->location_visible);
	gb_tree_view_set_check_visible (tv, p->check_visible);
	
	/* dnd */
	gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (tv),
						GDK_BUTTON1_MASK,
						tree_view_row_targets,
						G_N_ELEMENTS (tree_view_row_targets),
						GDK_ACTION_MOVE | GDK_ACTION_COPY);
	
	gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (tv),
					      tree_view_row_targets,
					      G_N_ELEMENTS (tree_view_row_targets),
					      GDK_ACTION_MOVE | GDK_ACTION_COPY);
	
	p->double_click_action = GB_TV_EDIT;

}

static void
gb_tree_view_finalize_impl (GObject *o)
{
	GbTreeView *tv = GB_TREE_VIEW (o);
	GbTreeViewPrivate *p = tv->priv;
	
	gb_tree_view_set_location_source (tv, NULL);
	
	g_slist_free (p->selected);
	if (p->location_column) g_object_unref (p->location_column);
	if (p->check_column) g_object_unref (p->check_column);
	g_free (p);
	
	G_OBJECT_CLASS (gtk_tree_view_class)->finalize (o);
}

GbTreeView *
gb_tree_view_new (void)
{
	GbTreeView *ret = g_object_new (GB_TYPE_TREE_VIEW, NULL);
	return ret;
}

void
gb_tree_view_set_model (GbTreeView *tv, GbTreeModel *m)
{
	GbTreeViewPrivate *p = tv->priv;
	
	if ((GtkTreeModel *) m == gtk_tree_view_get_model (GTK_TREE_VIEW (tv)))
	{
		return;
	}

	g_slist_free (p->selected);
	p->selected = NULL;

	if (m)
	{
		gtk_tree_view_set_model (GTK_TREE_VIEW (tv),
					 GTK_TREE_MODEL (m));
		
		if (p->autoexpand_roots)
		{
			/* make sure the roots are expanded */
			if (!gb_tree_model_get_hide_roots (m))
			{
				GtkTreePath *path;
				GtkTreeIter iter;
				
				path = gtk_tree_path_new_from_string ("0");
				gtk_tree_model_get_iter (GTK_TREE_MODEL (m), &iter, path);
				
				do
				{
					gtk_tree_view_expand_row (GTK_TREE_VIEW (tv), path, FALSE);
					gtk_tree_path_next (path);
				}
				while (gtk_tree_model_iter_next (GTK_TREE_MODEL (m), &iter));
				gtk_tree_path_free (path);
			}
		}
	}
	else
	{
		gtk_tree_view_set_model (GTK_TREE_VIEW (tv), NULL);
	}
}

void
gb_tree_view_set_double_click_action (GbTreeView *tv, GbTreeViewAction a)
{
	GbTreeViewPrivate *p = tv->priv;
	p->double_click_action = a;
}

static void 
gb_tree_view_ensure_selected_path (GtkTreeSelection *sel, GtkTreePath *path)
{
	if (!gtk_tree_selection_path_is_selected (sel, path))
	{
		gtk_tree_selection_unselect_all (sel);
		gtk_tree_selection_select_path (sel, path);
	}
}

static gboolean
gb_tree_view_treeview_button_press_event_cb (GtkWidget *widget, 
					     GdkEventButton *event, 
					     GbTreeView *tv)
{
	GbTreeViewPrivate *p = tv->priv;
	GtkTreeView *tree_view;
	GtkTreePath *path;
	GtkTreeSelection *sel;
	
	tree_view = GTK_TREE_VIEW (widget);
	
	if (event->window != gtk_tree_view_get_bin_window (tree_view)) 
	{
		return FALSE;
	}

	sel = gtk_tree_view_get_selection (tree_view);
	
	if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
					   &path, NULL, NULL, NULL)) 
	{
		if (event->button == 3)
		{
			GbBookmark *b;

			gb_tree_view_ensure_selected_path (sel, path);
			gtk_tree_path_free (path);

			b = p->selected && !p->selected->next ? p->selected->data : NULL;
			if (b)
			{
				gb_context_menu_quick (b, (GdkEventButton *) event, 
						       p->location_source, G_OBJECT (tv));
				return TRUE;

			}
			else if (p->selected)
			{
				/* a context menu for several bookmarks */
				gb_context_menu_several_quick (p->selected,
							       (GdkEventButton *) event, 
							       p->location_source, G_OBJECT (tv));
				return TRUE;
			}
			else
			{
				LOG ("Context menu not shown because nothing was selected.");
			}
		}
		else if (event->button == 2)
		{
			GSList *li;

			gb_tree_view_ensure_selected_path (sel, path);
			gtk_tree_path_free (path);

			for (li = p->selected; li; li = li->next)
			{
				GbBookmark *b = li->data;
				const gchar *url = GB_IS_SITE (b) ? GB_SITE (b)->url : NULL;
				gb_activated_event (tv, b, url,
						    GB_BAF_NEW_TAB_OR_WINDOW,
						    (GdkEvent *) event);
			}
			return TRUE;

		}
		else
		{
			gtk_tree_path_free (path);
		}
	} 
	else
	{
		/* Deselect if people click outside any row. It's OK to
		   let default code run; it won't reselect anything. */
		gtk_tree_selection_unselect_all (sel);
	}
	
	return FALSE;
}

void
gb_tree_view_set_location_source (GbTreeView *tv, GbLocationSource *s)
{
	GbTreeViewPrivate *p = tv->priv;
	
	if (p->location_source)
	{
		g_object_remove_weak_pointer (G_OBJECT (p->location_source),
					      (gpointer *) &p->location_source);
	}
	
	p->location_source = s;
	
	if (p->location_source)
	{
		g_object_add_weak_pointer (G_OBJECT (p->location_source), 
					   (gpointer *) &p->location_source);
	}
}

static void
gb_tree_view_selection_changed_cb (GtkTreeSelection *treeselection, GbTreeView *tv)
{
	GbTreeViewPrivate *p = tv->priv;
	
	g_slist_free (p->selected);
	p->selected = NULL;
	gtk_tree_selection_selected_foreach (treeselection, gb_tree_view_selection_add_selected, tv);

	p->selected = g_slist_reverse (p->selected);
}

static void
gb_tree_view_selection_add_selected (GtkTreeModel *model, GtkTreePath *path, 
				     GtkTreeIter *iter, gpointer data)
{
	GbTreeView *tv = data;
	GbTreeViewPrivate *p = tv->priv;
	GbBookmark *b = gb_tree_model_bookmark_from_iter (GB_TREE_MODEL (model), iter);
	p->selected = g_slist_prepend (p->selected, b);
}

void
gb_tree_view_select (GbTreeView *tv, GbBookmark *b)
{
	GSList *l = g_slist_prepend (NULL, b);
	gb_tree_view_select_list (tv, l);
	g_slist_free (l);
}

static void
gb_tree_view_expand_path (GbTreeView *tv, const GtkTreePath *path)
{
	if (gtk_tree_path_get_depth ((GtkTreePath *) path) > 0)
	{
		GtkTreePath *c = gtk_tree_path_copy (path);
		if (gtk_tree_path_up (c))
		{
			gb_tree_view_expand_path (tv, c);
		}
		else
		{
			g_assert_not_reached ();
		}
		gtk_tree_path_free (c);
	}
	gtk_tree_view_expand_row (GTK_TREE_VIEW (tv), (GtkTreePath *) path, FALSE);
}

void
gb_tree_view_select_list (GbTreeView *tv, const GSList *l)
{
	GtkTreeSelection *s = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
	GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (tv));
	GtkTreeIter it;
	const GSList *li;
	
	if (!tm)
	{
		return;
	}

	gtk_tree_selection_unselect_all (s);

	for (li = l; li; li = li->next)
	{
		GbBookmark *b = li->data;
		if (gb_tree_model_iter_from_bookmark (GB_TREE_MODEL (tm), b, &it))
		{
			GtkTreePath *path = gtk_tree_model_get_path (tm, &it);
			if (path)
			{
				GtkTreePath *origpath = gtk_tree_path_copy (path);
				if (gtk_tree_path_up (path))
				{
					gb_tree_view_expand_path (tv, path);
				}
				gtk_tree_selection_select_iter (s, &it);
				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tv), origpath, NULL, FALSE, 0.0, 0.0);
				gtk_tree_path_free (origpath);
				gtk_tree_path_free (path);
			}
		}
	}
}

void
gb_tree_view_ensure_selected (GbTreeView *tv, GbBookmark *b)
{
	GbTreeViewPrivate *p = tv->priv;
	if (g_slist_find (p->selected, b) == NULL)
	{
		gb_tree_view_select (tv, b);
	}
}

void
gb_tree_view_ensure_expanded (GbTreeView *tv, GbBookmark *b)
{
	GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (tv));
	GtkTreeIter it;
	
	if (gb_tree_model_iter_from_bookmark (GB_TREE_MODEL (tm), b, &it))
	{
		GtkTreePath *path = gtk_tree_model_get_path (tm, &it);

		if (path)
		{
			gb_tree_view_expand_path (tv, path);
			gtk_tree_path_free (path);
		}
	}
}

const GSList *
gb_tree_view_get_selected_list (GbTreeView *tv)
{
	GbTreeViewPrivate *p = tv->priv;
	return p->selected;
}

GSList *
gb_tree_view_get_selected_list_prepared (GbTreeView *tv)
{
	GbTreeViewPrivate *p = tv->priv;
	return gb_util_remove_descendants_from_list (p->selected);
}

void
gb_tree_view_set_location_visible (GbTreeView *tv, gboolean val)
{
	GbTreeViewPrivate *p = tv->priv;
	p->location_visible = val;
	if (p->location_column)
	{
		gtk_tree_view_column_set_visible (p->location_column, p->location_visible);
	}
}

void
gb_tree_view_set_check_visible (GbTreeView *tv, gboolean val)
{
	GbTreeViewPrivate *p = tv->priv;
	p->check_visible = val;
	if (p->check_column)
	{
		gtk_tree_view_column_set_visible (p->check_column, p->check_visible);
	}
}

void
gb_tree_view_set_autoexpand_roots (GbTreeView *tv, gboolean val)
{
	GbTreeViewPrivate *p = tv->priv;
	p->autoexpand_roots = val;
}
