/*
 *  Copyright (C) 2000 Marco Pesenti Gritti
 *
 *  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.
 */

#include "galeon.h"
#include "bookmarks.h"
#include "misc.h"
#include "mozilla.h"

#include <string.h>
#include <libgnome/gnome-i18n.h>
#include <gtk/gtkselection.h>
#include <gtk/gtkdnd.h>

/* Internal variable used to save last insert position in the ctree */
static gint insert_pos;

/* DnD function prototypes */
void
bookmarks_editor_ctree_drag_data_get_cb (GtkWidget *widget,
					 GdkDragContext *context,
					 GtkSelectionData *selection_data, 
					 guint info, guint time, 
					 BookmarksEditorControls *controls);
void
bookmarks_editor_ctree_drag_data_received_cb (GtkWidget *widget,
					      GdkDragContext *context,
					      gint x, gint y,
					      GtkSelectionData *selection_data,
					      guint info, guint time, 
					      BookmarksEditorControls 
					      *controls);

gboolean bookmarks_editor_ctree_drag_motion (GtkWidget *widget,
					     GdkDragContext *context,
					     gint x,
					     gint y,
					     guint time,
					     BookmarksEditorControls *controls);
void bookmarks_editor_ctree_drag_leave (GtkWidget *widget,
					GdkDragContext *context,
					guint time,
					BookmarksEditorControls *controls);
static gboolean vscroll_timeout (gint direction, BookmarksEditorControls *controls);
static gboolean vscroll_timeout_up (BookmarksEditorControls *controls);
static gboolean vscroll_timeout_down (BookmarksEditorControls *controls);
static void selection_set_string (GtkSelectionData *sd, const gchar *str);

/* Internal helper functions from gtkctree.c */
#define GTK_CLIST_CLASS_FW(_widget_) \
	GTK_CLIST_CLASS(((GtkObject*) (_widget_))->klass)

static gboolean check_drag (GtkCTree         *ctree,
			    GtkCTreeNode     *drag_source,
			    GtkCTreeNode     *drag_target,
			    GtkCListDragPos   insert_pos);
static void drag_dest_cell (GtkCList         *clist,
			    gint              x,
			    gint              y,
			    GtkCListDestInfo *dest_info);
static void drag_info_destroy (gpointer data);

/* Convenience macro for drawing the drag line */
#define DRAW_DRAG_HIGHLIGHT(_clist, _info) \
	GTK_CLIST_CLASS_FW((_clist))->draw_drag_highlight((_clist),	      \
						g_list_nth((_clist)->row_list,\
						(_info)->cell.row)->data,     \
						(_info)->cell.row,	      \
						(_info)->insert_pos)


void
bookmarks_editor_ctree_drag_leave (GtkWidget *widget,
				   GdkDragContext *context,
				   guint time,
				   BookmarksEditorControls *controls)
{
	GtkCList *clist;
	GtkCListDestInfo *dest_info;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_CLIST (widget));
	g_return_if_fail (context != NULL);

	clist = GTK_CLIST (widget);

	dest_info = g_dataset_get_data (context, "drag-dest");
	if (dest_info)
	{
		if (dest_info->cell.row >= 0 &&
		    gtk_drag_get_source_widget(context) == widget)
		{
			/* clear the drag highlight */
			if (!clist->vtimer)
				DRAW_DRAG_HIGHLIGHT (clist, dest_info);
		}
	}
	g_dataset_remove_data (context, "drag-dest");

	/* Stop autoscrolling */
	if (clist->vtimer)
	{
		g_source_remove (clist->vtimer);
		clist->vtimer = 0;
	}
}

gboolean
bookmarks_editor_ctree_drag_motion (GtkWidget *widget,
				    GdkDragContext *context,
				    gint x,
				    gint y,
				    guint time,
				    BookmarksEditorControls *controls)

