/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
    plugin.c
    Copyright (C) 2000 Naba Kumar

    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 of the License, 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 <libanjuta/interfaces/ianjuta-editor.h>
#include <libanjuta/interfaces/ianjuta-editor-selection.h>
#include <libanjuta/interfaces/ianjuta-document-manager.h>
#include <libanjuta/anjuta-shell.h>
#include <libanjuta/anjuta-debug.h>

#include <libegg/menu/egg-entry-action.h>

#include "plugin.h"
#include "search-replace.h"
#include "search-replace_backend.h"
#include "config.h"

#define UI_FILE PACKAGE_DATA_DIR"/ui/anjuta-search.ui"
#define ICON_FILE "anjuta-search.png"

/* Find next occurence of expression in Editor
   Caching of FileBuffer might be useful here to improve performance
   Returns: TRUE = found, FALSE = not found
*/

static gboolean find_incremental(IAnjutaEditor* te, gchar* expression, 
								 SearchDirection dir)
{
	FileBuffer* fb = file_buffer_new_from_te (te);
	SearchExpression* se = g_new0(SearchExpression, 1);
	MatchInfo* info;
	gboolean ret;
		
	se->search_str = expression;
	se->regex = FALSE;
	se->greedy = FALSE;
	se->ignore_case = TRUE;
	se->whole_word = FALSE;
	se->whole_line = FALSE;
	se->word_start = FALSE;
	se->no_limit = FALSE;
	se->actions_max = 1;
	se->re = NULL;

	info = get_next_match(fb, dir, se);
	
	if (info != NULL)
	{
		gboolean backward;
		backward = dir == SD_BACKWARD?TRUE:FALSE;
		ianjuta_editor_selection_set (IANJUTA_EDITOR_SELECTION (te),
									  info->pos, info->pos + info->len, backward, NULL);
		ret = TRUE;
	}
	else
		ret = FALSE;
	
	match_info_free(info);
	file_buffer_free(fb);
	g_free(se);
	
	return ret;
}

static void
on_find1_activate (GtkAction * action, gpointer user_data)
{
	anjuta_search_replace_activate(FALSE, FALSE);
}

static void
on_find_and_replace1_activate (GtkAction * action, gpointer user_data)
{
	anjuta_search_replace_activate(TRUE, FALSE);
}

static void
on_find_in_files1_activate (GtkAction * action, gpointer user_data)
{
	anjuta_search_replace_activate(FALSE, TRUE);
}

/*  *user_data : TRUE=Forward  False=Backward  */
static void
on_findnext1_activate (GtkAction * action, gpointer user_data)
{
	search_replace_next();
}

static void
on_findprevious1_activate (GtkAction * action, gpointer user_data)
{
	search_replace_previous();
}

static void
on_enterselection (GtkAction * action, gpointer user_data)
{
	GtkAction *entry_action;
	AnjutaUI* ui;
	IAnjutaEditor *te;
	IAnjutaDocumentManager* docman;
	SearchPlugin* plugin;
	gchar *selectionText = NULL;
	GSList *proxies;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	ui = anjuta_shell_get_ui (ANJUTA_PLUGIN(plugin)->shell, NULL);
	docman = anjuta_shell_get_interface(ANJUTA_PLUGIN(plugin)->shell,
										IAnjutaDocumentManager, NULL);
	te = ianjuta_document_manager_get_current_editor(docman, NULL);
	if (!te) return;
	
	entry_action = anjuta_ui_get_action (ui, "ActionGroupSearch",
									   "ActionEditSearchEntry");
	g_return_if_fail (EGG_IS_ENTRY_ACTION (entry_action));
	
	selectionText = ianjuta_editor_selection_get (IANJUTA_EDITOR_SELECTION (te),
												  NULL);
	if (selectionText != NULL && selectionText[0] != '\0')
	{
		egg_entry_action_set_text (EGG_ENTRY_ACTION (entry_action), selectionText);
	}		
	/* Which proxy to focus? For now just focus the first one */
	proxies = gtk_action_get_proxies (GTK_ACTION (entry_action));
	if (proxies)
	{
		GtkWidget *child;
		child = gtk_bin_get_child (GTK_BIN (proxies->data));
		gtk_widget_grab_focus (GTK_WIDGET (child));
	}
	g_free (selectionText);
}

