/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 - 2004 Takuro Ashie
 *
 *  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 /* HAVE_CONFIG_H */

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

#include "kazehakase.h"
#include "kz-root-bookmark.h"
#include "kz-bookmark-bar.h"
#include "kz-bookmark-menu.h"
#include "kz-bookmark-item.h"
#include "kz-bookmark.h"
#include "kz-bookmark-file.h"
#include "kz-actions.h"
#include "utils.h"
#include "glib-utils.h"

typedef struct _KzBookmarkBarPrivate	KzBookmarkBarPrivate;
struct _KzBookmarkBarPrivate
{
	GtkWidget *dragged_item;
};
#define KZ_BOOKMARK_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_BOOKMARK_BAR, KzBookmarkBarPrivate))

enum {
	PROP_0,
	PROP_KZ_WINDOW,
	PROP_BOOKMARK_LIST
};

enum {
	TARGET_KAZEHAKASE_BOOKMARKS,
	TARGET_NETSCAPE_URL,
	TARGET_TEXT_URI_LIST
};

static GtkTargetEntry url_drag_types [] = 
{
	{"_KAZEHAKASE_BOOKMARKS", 0, TARGET_KAZEHAKASE_BOOKMARKS},
        { "_NETSCAPE_URL",        0, TARGET_NETSCAPE_URL},
	{ "text/uri-list",        0, TARGET_TEXT_URI_LIST}
};

static const guint n_url_drag_types = G_N_ELEMENTS (url_drag_types);

static GObject* constructor  (GType           type,
			      guint           n_props,
			      GObjectConstructParam *props);
static void     dispose      (GObject *object);
static void     set_property (GObject       *object,
			      guint          prop_id,
			      const GValue  *value,
			      GParamSpec    *pspec);
static void     get_property (GObject       *object,
			      guint          prop_id,
			      GValue        *value,
			      GParamSpec    *pspec);

static gboolean button_release (GtkWidget      *widget,
			        GdkEventButton *event);
static void     drag_data_received
			     (GtkWidget *widget,
			      GdkDragContext *context,
			      gint x, gint y,
			      GtkSelectionData *data,
			      guint info,
			      guint time);
static void     drag_leave   (GtkWidget *widget,
			      GdkDragContext *context,
			      guint time);
static gboolean drag_motion  (GtkWidget *widget,
			      GdkDragContext *context,
			      gint x, gint y,
			      guint time);

static void     kz_bookmark_bar_refresh_all  (KzBookmarkBar *bar);

static void     kz_bookmark_bar_connect_signal    (KzBookmarkBar *bar);
static void     kz_bookmark_bar_disconnect_signal (KzBookmarkBar *bar);


static void     cb_bookmark_list_updated         (KzBookmark *folder,
						  KzBookmarkBar *bar);
static void     cb_bookmark_list_remove_child    (KzBookmark *folder,
						  KzBookmark *child,
						  KzBookmarkBar *bar);
static void     cb_bookmark_list_insert_child  	 (KzBookmark *folder,
						  KzBookmark *child,
						  KzBookmark *sibling,
						  KzBookmarkBar *bar);

static GtkToolItem *create_tool_item (KzBookmarkBar *bar,
				      KzBookmark *child);

G_DEFINE_TYPE(KzBookmarkBar, kz_bookmark_bar, GTK_TYPE_EVENT_BOX)