{
	GtkCList *clist;
	GtkCTree *ctree;
	GtkCListDestInfo new_info, *dest_info;
	GtkCTreeNode *drag_source, *drag_target;
	gint title_y, clist_y, offset;

	g_return_val_if_fail(widget != NULL, FALSE);
	g_return_val_if_fail(GTK_IS_CTREE (widget), FALSE);

	clist = GTK_CLIST (widget);
	ctree = GTK_CTREE (widget);

	/* If dragging within the same widget, move instead of copy */
	if (widget == gtk_drag_get_source_widget(context))
		gdk_drag_status(context, GDK_ACTION_MOVE, time);
	else
		gdk_drag_status(context, context->suggested_action, time);

	dest_info = g_dataset_get_data (context, "drag-dest");
	if (!dest_info)
	{
		dest_info = g_new0 (GtkCListDestInfo, 1);
		dest_info->cell.row = -1;
		dest_info->cell.column = -1;
		dest_info->insert_pos  = GTK_CLIST_DRAG_NONE;
		insert_pos = GTK_CLIST_DRAG_NONE;
		g_dataset_set_data_full(context, "drag-dest",
					dest_info,
					drag_info_destroy);
	}

	drag_dest_cell (clist, x, y, &new_info);
	drag_source = GTK_CTREE_NODE(g_list_nth (clist->row_list,
				     clist->click_cell.row));
	drag_target = GTK_CTREE_NODE(g_list_nth (clist->row_list,
				     new_info.cell.row));

	if (widget == gtk_drag_get_source_widget (context) &&
	    !check_drag (ctree, drag_source, drag_target, new_info.insert_pos))
	{
		if (dest_info->cell.row < 0)
		{
			gdk_drag_status (context, GDK_ACTION_DEFAULT, time);
			return FALSE;
		}
		return TRUE;
	}

	if (new_info.cell.row != dest_info->cell.row ||
	    (new_info.cell.row == dest_info->cell.row &&
	     dest_info->insert_pos != new_info.insert_pos))
	{
		if (dest_info->cell.row >= 0 && !clist->vtimer)
			DRAW_DRAG_HIGHLIGHT (clist, dest_info);

		insert_pos = new_info.insert_pos;
		dest_info->insert_pos = new_info.insert_pos;
		dest_info->cell.row = new_info.cell.row;
		dest_info->cell.column = new_info.cell.column;

		if (dest_info->cell.row >= 0 && !clist->vtimer)
			DRAW_DRAG_HIGHLIGHT (clist, dest_info);
	}

	gdk_window_get_origin (clist->title_window, NULL, &title_y);
	gdk_window_get_origin (clist->clist_window, NULL, &clist_y);
	offset = clist_y - title_y;

	/* Don't scroll if we're at the first or last row */
	if (dest_info->cell.row == 0 || dest_info->cell.row == clist->rows - 1)
		return TRUE;

	if (y > (clist->clist_window_height + offset - clist->row_height + 1))
	{
		if (!clist->vtimer)
		{
			if (dest_info->cell.row != -1)
				DRAW_DRAG_HIGHLIGHT (clist, dest_info);
			clist->vtimer = g_timeout_add (50, (GSourceFunc)
						       vscroll_timeout_down,
						       controls);
		}
	}
	else if (y < (offset + clist->row_height + 1))
	{
		if (!clist->vtimer)
		{
			if (dest_info->cell.row != -1)
				DRAW_DRAG_HIGHLIGHT (clist, dest_info);
			clist->vtimer = g_timeout_add (50, (GSourceFunc)
						       vscroll_timeout_up,
						       controls);
		}
	}
	else
	{
		if (clist->vtimer)
		{
			if (dest_info->cell.row != -1)
				DRAW_DRAG_HIGHLIGHT (clist, dest_info);
			g_source_remove (clist->vtimer);
			clist->vtimer = 0;
		}
	}
	return TRUE;
}

static gboolean 
vscroll_timeout_up (BookmarksEditorControls *controls)
{
	return vscroll_timeout (-1, controls);
}

static gboolean
vscroll_timeout_down (BookmarksEditorControls *controls)
{
	return vscroll_timeout (1, controls);
}

static gboolean
vscroll_timeout (gint direction, BookmarksEditorControls *controls)
{
	GtkCList *clist = GTK_CLIST (controls->ctree);
	gfloat scroll_step, newval;

	g_return_val_if_fail (clist != NULL, FALSE);

	scroll_step = (clist->row_height / 2) + 1;

	if (direction < 0)
	{
		newval = clist->vadjustment->value - scroll_step;
		if (newval < 0)
		{
			gtk_adjustment_set_value (clist->vadjustment, 0);
			return FALSE;
		}
		else
		{
			gtk_adjustment_set_value (clist->vadjustment, newval);
		}
	}
	else
	{
		float limit = clist->vadjustment->upper -
			      clist->vadjustment->page_size;

		newval = clist->vadjustment->value + scroll_step;
		if (newval > limit)
		{
			gtk_adjustment_set_value(clist->vadjustment, limit);
			return FALSE;
		}
		else
		{
			gtk_adjustment_set_value(clist->vadjustment, newval);
		}
	}
	return TRUE;
}