static void
on_prev_occur(GtkAction * action, gpointer user_data)
{
	IAnjutaEditor* te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
    gint return_;
	gchar *buffer = NULL;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell,
										IAnjutaDocumentManager, NULL);
	te = ianjuta_document_manager_get_current_editor (docman, NULL);
	if(!te) return;
	if ((buffer = ianjuta_editor_selection_get (IANJUTA_EDITOR_SELECTION (te), NULL)))
	{
		g_strstrip(buffer);
		if ('\0' == *buffer)
		{
			g_free(buffer);
			buffer = NULL;
		}
	}
	if (NULL == buffer)
	{
		buffer = ianjuta_editor_get_current_word(te, NULL);
		if (!buffer)
			return;
	}
    return_= find_incremental(te, buffer, SD_BACKWARD);
	
	g_free(buffer);
}

static void 
on_next_occur(GtkAction * action, gpointer user_data)
{
	IAnjutaEditor* te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
    gint return_;
	gchar *buffer = NULL;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = anjuta_shell_get_interface (ANJUTA_PLUGIN (plugin)->shell,
										IAnjutaDocumentManager, NULL);
	te = ianjuta_document_manager_get_current_editor (docman, NULL);
	if(!te) return;
	if ((buffer = ianjuta_editor_selection_get (IANJUTA_EDITOR_SELECTION (te), NULL)))
	{
		g_strstrip(buffer);
		if ('\0' == *buffer)
		{
			g_free(buffer);
			buffer = NULL;
		}
	}
	if (NULL == buffer)
	{
		buffer = ianjuta_editor_get_current_word(te, NULL);
		if (!buffer)
			return;
	}
    return_= find_incremental(te, buffer, SD_FORWARD);
	
	g_free(buffer);
}

/* Incremental search */

typedef struct
{
	gint pos;
	gboolean wrap;
	gboolean end;
	gchar* last;
	
} IncrementalSearch;

static void
on_incremental_entry_key_press (GtkWidget *entry, GdkEventKey *event,
								SearchPlugin *plugin)
{
	if (event->keyval == GDK_Escape)
	{
		IAnjutaEditor *te;
		
		te = ianjuta_document_manager_get_current_editor(plugin->docman, NULL);
		if (te)
			ianjuta_editor_grab_focus (te, NULL);
	}
}

static void on_toolbar_find_start_over(GtkAction * action, gpointer user_data);

/* FIXME: Wrapping does not yet work */

