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

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 - 2004 Takuro Ashie
 *  Copyright (C) 2004 Hidetaka Iwai
 *
 *  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 "kz-location-entry-action.h"

#include <stdlib.h>
#include <gdk/gdkkeysyms.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include "kazehakase.h"
#include "utils/utils.h"
#include "gobject-utils.h"
#include "kz-bookmark.h"
#include "kz-smart-bookmark.h"
#include "kz-actions.h"
#include "eggregex.h"

#define KZ_LOCATION_ENTRY_ACTION_KEY "KzLocationEntryAction::Action"

enum {
	PROP_0,
	PROP_KZ_WINDOW,
	PROP_KZ_BOOKMARK
};

static void kz_location_entry_action_class_init       (KzLocationEntryActionClass *class);
static void kz_location_entry_action_init             (KzLocationEntryAction      *action);
static void kz_location_entry_action_dispose          (GObject *obj);
static void kz_location_entry_action_set_property     (GObject           *object,
						       guint              prop_id,
						       const GValue      *value,
						       GParamSpec        *pspec);
static void kz_location_entry_action_get_property     (GObject           *object,
						       guint              prop_id,
						       GValue            *value,
						       GParamSpec        *pspec);

static void kz_location_entry_action_activate         (GtkAction         *action);
static void kz_location_entry_action_connect_proxy    (GtkAction         *action,
						       GtkWidget         *proxy);
static void kz_location_entry_action_disconnect_proxy (GtkAction         *action,
						       GtkWidget         *proxy);

static void     kz_location_entry_action_history_changed (KzHistoryAction *action);
static void     kz_location_entry_action_sync_history    (KzLocationEntryAction *action);
static void     kz_location_entry_action_reset_history   (KzLocationEntryAction *action);
static void     kz_location_entry_action_clear_history   (KzLocationEntryAction *action);
static void     cb_editable_changed                      (GtkEditable *editable,
							  KzLocationEntryAction *action);
static gboolean cb_entry_key_press                       (GtkWidget *widget,
							  GdkEventKey *event,
							  KzLocationEntryAction *action);
static void     cb_entry_populate_popup         	 (GtkEntry *entry,
						   	  GtkMenu *menu,
	  			    	 	   	  KzLocationEntryAction *action);

static void     cb_profile_global_changed     (KzProfile       *profile,
					       const gchar     *section,
					       const gchar     *key,
					       const gchar     *old_value,
					       GtkEntry        *entry);
static KzHistoryActionClass *parent_class = NULL;


KZ_OBJECT_GET_TYPE(kz_location_entry_action,
		   "KzLocationEntryAction",
		   KzLocationEntryAction,
		   kz_location_entry_action_class_init,
		   kz_location_entry_action_init,
		   KZ_TYPE_HISTORY_ACTION)


static void
kz_location_entry_action_class_init (KzLocationEntryActionClass *klass)
{
	GObjectClass *object_class;
	GtkActionClass *action_class;
	KzHistoryActionClass *history_class;

	parent_class  = g_type_class_peek_parent(klass);
	object_class  = G_OBJECT_CLASS(klass);
	action_class  = GTK_ACTION_CLASS(klass);
	history_class = KZ_HISTORY_ACTION_CLASS(klass);

	object_class->set_property     = kz_location_entry_action_set_property;
	object_class->get_property     = kz_location_entry_action_get_property;
	object_class->dispose          = kz_location_entry_action_dispose;

	action_class->activate         = kz_location_entry_action_activate;
	action_class->connect_proxy    = kz_location_entry_action_connect_proxy;
	action_class->disconnect_proxy = kz_location_entry_action_disconnect_proxy;

	history_class->history_changed = kz_location_entry_action_history_changed;

	g_object_class_install_property
		(object_class,
		 PROP_KZ_WINDOW,
		 g_param_spec_object ("kz-window",
				      _("KzWindow"),
				      _("The KzWindow to add a home button"),
				      KZ_TYPE_WINDOW,
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT_ONLY));
	g_object_class_install_property
		(object_class,
		 PROP_KZ_BOOKMARK,
		 g_param_spec_object ("kz-bookmark",
				      _("KzBookmark"),
				      _("The Bookmark object"),
				      KZ_TYPE_BOOKMARK,
				      G_PARAM_READWRITE));
}


static void
kz_location_entry_action_init (KzLocationEntryAction *action)
{
	action->kz            = NULL;
	action->bookmark      = NULL;
	action->history       = NULL;
	action->activating    = FALSE;
	action->synchronizing = FALSE;
}