void
bookmarks_editor_ctree_drag_data_get_cb (GtkWidget *widget,
					 GdkDragContext *context,
					 GtkSelectionData *selection_data, 
					 guint info, guint time, 
					 BookmarksEditorControls *controls)
{
	if (controls->last_pressed) 
	{
		gchar *mem = NULL;
		switch (info) {
		case DND_TARGET_GALEON_BOOKMARK:
			mem = bookmarks_item_to_string 
				(controls->last_pressed);
			selection_set_string (selection_data, mem);
			break;
		case DND_TARGET_STRING:
			if (((controls->last_pressed->type == BM_AUTOBOOKMARKS)
			     || (controls->last_pressed->type == BM_FOLDER))
			    && (controls->last_pressed->name)
			    && (strlen (controls->last_pressed->name) > 0)) 
			{
				selection_set_string 
					(selection_data,
					 controls->last_pressed->name);
				break;
			} /* if not a folder, pass url instead of name */
		case DND_TARGET_NETSCAPE_URL:
		case DND_TARGET_GALEON_URL:
			if ((controls->last_pressed->url)
			    && (strlen (controls->last_pressed->url) > 0)) 
			{
				selection_set_string 
					(selection_data, 
					 controls->last_pressed->url);
			}
			break;
		case DND_TARGET_TEXT_URI_LIST:
			if (controls->last_pressed->type == BM_FOLDER) 
			{
				GList *l;
				gchar *text = g_strdup ("");
				for (l = controls->last_pressed->list; 
				     l != NULL; 
				     l = g_list_next (l)) {
					BookmarkItem *item = l->data;
					if (item->url 
					    && (strlen (item->url) > 0)) 
					{
						gchar *prevtext = text;
						text = g_strconcat (text,
								    item->url, 
								    "\r\n",
								    NULL);
						g_free (prevtext);
					}
				}
				selection_set_string (selection_data, text);
				g_free (text);
			} 
			else if (controls->last_pressed->url) 
			{
				gchar *text = g_strconcat 
					(controls->last_pressed->url, "\r\n",
					 NULL);
				selection_set_string (selection_data, text);
				g_free (text);
			}
			break;
		default:
			g_warning ("Unknown DND type");
			break;
		}
		/* gtk_selection_data_set copies the data */
		if (mem) g_free (mem);
	} 
}