static void
on_toolbar_find_clicked (GtkAction *action, gpointer user_data)
{
	const gchar *string;
	gchar* expression;
	gint ret;
	IAnjutaEditor *te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
	IncrementalSearch *search_params;
	gboolean search_wrap = FALSE;
	AnjutaStatus *status; 
	AnjutaUI* ui;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = plugin->docman;
	te = ianjuta_document_manager_get_current_editor(docman, NULL);
	ui = anjuta_shell_get_ui (ANJUTA_PLUGIN(plugin)->shell, NULL);
	
	if (!te)
		return;

	search_params = g_object_get_data (G_OBJECT (te), "incremental_search");
	if (!search_params)
	{
		search_params = g_new0 (IncrementalSearch, 1);
		g_object_set_data_full (G_OBJECT (te), "incremental_search",
								search_params, (GDestroyNotify)g_free);
	}
	if (EGG_IS_ENTRY_ACTION (action))
	{
		string = egg_entry_action_get_text (EGG_ENTRY_ACTION (action));
	}
	else
	{
		GtkAction *entry_action;
		entry_action = anjuta_ui_get_action (ui,
											 "ActionGroupSearch",
											 "ActionEditSearchEntry");
		g_return_if_fail (EGG_IS_ENTRY_ACTION (entry_action));
		string = egg_entry_action_get_text (EGG_ENTRY_ACTION (entry_action));
	}
	if (search_params->pos >= 0 &&  search_params->wrap)
	{
		/* If incremental search wrap requested, so wrap it. */
		search_wrap = TRUE;
	}
	
	expression = g_strdup(string);
	if (search_params->end &&
		g_str_has_prefix(expression, search_params->last))
	{
		g_free(expression);
		return;
	}
	else
		search_params->end = FALSE;
	if (search_wrap)
	{
		ianjuta_editor_goto_position(te, 0, NULL);
		ret = find_incremental(te, expression, SD_FORWARD);
		search_params->wrap = FALSE;
	}
	else
	{
		ret = find_incremental(te, expression, SD_FORWARD);
	}
	
	status = anjuta_shell_get_status (ANJUTA_PLUGIN (user_data)->shell, NULL);
	
	if (ret == FALSE) 
	{
		if (search_params->pos < 0)
		{
			GtkWindow *parent;
			GtkWidget *dialog;
			
			parent = GTK_WINDOW (ANJUTA_PLUGIN(user_data)->shell);
			dialog = gtk_message_dialog_new (parent,
											 GTK_DIALOG_DESTROY_WITH_PARENT,
											 GTK_MESSAGE_QUESTION,
											 GTK_BUTTONS_YES_NO,
					_("No matches. Wrap search around the document?"));
			if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES)
				on_toolbar_find_start_over (action, user_data);
			gtk_widget_destroy (dialog);
		}
		else
		{
			if (search_wrap == FALSE)
			{
				anjuta_status_push(status,
					_("Incremental search for '%s' failed. Press Enter or click Find to continue searching at the top."),
					string);
				search_params->wrap = 1;
				gdk_beep();
			}
			else
			{
				search_params->end = TRUE;
				anjuta_status_push (status, _("Incremental search for '%s' (continued at top) failed."), 
				                    string);
				search_params->wrap = 0;
			}
		}
	}
	else
	{
		anjuta_status_clear_stack (status);
	}
	g_free(search_params->last);
	search_params->last = expression;
}

static void
on_toolbar_find_start_over (GtkAction * action, gpointer user_data)
{
	IAnjutaEditor *te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = plugin->docman;
	te = ianjuta_document_manager_get_current_editor(docman, NULL);
	
	/* search from doc start */
	ianjuta_editor_goto_position(te, 0, NULL);
	on_toolbar_find_clicked (action, user_data);
}

static gboolean
on_toolbar_find_incremental_start (GtkAction *action, gpointer user_data)
{
	IAnjutaEditor *te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
	IncrementalSearch *search_params;
	GSList *entries, *node;
	static GHashTable *entries_connected = NULL;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = plugin->docman;
	te = ianjuta_document_manager_get_current_editor(docman, NULL);

	if (!te) return FALSE;
	
	/* Make sure we set up escape for getting out the focus to the editor */
	if (entries_connected == NULL)
	{
		entries_connected = g_hash_table_new (g_direct_hash, g_direct_equal);
	}
	entries = gtk_action_get_proxies (action);
	node = entries;
	while (node)
	{
		GtkWidget *entry;
		entry = GTK_WIDGET (node->data);
		if (!g_hash_table_lookup (entries_connected, entry))
		{
			g_signal_connect (G_OBJECT (entry), "key-press-event",
							  G_CALLBACK (on_incremental_entry_key_press),
							  plugin);
			g_hash_table_insert (entries_connected, entry, entry);
		}
		node = g_slist_next (node);
	}

	search_params = g_object_get_data (G_OBJECT (te), "incremental_search");
	if (!search_params)
	{
		search_params = g_new0 (IncrementalSearch, 1);
		g_object_set_data_full (G_OBJECT (te), "incremental_search",
								search_params, (GDestroyNotify)g_free);
	}
	/* Prepare to begin incremental search */	
	search_params->pos = ianjuta_editor_get_position(te, NULL);
	search_params->wrap = FALSE;
	return FALSE;
}