static void
kz_bookmark_bar_class_init (KzBookmarkBarClass *klass)
{
	GObjectClass *gobject_class;
	GtkWidgetClass *widget_class;

	gobject_class = (GObjectClass *) klass;
	widget_class  = (GtkWidgetClass *) klass;

	/* GObject class */
	gobject_class->constructor  = constructor;
	gobject_class->dispose      = dispose;
	gobject_class->set_property = set_property;
	gobject_class->get_property = get_property;

	widget_class->button_release_event = button_release;
	widget_class->drag_data_received   = drag_data_received;
	widget_class->drag_leave           = drag_leave;
	widget_class->drag_motion          = drag_motion;

	g_object_class_install_property
		(gobject_class,
		 PROP_KZ_WINDOW,
		 g_param_spec_object ("kz-window",
				      _("KzWindow"),
				      _("The parent kazehakase window"),
				      KZ_TYPE_WINDOW,
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property
		(gobject_class,
		 PROP_BOOKMARK_LIST,
		 g_param_spec_object ("bookmark-folder",
				      _("BookmarkFolder"),
				      _("Bookmarks list to show"),
				      KZ_TYPE_BOOKMARK,
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT_ONLY));
	g_type_class_add_private(gobject_class, sizeof(KzBookmarkBarPrivate));
}


static void
kz_bookmark_bar_init (KzBookmarkBar *bar)
{
	KzBookmarkBarPrivate *priv = KZ_BOOKMARK_BAR_GET_PRIVATE (bar);
	bar->toolbar = gtk_toolbar_new();
	bar->kz      = NULL;
	bar->folder  = NULL;
	
	priv->dragged_item = GTK_WIDGET(gtk_separator_tool_item_new());
	g_object_ref(priv->dragged_item);

	gtk_container_add(GTK_CONTAINER(bar), bar->toolbar);
	gtk_widget_show(bar->toolbar);

	gtk_drag_dest_set(GTK_WIDGET(bar), 
			  GTK_DEST_DEFAULT_ALL, 
                          url_drag_types, n_url_drag_types,
			  GDK_ACTION_LINK | GDK_ACTION_MOVE);
}


static void
dispose (GObject *object)
{
	KzBookmarkBar *bar = KZ_BOOKMARK_BAR(object);
	KzBookmarkBarPrivate *priv = KZ_BOOKMARK_BAR_GET_PRIVATE (bar);

	if (bar->folder)
	{
		kz_bookmark_bar_disconnect_signal(bar);
		g_object_unref(bar->folder);
		bar->folder = NULL;
	}

	if (bar->kz)
	{
		g_object_unref(bar->kz);
		bar->kz = NULL;
	}

	if (priv->dragged_item)
	{
		gtk_widget_destroy(GTK_WIDGET(priv->dragged_item));
		g_object_unref(priv->dragged_item);
		priv->dragged_item = NULL;
	}

	if (G_OBJECT_CLASS (kz_bookmark_bar_parent_class)->dispose)
		G_OBJECT_CLASS (kz_bookmark_bar_parent_class)->dispose(object);
}


static GObject*
constructor (GType                  type,
             guint                  n_props,
             GObjectConstructParam *props)
{
	KzBookmarkBar *bar;

	GObject *object;
	GObjectClass *klass = G_OBJECT_CLASS(kz_bookmark_bar_parent_class);

	object = klass->constructor(type, n_props, props);

	bar = KZ_BOOKMARK_BAR(object);

	kz_bookmark_bar_refresh_all(bar);
	return object;
}


static void
set_property (GObject         *object,
              guint            prop_id,
              const GValue    *value,
              GParamSpec      *pspec)
{
	KzBookmarkBar *bar = KZ_BOOKMARK_BAR(object);
  
	switch (prop_id)
	{
	case PROP_KZ_WINDOW:
		bar->kz = g_object_ref(g_value_get_object(value));
		break;
	case PROP_BOOKMARK_LIST:
		bar->folder = g_object_ref(g_value_get_object(value));
		kz_bookmark_bar_connect_signal(bar);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
get_property (GObject         *object,
              guint            prop_id,
              GValue          *value,
              GParamSpec      *pspec)
{
	KzBookmarkBar *bar = KZ_BOOKMARK_BAR(object);

	switch (prop_id)
	{
	case PROP_KZ_WINDOW:
		g_value_set_object(value, bar->kz);
		break;
	case PROP_BOOKMARK_LIST:
		g_value_set_object(value, bar->folder);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static gboolean
button_release (GtkWidget *widget, GdkEventButton *event)
{
	KzBookmarkBar *bar;

	bar = KZ_BOOKMARK_BAR(widget);

	switch (event->button)
	{
	case 1:
		break;
	case 2:
		break;
	case 3:
		kz_actions_popup_bookmark_menu_modal(bar->kz,
						     bar->folder,
						     event->button,
						     event->time);
		return TRUE;
		break;
	default:
		break;
	}
	return FALSE;
}


GtkWidget *
kz_bookmark_bar_new (KzWindow *kz, KzBookmark *folder)
{
	GObject *obj;

	g_return_val_if_fail(KZ_IS_BOOKMARK(folder), NULL);
	g_return_val_if_fail(kz_bookmark_is_folder(folder), NULL);

	obj = g_object_new(KZ_TYPE_BOOKMARK_BAR,
			   "kz-window",       kz,
			   "bookmark-folder", folder,
			   NULL);

	return GTK_WIDGET(obj);
}


static void
remove_item (GtkWidget *child, gpointer data)
{
	gtk_container_remove(GTK_CONTAINER(child->parent), child);
}


static void
kz_bookmark_bar_refresh_all (KzBookmarkBar *bar)
{
	GList *children, *node;

	gtk_container_foreach (GTK_CONTAINER (GTK_TOOLBAR(bar->toolbar)),
			       remove_item, NULL);

	children = kz_bookmark_get_children(bar->folder);
	for (node = children; node; node = g_list_next(node))
	{
		GtkToolItem *toolitem;
		KzBookmark *bookmark = node->data;
		
		toolitem = create_tool_item(bar, bookmark);
		gtk_toolbar_insert(GTK_TOOLBAR(bar->toolbar),
				   toolitem, -1);
	}
	g_list_free(children);

	gtk_widget_queue_resize(GTK_WIDGET(bar));
}


static void
kz_bookmark_bar_connect_signal (KzBookmarkBar *bar)
{
	g_signal_connect(bar->folder, "children-reordered",
			 G_CALLBACK(cb_bookmark_list_updated),
			 bar);	
	g_signal_connect(bar->folder, "insert-child",
			 G_CALLBACK(cb_bookmark_list_insert_child),
			 bar);
	g_signal_connect(bar->folder, "remove-child",
			 G_CALLBACK(cb_bookmark_list_remove_child),
			 bar);	
}


static void
kz_bookmark_bar_disconnect_signal (KzBookmarkBar *bar)
{
	g_signal_handlers_disconnect_by_func
		(bar->folder,
		 G_CALLBACK(cb_bookmark_list_updated), bar);
	g_signal_handlers_disconnect_by_func
		(bar->folder,
		 G_CALLBACK(cb_bookmark_list_insert_child), bar);
	g_signal_handlers_disconnect_by_func
		(bar->folder,
		 G_CALLBACK(cb_bookmark_list_remove_child), bar);
}


static void
drag_data_received (GtkWidget *widget,
                    GdkDragContext *context,
                    gint x, gint y,
                    GtkSelectionData *data,
                    guint info,
                    guint time)
{
	KzBookmarkBar *bar;
	KzBookmark *bookmark, *sibling, *parent;
	gchar **strings = NULL; 
	GtkWidget *src_widget;
	const gchar *title;
	gchar *utf8_title = NULL;
	GList *children;
	gint index;

	bar = KZ_BOOKMARK_BAR(widget);
	index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(bar->toolbar),
					   x, y);

	children = kz_bookmark_get_children(bar->folder);
	sibling = g_list_nth_data(children, index);
	g_list_free(children);

	switch (info)
	{
	 case TARGET_KAZEHAKASE_BOOKMARKS:
		src_widget = gtk_drag_get_source_widget(context);
		
		if (!KZ_IS_BOOKMARK_ITEM(src_widget))
			break;

		bookmark = KZ_BOOKMARK_ITEM(src_widget)->bookmark;
		if (bookmark == sibling)
			break;
	
		g_object_ref(bookmark);

		parent = kz_bookmark_get_parent(bookmark);
		kz_bookmark_remove(parent, bookmark);

		if (sibling)
		{
			kz_bookmark_insert_before(bar->folder,
						  bookmark,
						  sibling);
		}
		else
		{
			kz_bookmark_append(bar->folder, bookmark);
		}
		kz_bookmark_file_save(KZ_BOOKMARK_FILE(bar->folder));
		gtk_drag_finish(context, TRUE, TRUE, time);
	 	break;
	 case TARGET_NETSCAPE_URL:
	 case TARGET_TEXT_URI_LIST:
		if (data->length < 0) return;
		strings = g_strsplit((const gchar*)data->data, "\n", 2);
		if (!strings) return;
		if (strings[1] != NULL)
		{
			utf8_title = g_locale_to_utf8(strings[1],
						      strlen(strings[1]),
						      NULL,
						      NULL,
						      NULL);
		}
		if (utf8_title && g_utf8_validate(utf8_title, -1, NULL))
			title = utf8_title;
		else 
			title = _("title");
		
		/* FIXME! we should also add normal bookmark. */
		/* Create New KzBookmark */
		if (strstr(strings[0], "xml") ||
		    strstr(strings[0], "rss") ||
		    strstr(strings[0], "rdf"))
		{
			bookmark = KZ_BOOKMARK(kz_bookmark_file_new(strings[0],
							title,
							NULL));
		}
		else
		{
			bookmark = kz_bookmark_new_with_attrs(title,
							      strings[0],
							      NULL);
		}
		if (sibling)
		{
			kz_bookmark_insert_before(bar->folder,
						  bookmark,
						  sibling);
		}
		else
		{
			kz_bookmark_append(bar->folder, bookmark);
		}
		g_strfreev(strings);
		if (utf8_title)
			g_free(utf8_title);
		g_object_unref(bookmark);
		kz_bookmark_file_save(KZ_BOOKMARK_FILE(bar->folder));
		gtk_drag_finish(context, TRUE, FALSE, time);
		break;
	 default:
	 	gtk_drag_finish(context, FALSE, FALSE, time);
		break;
	}
}

static void
drag_leave   (GtkWidget *widget,
              GdkDragContext *context,
              guint time)
{
	KzBookmarkBar *bar;

	bar = KZ_BOOKMARK_BAR(widget);
	gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bar->toolbar),
					    NULL, 0);
}


static gboolean 
drag_motion (GtkWidget *widget,
             GdkDragContext *context,
             gint x, gint y,
             guint time)
{
	KzBookmarkBar *bar;
	KzBookmarkBarPrivate *priv = KZ_BOOKMARK_BAR_GET_PRIVATE (widget);
	gint index;

	bar = KZ_BOOKMARK_BAR(widget);
	index = gtk_toolbar_get_drop_index(GTK_TOOLBAR(bar->toolbar),
					   x, y);

	gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(bar->toolbar),
					    GTK_TOOL_ITEM(priv->dragged_item), index);
	
	gdk_drag_status(context, context->suggested_action, time);

	return TRUE;
}


static GtkToolItem *
create_tool_item (KzBookmarkBar *bar, KzBookmark *child)
{
	GtkToolItem *toolitem;
	
	g_return_val_if_fail(KZ_IS_BOOKMARK(child), NULL);
	g_return_val_if_fail(KZ_IS_BOOKMARK_BAR(bar), NULL);

	if (kz_bookmark_is_separator(child))
		toolitem = gtk_separator_tool_item_new();
	else
		toolitem = kz_bookmark_item_new(bar->kz, child);
	
	gtk_widget_show(GTK_WIDGET(toolitem));
	return toolitem;	
}


static void
cb_bookmark_list_updated (KzBookmark *folder, KzBookmarkBar *bar)
{
	g_return_if_fail(KZ_IS_BOOKMARK_BAR(bar));

	kz_bookmark_bar_refresh_all(bar);
}


static void
cb_bookmark_list_insert_child  (KzBookmark *folder,
				KzBookmark *child,
				KzBookmark *sibling,
				KzBookmarkBar *bar)
{
	gint index = -1;
	GList *children;
	GtkToolItem *toolitem;
	
	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(KZ_IS_BOOKMARK_BAR(bar));

	if (sibling)
	{
		children = kz_bookmark_get_children(folder);
		index = g_list_index(children, sibling); 
		g_list_free(children);
	}

	toolitem = create_tool_item(bar, child);
	
	gtk_toolbar_insert(GTK_TOOLBAR(bar->toolbar),
			   toolitem, index);
}


static void
cb_bookmark_list_remove_child  (KzBookmark *folder,
				KzBookmark *child,
				KzBookmarkBar *bar)
{
	gint index;
	GList *children;
	GtkToolItem *toolitem;

	g_return_if_fail(KZ_IS_BOOKMARK(child));
	g_return_if_fail(KZ_IS_BOOKMARK_BAR(bar));

	children = kz_bookmark_get_children(folder);
	index = g_list_index(children, child); 
	g_list_free(children);

	if (index == -1) return;
	
	toolitem = gtk_toolbar_get_nth_item(GTK_TOOLBAR(bar->toolbar),
					    index);
	if (!toolitem) return;
	gtk_widget_destroy(GTK_WIDGET(toolitem));
}