void
bookmarks_editor_ctree_drag_data_received_cb (GtkWidget *widget,
					      GdkDragContext *context,
					      gint x, gint y,
					      GtkSelectionData *selection_data,
					      guint info, guint time, 
					      BookmarksEditorControls
					      *controls)
{
	GtkCTree *ctree;
	GtkCList *clist;
	gchar *s, *t;
	GtkCListDestInfo dest_info;
	char **tmp;
	 
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_CTREE (widget));
	g_return_if_fail (context != NULL);
	g_return_if_fail (selection_data != NULL);

	ctree = GTK_CTREE (widget);
	clist = GTK_CLIST (widget);
	
	/* Get the destination node info */
	drag_dest_cell (clist, x, y, &dest_info);
	
	/* use the saved insert position, since the mouse position
	   may have moved after the button was released */
	dest_info.insert_pos = insert_pos;

	/* check if this is a reordering drag */
	if (gtk_drag_get_source_widget(context) == widget)
	{
		GList *selections, *selections_sorted;
		GtkCTreeNode *source_node, *dest_node;

		if (clist->click_cell.row < 0)
		{
			return;
		}

		selections_sorted = bookmarks_get_selections_sorted (clist);

		/* move the first selection to the insertion point */

		source_node = GTK_CTREE_NODE (selections_sorted->data);
		dest_node = GTK_CTREE_NODE (g_list_nth (clist->row_list,
							dest_info.cell.row));

		if (!source_node || !dest_node
		    || !check_drag (ctree, source_node, dest_node,
				    dest_info.insert_pos))
		{
			return;
		}

		/* Just moving the tree nodes is enough.
		   bookmarks_editor_ctree_tree_move_cb will take care of 
		   everything */
		switch (dest_info.insert_pos)
		{
		case GTK_CLIST_DRAG_NONE:
			break;
		case GTK_CLIST_DRAG_INTO:
			gtk_ctree_move(ctree, source_node, dest_node,
				       GTK_CTREE_ROW(dest_node)->children);
			break;
		case GTK_CLIST_DRAG_BEFORE:
			gtk_ctree_move(ctree, source_node,
				       GTK_CTREE_ROW(dest_node)->parent,
						     dest_node);
			break;
		case GTK_CLIST_DRAG_AFTER:
			gtk_ctree_move(ctree, source_node,
				       GTK_CTREE_ROW(dest_node)->parent, 
				       GTK_CTREE_ROW(dest_node)->sibling);
			break;
		}

		/* insert the rest of the selections */
		selections = selections_sorted->next;
		while (selections)
		{
			dest_node = source_node;
			source_node = GTK_CTREE_NODE(selections->data);

			if (!source_node || !dest_node)
			{
				return;
			}

			gtk_ctree_move (ctree, source_node,
					GTK_CTREE_ROW(dest_node)->parent, 
					GTK_CTREE_ROW(dest_node)->sibling);
			selections = selections->next;
		}
		g_list_free(selections_sorted);
	}
	else
	{
		BookmarkItem *b = NULL;
		BookmarkItem *near = NULL;
		gint row = -1;
		gchar* utfstr;
		
		switch(info)
		{
		case DND_TARGET_GALEON_BOOKMARK:
			b = bookmarks_item_from_string (selection_data->data);
			break;
		case DND_TARGET_TEXT_URI_LIST:
			utfstr = mozilla_locale_to_utf8(_("New folder"));
			b = bookmarks_new_bookmark 
				(BM_FOLDER, TRUE, utfstr, NULL,
				 NULL, NULL, NULL);
			g_free(utfstr);
			s = selection_data->data;
			while ((t = strstr (s, "\r\n"))) {
				gchar *url = g_strndup (s, t-s);
				BookmarkItem *b2 = bookmarks_new_bookmark 
					(BM_SITE, TRUE, NULL, url, NULL,
					 NULL, NULL);
				b2->parent = b;
				b->list = g_list_append (b->list, b2);
				g_free (url);
				s = t+1;
			};
			break;
		case DND_TARGET_NETSCAPE_URL:
			tmp = g_strsplit ((gchar *) selection_data->data, 
					  "\n", 2);
			if (tmp && tmp[0])
			{
				b = bookmarks_new_bookmark (BM_SITE, TRUE,
							    tmp[1], tmp[0], 
							    NULL, NULL, NULL);
			}
			else
			{
				b = bookmarks_new_bookmark 
					(BM_SITE, TRUE, NULL,
					 (gchar *) selection_data->data,
					 NULL, NULL, NULL);
			}
			g_strfreev (tmp);
			break;
		case DND_TARGET_GALEON_URL:
		case DND_TARGET_STRING:
			b = bookmarks_new_bookmark 
				(BM_SITE, TRUE, NULL, 
				 selection_data->data, NULL, NULL, NULL);
			break;
		default:
			g_warning ("Unknown DND type");
			break;
		}
		
		if (!b)
			return;

		near = gtk_ctree_node_get_row_data 
			(GTK_CTREE (controls->ctree), gtk_ctree_node_nth
			 (GTK_CTREE (widget), dest_info.cell.row));
		if (!near) near = controls->root_bookmark;
		
		/* this happens if the bookmarks clist is empty */
		if (dest_info.insert_pos == GTK_CLIST_DRAG_NONE)
			dest_info.insert_pos = GTK_CLIST_DRAG_INTO;

		bookmarks_insert_bookmark (b, near, dest_info.insert_pos);
		bookmarks_update_alias (b->parent);
		bookmarks_editor_place_tree_items (b);
		bookmarks_toolbars_check_update (b);
		bookmarks_toolbars_check_update (near);
		
		/* set the focus so it matches the selection */
		if (gtk_ctree_node_nth (GTK_CTREE (controls->ctree),
				        dest_info.cell.row)
		    != bookmarks_get_tree_item (b, controls))
		{
			if (GTK_CTREE_NODE_NEXT (bookmarks_get_tree_item 
						 (b, controls)) == NULL)
				row = g_list_length
					(g_list_first
					 ((GList *) bookmarks_get_tree_item
					  (b, controls))) - 1;
			else
				row = g_list_position
					(g_list_first
					 ((GList *)bookmarks_get_tree_item 
					  (b, controls)),
					 (GList *) bookmarks_get_tree_item
					 (b, controls));
		}
		if (row >= -1)
			GTK_CLIST(controls->ctree)->focus_row = row;
		gtk_ctree_select (GTK_CTREE (controls->ctree),
				  bookmarks_get_tree_item (b, controls));
		bookmarks_toolbars_check_update (b);
	}
	bookmarks_dirty = TRUE;
}

/* CTree reordering does not work if DnD is enabled, since the reordering
   code also uses drag and drop.  These routines were taken from gtkctree.c
   (with minor modifications) so we can use DnD to drag bookmarks from
   the bookmarks editor as well as reorder the CTree */