static gboolean
on_toolbar_find_incremental_end (GtkAction *action, gpointer user_data)
{
	IAnjutaEditor *te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
	IncrementalSearch *search_params;
	AnjutaStatus *status; 
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = plugin->docman;
	te = ianjuta_document_manager_get_current_editor(docman, NULL);

	if (!te) 
		return FALSE;
		
	status = anjuta_shell_get_status (ANJUTA_PLUGIN (user_data)->shell, NULL);
	anjuta_status_clear_stack (status);
	
	search_params = g_object_get_data (G_OBJECT (te), "incremental_search");
	if (search_params)
	{
		search_params->pos = -1;
		search_params->wrap = FALSE;
	}
	return FALSE;
}

static void
on_toolbar_find_incremental (GtkAction *action, gpointer user_data)
{
	const gchar *entry_text;
	IAnjutaEditor *te;
	IAnjutaDocumentManager *docman;
	SearchPlugin *plugin;
	IncrementalSearch *search_params;
	
	plugin = ANJUTA_PLUGIN_SEARCH (user_data);
	docman = plugin->docman;
	te = ianjuta_document_manager_get_current_editor(docman, NULL);
	
	if (!te)
		return;
	
	if (EGG_IS_ENTRY_ACTION (action))
	{
		entry_text = egg_entry_action_get_text (EGG_ENTRY_ACTION (action));
	}
	else
	{
		AnjutaUI *ui;
		GtkAction *entry_action;
		
		ui = ANJUTA_UI (g_object_get_data (G_OBJECT (user_data), "ui"));
		entry_action = anjuta_ui_get_action (ui, "ActionGroupSearch",
											 "ActionEditSearchEntry");
		g_return_if_fail (EGG_IS_ENTRY_ACTION (entry_action));
		entry_text =
			egg_entry_action_get_text (EGG_ENTRY_ACTION (entry_action));
	}
	if (!entry_text || strlen(entry_text) < 1) return;
	
	search_params = g_object_get_data (G_OBJECT (te), "incremental_search");
	if (!search_params)
	{
		search_params = g_new0 (IncrementalSearch, 1);
		g_object_set_data_full (G_OBJECT (te), "incremental_search",
								search_params, (GDestroyNotify)g_free);
		search_params->pos =
			ianjuta_editor_get_position(te, NULL);
	}
	else
	{
		/* Do not wrap around if the user just pressed backspace */
		if (search_params->last && 
			g_str_has_prefix(search_params->last, entry_text))
		{
			DEBUG_PRINT ("Do not wrap");
			search_params->wrap = FALSE;
		}
	}
	if (search_params->pos < 0)
		return;
	
	ianjuta_editor_goto_position(te, search_params->pos, NULL);
	on_toolbar_find_clicked (NULL, user_data);
}

static GtkActionEntry actions_search[] = {
  { "ActionMenuEditSearch", NULL, N_("_Search"), NULL, NULL, NULL},
  { "ActionEditSearchFind", GTK_STOCK_FIND, N_("_Find..."), "<control>f",
	N_("Search for a string or regular expression in the editor"),
    G_CALLBACK (on_find1_activate)},
  { "ActionEditSearchFindNext", GTK_STOCK_FIND, N_("Find _Next"), "<control>g",
	N_("Repeat the last Find command"),
    G_CALLBACK (on_findnext1_activate)},
  { "ActionEditSearchFindPrevious", GTK_STOCK_FIND, N_("Find _Previous"),
	"<control><shift>g",
	N_("Repeat the last Find command"),
	G_CALLBACK (on_findprevious1_activate)},
  { "ActionEditSearchReplace", GTK_STOCK_FIND_AND_REPLACE, N_("Find and R_eplace..."),
	"<control>h",
	N_("Search for and replace a string or regular expression with another string"),
    G_CALLBACK (on_find_and_replace1_activate)},
  { "ActionEditAdvancedSearch", GTK_STOCK_FIND, N_("Advanced Search And Replace"),
	NULL, N_("New advance search And replace stuff"),
	G_CALLBACK (on_find1_activate)},
  { "ActionEditSearchSelectionISearch", NULL, N_("_Enter Selection/I-Search"),
	"<control>e",
	N_("Enter the selected text as the search target"),
    G_CALLBACK (on_enterselection)},
  { "ActionEditSearchInFiles", NULL, N_("Fin_d in Files..."), "<shift><control>f",
	N_("Search for a string in multiple files or directories"),
    G_CALLBACK (on_find_in_files1_activate)},
	{ "ActionEditGotoOccuranceNext", GTK_STOCK_JUMP_TO,
	N_("Ne_xt Occurrence"), NULL,
	N_("Find the next occurrence of current word"),
    G_CALLBACK (on_next_occur)},
  { "ActionEditGotoOccurancePrev",GTK_STOCK_JUMP_TO,
	N_("Pre_vious Occurrence"),  NULL,
	N_("Find the previous occurrence of current word"),
    G_CALLBACK (on_prev_occur)},
};