static void
kz_location_entry_action_dispose (GObject *obj)
{
	KzLocationEntryAction *action = KZ_LOCATION_ENTRY_ACTION(obj);

	if (action->kz)
	{
		kz_profile_set_save_each_time(kz_global_profile, FALSE);
		kz_location_entry_action_store_history(action);
		kz_profile_set_save_each_time(kz_global_profile, TRUE);
		g_object_unref(action->kz);
		action->kz = NULL;
	}

	kz_location_entry_action_clear_history(action);

	if (action->bookmark)
	{
		g_object_unref(action->bookmark);
		action->bookmark = NULL;
	}

	if (G_OBJECT_CLASS(parent_class)->dispose)
		G_OBJECT_CLASS(parent_class)->dispose(obj);
}


static void
kz_location_entry_action_set_property (GObject         *object,
				       guint            prop_id,
				       const GValue    *value,
				       GParamSpec      *pspec)
{
	KzLocationEntryAction *action = KZ_LOCATION_ENTRY_ACTION(object);
  
	switch (prop_id)
	{
	case PROP_KZ_WINDOW:
		action->kz = g_object_ref(g_value_get_object(value));
		break;
	case PROP_KZ_BOOKMARK:
		if (action->bookmark)
			g_object_unref(action->bookmark);
		action->bookmark = g_object_ref(g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_location_entry_action_get_property (GObject         *object,
				       guint            prop_id,
				       GValue          *value,
				       GParamSpec      *pspec)
{
	KzLocationEntryAction *action = KZ_LOCATION_ENTRY_ACTION(object);

	switch (prop_id)
	{
	case PROP_KZ_WINDOW:
		g_value_set_object(value, action->kz);
		break;
	case PROP_KZ_BOOKMARK:
		g_value_set_object(value, action->bookmark);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_location_entry_action_activate (GtkAction *action)
{
	KzLocationEntryAction *entry_action;
	const gchar *uri;
	gchar *smart_uri;
	gboolean new_tab;
	GdkModifierType state = (GdkModifierType)0;
	gint x, y;
	guint new_tab_mask = GDK_CONTROL_MASK;
 
	g_return_if_fail(KZ_IS_LOCATION_ENTRY_ACTION(action));
	entry_action = KZ_LOCATION_ENTRY_ACTION(action);

	kz_location_entry_action_reset_history(entry_action);
	
	entry_action->activating = TRUE;
	if (GTK_ACTION_CLASS(parent_class)->activate)
		GTK_ACTION_CLASS(parent_class)->activate(action);
	entry_action->activating = FALSE;


	uri = kz_entry_action_get_text(KZ_ENTRY_ACTION(action));
	smart_uri = kz_smart_bookmark_get_smart_uri(KZ_SMART_BOOKMARK(entry_action->bookmark), uri);

	gdk_window_get_pointer(NULL, &x, &y, &state);
	
	KZ_CONF_GET("Tab" , "new_tab_mask" , new_tab_mask , INT);
	KZ_CONF_GET("Global", "entry_open_in_new_tab", new_tab, BOOL);
	new_tab = (state & new_tab_mask) ? !new_tab: new_tab;

	if (new_tab)
		kz_window_open_new_tab(entry_action->kz, smart_uri);
	else	
		kz_window_load_url(entry_action->kz, smart_uri);
	g_free(smart_uri);
}


static void
kz_location_entry_action_connect_proxy (GtkAction *action, GtkWidget *proxy)
{
	GtkEntry *entry;

	GTK_ACTION_CLASS (parent_class)->connect_proxy (action, proxy);

	entry = kz_entry_action_get_entry_widget(KZ_ENTRY_ACTION(action), proxy);
	if (entry)
	{
		gboolean autocompletion = FALSE;
		KZ_CONF_GET("Global", "use_auto_completion", autocompletion, BOOL);
		if(autocompletion)
			kz_history_set_completion (KZ_HISTORY_ACTION(action), entry);

		g_object_set_data(G_OBJECT(entry),
				  KZ_LOCATION_ENTRY_ACTION_KEY,
				  action);
		g_signal_connect(entry, "changed",
				 G_CALLBACK(cb_editable_changed), action);
		g_signal_connect(entry, "key-press-event",
				 G_CALLBACK(cb_entry_key_press), action);
		g_signal_connect(entry, "populate-popup",
				 G_CALLBACK(cb_entry_populate_popup), action);
		g_signal_connect(kz_global_profile, "changed::Global",
				 G_CALLBACK(cb_profile_global_changed), entry);
	}
}


static void
kz_location_entry_action_disconnect_proxy (GtkAction *action, GtkWidget *proxy)
{
	GtkEntry *entry;

	entry = kz_entry_action_get_entry_widget(KZ_ENTRY_ACTION(action), proxy);
	if (entry)
	{
		g_signal_handlers_disconnect_by_func
			(entry,
			 G_CALLBACK(cb_editable_changed),
			 action);
		g_signal_handlers_disconnect_by_func
			(entry,
			 G_CALLBACK(cb_entry_key_press),
			 action);
		g_signal_handlers_disconnect_by_func
			(entry,
			 G_CALLBACK(cb_entry_populate_popup),
			 action);
		g_signal_handlers_disconnect_by_func
			(kz_global_profile,
			 G_CALLBACK(cb_profile_global_changed),
			 entry);
	        g_object_set_data(G_OBJECT(entry),
		        	  KZ_LOCATION_ENTRY_ACTION_KEY,
			          NULL);
	}

	GTK_ACTION_CLASS (parent_class)->disconnect_proxy (action, proxy);
}


static void
kz_location_entry_action_history_changed (KzHistoryAction *action)
{
	g_return_if_fail(KZ_IS_LOCATION_ENTRY_ACTION(action));

	kz_location_entry_action_sync_history(KZ_LOCATION_ENTRY_ACTION(action));

	if (KZ_HISTORY_ACTION_CLASS(parent_class)->history_changed)
		KZ_HISTORY_ACTION_CLASS(parent_class)->history_changed(action);
}


KzLocationEntryAction *
kz_location_entry_action_new (KzWindow *kz)
{
	KzLocationEntryAction *action;

	action = g_object_new(KZ_TYPE_LOCATION_ENTRY_ACTION,
			      "name",        "LocationEntry",
			      "label",       _("Location Entry"),
			      "tooltip",     NULL,
			      "stock_id",    GTK_STOCK_NEW,
			      "kz-window",   kz,
			      NULL);
	kz_profile_set_save_each_time(kz_global_profile, FALSE);
	kz_location_entry_action_restore_history(action);
	kz_profile_set_save_each_time(kz_global_profile, TRUE);

	return action;
}


void
kz_location_entry_action_store_history (KzLocationEntryAction *action)
{
	GList *list, *node;
	gint i = 0;

	g_return_if_fail(KZ_IS_LOCATION_ENTRY_ACTION(action));

	list = kz_profile_enum_key(kz_global_profile, "LocationEntry", TRUE);
	for (node = list; node; node = g_list_next(node))
	{
		const gchar *key = node->data;

		if (key && *key && key_seems_sequential(key, "history"))
			kz_profile_delete_key(kz_global_profile,
					      "LocationEntry", key);
	}
	g_list_free(list);

	list = kz_history_action_get_history(KZ_HISTORY_ACTION(action));
	for (node = list; node; node = g_list_next(node))
	{
		const gchar *uri = node->data;
		gchar key[256];

		if (!uri || !*uri) continue;

		g_snprintf(key, G_N_ELEMENTS(key), "history%d", i);

		KZ_CONF_SET_STR("LocationEntry", key, uri);
		i++;
	}
	g_list_foreach(list, (GFunc) g_free, NULL);
	g_list_free(list);

}


void
kz_location_entry_action_restore_history (KzLocationEntryAction *action)
{
	GList *list, *node, *history = NULL;
	gint max_history = 32;
	gboolean success;
	gchar *text;

	g_return_if_fail(KZ_IS_LOCATION_ENTRY_ACTION(action));

	if (action->activating) return;

	text = g_strdup(kz_entry_action_get_text(KZ_ENTRY_ACTION(action)));

	list = kz_profile_enum_key(kz_global_profile, "LocationEntry", TRUE);
	for (node = list; node; node = g_list_next(node))
	{
		const gchar *key = node->data;
		gchar *value;

		if (!(key && *key) || !key_seems_sequential(key, "history"))
			continue;

		value = KZ_CONF_GET_STR("LocationEntry", key);
		if (value && *value)
			history = g_list_append(history, value);
	}

	success = KZ_CONF_GET("LocationEntry", "max_history",
			      max_history, INT);
	if (success)
		kz_history_action_set_max_history
			(KZ_HISTORY_ACTION(action), max_history);

	if (history)
	{
		kz_history_action_set_history(KZ_HISTORY_ACTION(action),
					      history);
	}

	g_list_foreach(history, (GFunc) g_free, NULL);
	g_list_free(history);

	kz_location_entry_action_clear_history(action);
	
	kz_entry_action_set_text(KZ_ENTRY_ACTION(action), text);
	g_free(text);
}


static void
kz_location_entry_action_sync_history (KzLocationEntryAction *action)
{
	const GList *list, *node;

	g_return_if_fail(KZ_IS_LOCATION_ENTRY_ACTION(action));

	if (action->synchronizing) return;
	action->synchronizing = TRUE;

	kz_location_entry_action_store_history
		(KZ_LOCATION_ENTRY_ACTION(action));

	list = kz_window_get_window_list();
	for (node = list; node; node = g_list_next(node))
	{
		KzWindow *tmpkz = node->data;
		GtkAction *act;

		if (tmpkz == action->kz) continue;

		act = gtk_action_group_get_action(tmpkz->actions,
						  "LocationEntry");
		if (KZ_LOCATION_ENTRY_ACTION(act)->synchronizing) continue;
		kz_location_entry_action_restore_history
			(KZ_LOCATION_ENTRY_ACTION(act));
	}

	action->synchronizing = FALSE;
}

static void
kz_location_entry_action_reset_history(KzLocationEntryAction *action)
{
	if (action->history)
	{
		kz_history_action_set_history(KZ_HISTORY_ACTION(action),
					      action->history);
		kz_location_entry_action_clear_history(action);
	}
}

static void
kz_location_entry_action_clear_history(KzLocationEntryAction *action)
{
	if (action->history)
	{
		g_list_foreach(action->history, (GFunc)g_free, NULL);
		g_list_free(action->history);
		action->history = NULL;
	}
}


static void
get_file_path_from_uri(const gchar *uri, gchar **path, gchar **prefix)
{
	GError *error = NULL;
	EggRegex *regex;

	*path = NULL;
	*prefix = NULL;

	g_return_if_fail(uri);
	
	regex = egg_regex_new("^\\s*(file://|)(/.*)\\s*",
			      EGG_REGEX_CASELESS | EGG_REGEX_EXTENDED,
			      0, &error);

	if (error)
	{
		g_warning("%s", error->message);
		g_error_free(error);
		return;
	}
	
	if (egg_regex_match(regex, uri, -1, 0) > 0)
	{
		*prefix = egg_regex_fetch(regex, uri, 1);
		*path = egg_regex_fetch(regex, uri, 2);
	}
	
	egg_regex_free(regex);
}

static gboolean
is_dir(const gchar *path)
{
	gboolean result = FALSE;
	struct stat buf;

	if (g_stat(path, &buf) == 0)
	{
		result = S_ISDIR(buf.st_mode);
	}

	return result;
}

static GList*
collect_file_path(GDir *dir, const gchar *prefix, gchar *dir_name,
		  gchar *base_name, guint max)
{
	GList *list = g_list_alloc();
	gchar *dir_base_name = g_path_get_basename(dir_name);
	gboolean is_dir_path = (strcmp(base_name, dir_base_name) == 0);
	const gchar *name;
	gchar *full_path;
	gchar *utf8_full_path;
	gchar *full_uri;
	gchar *separator;
	guint capacity = max;
			
	for (name = g_dir_read_name(dir);
	     name && capacity > 0;
	     name = g_dir_read_name(dir))
	{
		if (!is_dir_path &&
		    !g_str_has_prefix(name, base_name))
			continue;
				
		full_path = g_build_filename(dir_name, name, NULL);
		separator = is_dir(full_path) ? G_DIR_SEPARATOR_S : NULL;
		if ((g_utf8_validate(full_path, -1, NULL)))
		{
			utf8_full_path = full_path;
		}
		else
		{
			utf8_full_path = g_filename_to_utf8(full_path,
							    -1,
							    NULL,
							    NULL,
							    NULL);
			g_free(full_path);
			if (!utf8_full_path) continue;
		}
					
		full_uri = g_strconcat(prefix, utf8_full_path,
				       separator, NULL);
		list = g_list_append(list, full_uri);

		g_free(utf8_full_path);

		capacity--;
	}

	g_free(dir_base_name);
	
	return list;
}

static void
kz_location_entry_action_add_file_path_to_history(KzLocationEntryAction *action,
						  const gchar *path,
						  const gchar *prefix)
{
	GDir *dir;
	gchar *dir_name = g_path_get_dirname(path);
	gchar *base_name = g_path_get_basename(path);

	dir = g_dir_open(dir_name, 0, NULL);

	if (dir)
	{
		guint max = kz_history_action_get_max_history
			(KZ_HISTORY_ACTION(action));
		GList *list = collect_file_path(dir, prefix, dir_name,
						base_name, max);

		g_dir_close(dir);
		
		list = g_list_concat(list,
				     kz_history_action_get_history(KZ_HISTORY_ACTION(action)));
		kz_history_action_set_history(KZ_HISTORY_ACTION(action), list);

		g_list_foreach(list, (GFunc)g_free, NULL);
		g_list_free(list);
	}

	g_free(base_name);
	g_free(dir_name);
}

static void
kz_location_entry_action_setup_file_completion(KzLocationEntryAction *action)
{
	const gchar *uri = kz_entry_action_get_text(KZ_ENTRY_ACTION(action));
	gchar *path, *prefix;
	
	get_file_path_from_uri(uri, &path, &prefix);
	
	if (path)
	{
		if (action->history)
		{
			kz_history_action_set_history(KZ_HISTORY_ACTION(action),
						      action->history);
		}
		else
		{
			action->history =
				kz_history_action_get_history(KZ_HISTORY_ACTION(action));
		}
		kz_location_entry_action_add_file_path_to_history(action,
								  path,
								  prefix);
		g_free(path);
		g_free(prefix);
	}
	else
	{
		kz_location_entry_action_reset_history(action);
	}
}


static void
cb_editable_changed (GtkEditable *editable, KzLocationEntryAction *action)
{
	if (KZ_HISTORY_ACTION(action)->completion)
		kz_location_entry_action_setup_file_completion(action);
}


static gboolean
cb_entry_key_press (GtkWidget *widget, GdkEventKey *event,
		    KzLocationEntryAction *action)
{
	guint new_tab_mask = GDK_CONTROL_MASK;

	KZ_CONF_GET("Tab" , "new_tab_mask" , new_tab_mask , INT);

	if ((event->keyval == GDK_Return || event->keyval == GDK_ISO_Enter)
	    && (event->state & new_tab_mask))
	{
		gtk_action_activate(GTK_ACTION(action));
		return TRUE;
	}

	return FALSE;
}


static void
cb_activate_edit_bookmark (GtkMenuItem *menuitem,
			   KzLocationEntryAction *action)
{
	GtkAction *edit_action;
	KzWindow *kz = action->kz;

	kz_actions_set_bookmark_for_action(kz, action->bookmark);
	edit_action = gtk_action_group_get_action(kz->actions,
					     	  "EditBookmarks");
	if (edit_action)
		gtk_action_activate(edit_action);
	kz_actions_set_bookmark_for_action(kz, NULL);
}

static void 
cb_entry_populate_popup (GtkEntry *entry, GtkMenu *menu,
		    	 KzLocationEntryAction *action)
{
	GtkWidget *separator, *menuitem;

	separator = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
	gtk_widget_show(separator);

	menuitem = gtk_menu_item_new_with_mnemonic(_("_Edit Smart Bookmark"));
	g_signal_connect(menuitem, "activate",
			 G_CALLBACK(cb_activate_edit_bookmark), action);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	gtk_widget_show(menuitem);
}

void
kz_location_entry_action_set_bookmark (KzLocationEntryAction *action,
				       KzBookmark *bookmark)
{
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_object_set(G_OBJECT(action),
		     "kz-bookmark", bookmark, NULL);
}


static void
cb_profile_global_changed (KzProfile *profile,
			   const gchar *section, const gchar *key,
			   const gchar *old_value,
			   GtkEntry    *entry)
{
     	if (!GTK_IS_ENTRY(entry)) return;
	
	switch (key[0])
	{
	case 'u':
		if (strcmp(key, "use_auto_completion") == 0)
		{
			GtkAction *action;
	                
			action = g_object_get_data(G_OBJECT(entry),
        			        	   KZ_LOCATION_ENTRY_ACTION_KEY);
			if (action && KZ_IS_HISTORY_ACTION(action))
                        {
		                gboolean autocompletion = FALSE;
        		        KZ_CONF_GET("Global", "use_auto_completion", autocompletion, BOOL);
                                if(autocompletion)
                                       kz_history_set_completion (KZ_HISTORY_ACTION(action), entry);
                                else

	                               kz_history_unset_completion (KZ_HISTORY_ACTION(action), entry);
                        }
		}
		else if (strcmp(key, "use_inline_completion") == 0)
		{
			GtkAction *action;
	                
			action = g_object_get_data(G_OBJECT(entry),
        			        	   KZ_LOCATION_ENTRY_ACTION_KEY);
			if (action && KZ_IS_HISTORY_ACTION(action))
                        {
		                gboolean inline_completion = FALSE;
        		        KZ_CONF_GET("Global", "use_inline_completion",
					    inline_completion, BOOL);
				kz_history_set_inline_completion(KZ_HISTORY_ACTION(action),
								 inline_completion);
			}
		}
		break;
	default:
		break;
	}
}