#define CELL_SPACING	1
#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
				    (((row) + 1) * CELL_SPACING) + \
				    (clist)->voffset)
#define ROW_FROM_YPIXEL(clist, y)  (((y) - (clist)->voffset) / \
				    ((clist)->row_height + CELL_SPACING))

static void
drag_info_destroy (gpointer data)
{
	GtkCListDestInfo *info = data;

	g_free (info);
}

static void
drag_dest_cell (GtkCList         *clist,
		gint              x,
		gint              y,
		GtkCListDestInfo *dest_info)
{
	GtkWidget *widget;

	widget = GTK_WIDGET(clist);

	dest_info->insert_pos = GTK_CLIST_DRAG_NONE;

	y -= (GTK_CONTAINER(widget)->border_width +
	      widget->style->klass->ythickness +
	      clist->column_title_area.height);
	
	dest_info->cell.row = ROW_FROM_YPIXEL(clist, y);

	if (dest_info->cell.row >= clist->rows)
	{
		dest_info->cell.row = clist->rows - 1;
		y = ROW_TOP_YPIXEL(clist, dest_info->cell.row) 
			+ clist->row_height;
	}
	if (dest_info->cell.row < -1)
		dest_info->cell.row = -1;

	dest_info->cell.column = 0;

	if (dest_info->cell.row >= 0)
	{
		gint y_delta;
		gint h = 0;

		y_delta = y - ROW_TOP_YPIXEL (clist, dest_info->cell.row);
      
		if (GTK_CLIST_DRAW_DRAG_RECT(clist) &&
		    !GTK_CTREE_ROW (g_list_nth (clist->row_list,
				    dest_info->cell.row))->is_leaf)
		{
			dest_info->insert_pos = GTK_CLIST_DRAG_INTO;
			h = clist->row_height / 4;
		}
		else if (GTK_CLIST_DRAW_DRAG_LINE(clist))
		{
			dest_info->insert_pos = GTK_CLIST_DRAG_BEFORE;
			h = clist->row_height / 2;
		}

		if (GTK_CLIST_DRAW_DRAG_LINE(clist))
		{
			if (y_delta < h)
			{
				dest_info->insert_pos = GTK_CLIST_DRAG_BEFORE;
			}
			else if (clist->row_height - y_delta < h)
			{
				dest_info->insert_pos = GTK_CLIST_DRAG_AFTER;
			}
		}
	}
}

static gboolean
check_drag (GtkCTree        *ctree,
	    GtkCTreeNode    *drag_source,
	    GtkCTreeNode    *drag_target,
	    GtkCListDragPos  insert_pos)
{
	g_return_val_if_fail(ctree != NULL, FALSE);
	g_return_val_if_fail(GTK_IS_CTREE(ctree), FALSE);

	if (drag_source && drag_source != drag_target &&
	    (!GTK_CTREE_ROW(drag_source)->children ||
	     !gtk_ctree_is_ancestor(ctree, drag_source, drag_target)))
	{
		switch (insert_pos)
		{
		case GTK_CLIST_DRAG_NONE:
			return FALSE;
		case GTK_CLIST_DRAG_AFTER:
			if (GTK_CTREE_ROW(drag_target)->sibling != drag_source)
				return (!ctree->drag_compare 
					|| ctree->drag_compare
					(ctree,
					 drag_source,
					 GTK_CTREE_ROW(drag_target)->parent,
					 GTK_CTREE_ROW(drag_target)->sibling));
			break;
		case GTK_CLIST_DRAG_BEFORE:
			if (GTK_CTREE_ROW (drag_source)->sibling 
			    != drag_target)
				return (!ctree->drag_compare 
					|| ctree->drag_compare
					(ctree,
					 drag_source,
					 GTK_CTREE_ROW(drag_target)->parent,
					 drag_target));
			break;
		case GTK_CLIST_DRAG_INTO:
			if (!GTK_CTREE_ROW (drag_target)->is_leaf 
			    && GTK_CTREE_ROW (drag_target)->children 
			    != drag_source)
				return (!ctree->drag_compare
					|| ctree->drag_compare
					(ctree,
					 drag_source,
					 drag_target,
					 GTK_CTREE_ROW 
					 (drag_target)->children));
			break;
		}
	}
	return FALSE;
}

static void 
selection_set_string (GtkSelectionData *sd, const gchar *str)
{
	gtk_selection_data_set (sd, sd->target, 8, str, strlen (str));
}