gpointer parent_class;

static gboolean
activate_plugin (AnjutaPlugin *plugin)
{
	AnjutaUI *ui;
	GtkActionGroup* group;
	GtkAction*  action;
	SearchPlugin* splugin = ANJUTA_PLUGIN_SEARCH (plugin);
	IAnjutaDocumentManager* docman = anjuta_shell_get_interface(ANJUTA_PLUGIN(plugin)->shell,
																IAnjutaDocumentManager, NULL);
	
	
	ui = anjuta_shell_get_ui (plugin->shell, NULL);
	anjuta_ui_add_action_group_entries (ui, "ActionGroupSearch",
					_("Searching..."),
					actions_search,
					G_N_ELEMENTS (actions_search),
					GETTEXT_PACKAGE, TRUE, plugin);
		
	group = gtk_action_group_new ("ActionGroupSearch");
	action = g_object_new (EGG_TYPE_ENTRY_ACTION,
						   "name", "ActionEditSearchEntry",
						   "label", _("Search"),
						   "tooltip", _("Incremental search"),
						   "stock_id", GTK_STOCK_JUMP_TO,
						   "width", 150,
						   NULL);
	g_assert (EGG_IS_ENTRY_ACTION (action));
	g_signal_connect (action, "activate",
					  G_CALLBACK (on_toolbar_find_clicked), plugin);
	g_signal_connect (action, "changed",
					  G_CALLBACK (on_toolbar_find_incremental), plugin);
	g_signal_connect (action, "focus-in",
					  G_CALLBACK (on_toolbar_find_incremental_start), plugin);
	g_signal_connect (action, "focus-out",
					  G_CALLBACK (on_toolbar_find_incremental_end), plugin);
	gtk_action_group_add_action (group, action);
	
	/* FIXME: For some reason, if can_customize is set TRUE, AnjutaUI
	 * can't find this action
	 */
	anjuta_ui_add_action_group(ui, "ActionGroupSearch", _("Search Toolbar"),
							   group, TRUE);
	g_object_set (G_OBJECT (action), "sensitive", TRUE, NULL);

	
	splugin->uiid = anjuta_ui_merge (ui, UI_FILE);
	splugin->docman = docman;
	search_and_replace_init(docman);
	
	return TRUE;
}

static gboolean
deactivate_plugin (AnjutaPlugin *plugin)
{
	
	return TRUE;
}

static void
dispose (GObject *obj)
{
	//SearchPlugin *plugin = ANJUTA_PLUGIN_SEARCH (obj);
	
	GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (obj));
}

static void
search_plugin_instance_init (GObject *obj)
{
	//SearchPlugin *plugin = ANJUTA_PLUGIN_SEARCH (obj);
}

static void
search_plugin_class_init (GObjectClass *klass) 
{
	AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);

	plugin_class->activate = activate_plugin;
	plugin_class->deactivate = deactivate_plugin;
	klass->dispose = dispose;
}
ANJUTA_PLUGIN_BEGIN (SearchPlugin, search_plugin);
ANJUTA_PLUGIN_END;
ANJUTA_SIMPLE_PLUGIN (SearchPlugin, search_plugin);