/*
 *  Copyright (C) 2000, 2001, 2002 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 "global-history.h"
#include "eel-gconf-extensions.h"
#include "gul-string.h"
#include "gul-general.h"
#include "gul-gobject-misc.h"
#include "galeon-config.h"
#include "galeon-autocompletion-source.h"
#include "galeon-debug.h"

#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnome/gnome-util.h>
#include <libxml/parser.h>
#include <libxml/xmlwriter.h>
#include <glib/gstrfuncs.h>
#include <glib/gi18n.h>

#include <time.h>
#include <unistd.h>

/* when parsing using SAX, elements 0 and 1 of the attribute array are
 * the key and value respectively */
enum
{
	SAX_KEY   = 0,
	SAX_VALUE = 1
};

struct GlobalHistoryPrivate
{
	GHashTable *history_sites;
	GHashTable *history_hosts;
	HistoryItem *last_visited_site;
	gboolean history_dirty;
	gint expire_days;
};

struct HistoryItemPrivate
{
	HistoryItem *host;
	GHashTable *properties;
	GSList *children;
	gboolean is_host;
	gint zoom;
};

enum
{
        ADD,
	UPDATE,
	REMOVE,
        LAST_SIGNAL
};

static void
global_history_class_init (GlobalHistoryClass *klass);
static void
global_history_autocompletion_source_init (GaleonAutocompletionSourceIface *iface);
static void
global_history_init (GlobalHistory *gh);
static void
global_history_finalize (GObject *object);
static void
global_history_autocompletion_source_foreach (GaleonAutocompletionSource *source,
					      const gchar *basic_key,
					      GaleonAutocompletionSourceForeachFunc func,
					      gpointer data);
static void
global_history_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
						    const gchar *basic_key);
static void
global_history_emit_changed_data_changed (GlobalHistory *gh);

static HistoryItem *
history_add_host (GlobalHistory *gh, 
		  const char *name);
static HistoryItem *
history_add_item (GlobalHistory *gh,
		  char *url, 
		  char *title, 
		  GTime first, 
		  GTime last, 
		  gint visits);
static HistoryItem *
history_get_host (GlobalHistory *gh, 
		  const char *url);

static GObjectClass *parent_class = NULL;

/* how often to save the history, in milliseconds */
#define HISTORY_SAVE_INTERVAL (60 * 5 * 1000)

static guint global_history_signals[LAST_SIGNAL] = { 0 };

MAKE_GET_TYPE_IFACE (global_history, "GlobalHistory", GlobalHistory,
		     global_history_class_init, global_history_init, G_TYPE_OBJECT,
		     global_history_autocompletion_source_init, GALEON_TYPE_AUTOCOMPLETION_SOURCE);

static void
global_history_autocompletion_source_init (GaleonAutocompletionSourceIface *iface)
{
	iface->foreach = global_history_autocompletion_source_foreach;
	iface->set_basic_key = global_history_autocompletion_source_set_basic_key;
}

static void
global_history_class_init (GlobalHistoryClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        parent_class = g_type_class_peek_parent (klass);

        object_class->finalize = global_history_finalize;

	global_history_signals[ADD] =
                g_signal_new ("add",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, add),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
			      G_TYPE_POINTER);
	global_history_signals[UPDATE] =
                g_signal_new ("update",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, update),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
			      G_TYPE_POINTER);
	global_history_signals[REMOVE] =
                g_signal_new ("remove",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, remove),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
			      G_TYPE_POINTER);
}

/** 
 * Returns the number of days since the history item was visited for the last time
 */
static gint
calculate_age (GTime last)
{
        GTime now = time (NULL);
        gint days = (now - last) / (24 * 60 * 60);
        return days;
}

/**
 * history_parse_host_element: parse <host> elements
 */
static void
history_parse_host_element (GlobalHistory *gh, const xmlChar **attrs)
{
	HistoryItem *host;
	const xmlChar **attr;
	gchar *name = NULL;
	gint css = -1;
	gint zoom = -1;

	/* parse each attribute */
	for (attr = attrs; *attr != NULL; attr += 2)
	{
		if (xmlStrcmp (attr[SAX_KEY], "name") == 0)
		{
			name = g_strdup (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp (attr[SAX_KEY], "authorcss") == 0)
		{
			css = atoi (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp (attr[SAX_KEY], "zoom") == 0)
		{
			zoom = atoi (attr[SAX_VALUE]);
		}
		else
		{
			g_warning ("Unsupported attribute for "
				   "history.xml::%s: %s", 
				   attr[SAX_KEY], attr[SAX_VALUE]);
		}
	}

	/* create a host structure and store */
	host = history_add_host (gh, name);
	host->priv->zoom = zoom;
	g_free (name);
}

/**
 * history_parse_item_element: parse <item> elements
 */
static void
history_parse_item_element (GlobalHistory *gh, const xmlChar **attrs)
{
	const xmlChar **attr;
	HistoryItem *item;
	gchar *title = NULL;
	gchar *url = NULL;
	GTime first = 0;
	GTime last = 0;
	gint visits = 0;

	/* parse each attribute */
	for (attr = attrs; *attr != NULL; attr += 2)
	{
		if (xmlStrcmp(attr[SAX_KEY], "title") == 0)
		{
			title = g_strdup (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "url") == 0)
		{
			url = g_strdup (attr[SAX_VALUE]);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "first_time") == 0)
		{
			first = strtol (attr[SAX_VALUE], NULL, 10);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "last_time") == 0)
		{
			last = strtol (attr[SAX_VALUE], NULL, 10);
		}
		else if (xmlStrcmp(attr[SAX_KEY], "visits") == 0)
		{
			visits = atoi (attr[SAX_VALUE]);
		}
		else
		{
			g_warning("Unsupported attribute for "
				  "history.xml::%s: %s", 
				  attr[SAX_KEY], attr[SAX_VALUE]);
		}
	}

	/* check age */
	if (calculate_age (last) <= gh->priv->expire_days)
	{
		/* add to the history */
		item = history_add_item (gh, url, title,
					 first, last, visits);

		g_return_if_fail (item);

		for (attr = attrs; *attr != NULL; attr += 2)
		{
			if (xmlStrcmp(attr[SAX_KEY], "prop:") == 0)
			{
				global_history_set_page_property (gh,
								  item,
								  &attr[SAX_KEY][5],
								  attr[SAX_VALUE]);
			}
		}
	}
	else
	{
		g_free (url);
		g_free (title);			
	}

}

/**
 * history_start_element: callback called to parse a single element of the
 * history XML file.
 */
static void 
history_start_element (void *ctx, const xmlChar *fullname, 
		       const xmlChar **attrs)
{
	xmlParserCtxtPtr  ctxt = ctx;
	GlobalHistory    *gh   = ctxt->_private;

	/* parse element */
	if (xmlStrcmp (fullname, "host") == 0 && attrs != NULL)
	{
		/* <host> element */
		history_parse_host_element (gh, attrs);
	}
	else if (xmlStrcmp (fullname, "item") == 0 && attrs != NULL)
	{
		/* <item> element */
		history_parse_item_element (gh, attrs);
	}
	else if (xmlStrcmp (fullname, "history") == 0)
	{
		/* we recurse into this automatically */
	}
	else
	{
		g_warning ("unknown history element '%s'\n", fullname);
	}
}

/**
 * history_load: Loads the history from the history file (if it
 * exists). Creates the history dialog. Does not load expired entries
 */
static void
history_load (GlobalHistory *gh)
{
        /* definition of SAX parser for reading documents */
	static xmlSAXHandler parser =
	{
		NULL, /* internalSubset        */
		NULL, /* isStandalone          */
		NULL, /* hasInternalSubset     */
		NULL, /* hasExternalSubset     */
		NULL, /* resolveEntity         */
		NULL, /* getEntity             */
		NULL, /* entityDecl            */
		NULL, /* notationDecl          */
		NULL, /* attributeDecl         */
		NULL, /* elementDecl           */
		NULL, /* unparsedEntityDecl    */
		NULL, /* setDocumentLocator    */
		NULL, /* startDocument         */
		NULL, /* endDocument           */
		(startElementSAXFunc) history_start_element, /* startElement */
		NULL, /* endElement            */
		NULL, /* reference             */
		NULL, /* characters            */
		NULL, /* ignorableWhitespace   */
		NULL, /* processingInstruction */
		NULL, /* comment               */
		NULL, /* warning    (FIXME)    */
		NULL, /* error      (FIXME)    */
		NULL, /* fatalError (FIXME)    */
	};
        gchar *histfile;

	/* build the filename */
	histfile = g_build_filename (g_get_home_dir (), GALEON_DIR, 
				     "history.xml", NULL);
	
	/* build the initial history hash table */
        gh->priv->history_sites = g_hash_table_new (g_str_hash, g_str_equal);

	/* build the initial history hosts hashtable */
	gh->priv->history_hosts = g_hash_table_new (g_str_hash, g_str_equal);

	/* find out how many days to keep before expiring elements */
	gh->priv->expire_days = eel_gconf_get_integer (CONF_HISTORY_EXPIRE);

	LOG ("Loading history.");
	START_PROFILER ("Loading History");

	/* load the file if we can access it */
	if (access (histfile, F_OK) != -1)
	{
		xmlSubstituteEntitiesDefault (1);
		xmlSAXParseFileWithData (&parser, histfile, TRUE, gh);
	}

	STOP_PROFILER ("Loading History");
	
	/* free allocated strings */
	g_free (histfile);

	LOG ("History loaded: %d sites, %d hosts.", 
	     g_hash_table_size (gh->priv->history_sites),
	     g_hash_table_size (gh->priv->history_hosts));
}


static void
history_save_item_property (gpointer  key,
			    gpointer  value,
			    gpointer  user_data)
{
	xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

	g_return_if_fail (key);
	g_return_if_fail (value);

	/* I'm not convinced that the person that wrote this code
	 * originally meant to put the "prop:" bit before the value
	 * - crispin 30/03/2004 */
	xmlTextWriterWriteFormatAttribute (writer, (gchar*)key, "prop:%s", (gchar*)value);
}

/**
 * Saves one host of the history
 */
static void 
history_save_host (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hh = (HistoryItem *) value;
	xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

	g_return_if_fail (hh->url);

	if (!hh->priv->children) return;

	xmlTextWriterStartElement (writer, "host");
	xmlTextWriterWriteAttribute (writer, "name", hh->url);

	if (hh->priv->zoom > 0)
	{
		xmlTextWriterWriteFormatAttribute (writer, "zoom", "%d", hh->priv->zoom);
	}

	g_hash_table_foreach (hh->priv->properties,
			      (GHFunc)history_save_item_property,
			      writer);
	
	xmlTextWriterEndElement (writer);
}

/**
 * Saves one item of the history
 */
static void 
history_save_item (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hi = (HistoryItem *) value;
	xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

	g_return_if_fail (hi->title);
	g_return_if_fail (hi->url);

	xmlTextWriterStartElement (writer, "item");

	xmlTextWriterWriteAttribute (writer, "title", hi->title);
	xmlTextWriterWriteAttribute (writer, "url", hi->url);

	xmlTextWriterWriteFormatAttribute (writer, "first_time", "%d", hi->first);
	xmlTextWriterWriteFormatAttribute (writer, "last_time", "%d", hi->last);
	xmlTextWriterWriteFormatAttribute (writer, "visits", "%d", hi->visits);

	g_hash_table_foreach (hi->priv->properties,
			      (GHFunc)history_save_item_property,
			      writer);
	
	xmlTextWriterEndElement (writer);
}


/**
 * history_save_to_file: Actually do the file save
 */
static int
history_save_to_file (GlobalHistoryPrivate *ghpriv, xmlTextWriterPtr writer)
{
	int ret;

	ret = xmlTextWriterStartDocument (writer, "1.0", NULL, NULL);
	if (ret < 0) return ret;
	ret = xmlTextWriterStartElement (writer, "history");
	if (ret < 0) return ret;

        g_hash_table_foreach (ghpriv->history_hosts, history_save_host, writer);
        g_hash_table_foreach (ghpriv->history_sites, history_save_item, writer);

	ret = xmlTextWriterEndElement (writer); /* root */
	if (ret < 0) return ret;

	ret = xmlTextWriterEndDocument (writer);
	if (ret < 0) return ret;
	
	return ret;
}

/** 
 * history_save: saves the history out to the default XML file
 */
static void 
history_save (GlobalHistoryPrivate *ghpriv)
{
	gchar *histfile, *tmpfile;
	xmlTextWriterPtr writer;
	int ret;

	LOG ("saving history");

	if (!ghpriv->history_dirty)
	{
		return;
	}

	histfile = g_build_filename (g_get_home_dir (),
				     GALEON_DIR,
				     "history.xml",
				     NULL);
	tmpfile = g_strconcat (histfile, ".tmp", NULL);

	writer = xmlNewTextWriterFilename (tmpfile, 0);
	xmlTextWriterSetIndent (writer, 1);
	xmlTextWriterSetIndentString (writer, "  ");

	START_PROFILER ("Saving History");

	ret = history_save_to_file (ghpriv, writer);

	STOP_PROFILER ("Saving History");

	if (ret < 0)
	{
		g_warning( "Failed writing to history.xml" );
	}
	else
	{
		gul_general_switch_temp_file (histfile, tmpfile);
	}

	ghpriv->history_dirty = FALSE;

	xmlFreeTextWriter (writer);
	g_free (histfile);
	g_free (tmpfile);
}

void 
global_history_save_if_needed (GlobalHistory *gh)
{
	g_return_if_fail (IS_GLOBAL_HISTORY (gh));

	history_save (gh->priv);
}

/**
 * history_periodic_save_cb: save the history (if dirty) every once in a while
 */
static gboolean
history_periodic_save_cb (GlobalHistoryPrivate *ghpriv)
{
        /* save it */
        history_save (ghpriv);

        /* call again */
        return TRUE;
}

/**
 * history_item_free_cb: free one history item
 */
static void
history_item_free_cb (gpointer key, gpointer value, GlobalHistory *gh)
{
	HistoryItem *hi = (HistoryItem *)value;

	if (!hi->priv->is_host)
	{
		HistoryItem *host = hi->priv->host;
		if (host) /* I think host can't be NULL, but I'm not sure */
		{
			host->priv->children = g_slist_remove (host->priv->children, hi);
		}
	}
	
	/* if this was the last visited item, make it NULL. 
	 * It will be recalculated when needed */
	if (hi == gh->priv->last_visited_site)
	{
		gh->priv->last_visited_site = NULL;
	}

	g_free (hi->url); 
	g_free (hi->title);

	g_hash_table_destroy (hi->priv->properties);
}

static void
global_history_finalize (GObject *object)
{
        GlobalHistory *gh;

        g_return_if_fail (object != NULL);
        g_return_if_fail (IS_GLOBAL_HISTORY (object));

	LOG ("Finalizing GlobalHistory");

	gh = GLOBAL_HISTORY (object);

	/* save the history */
	history_save (gh->priv);
	
	/* destroy hosts hash table */
	g_hash_table_foreach_remove (gh->priv->history_sites, (GHRFunc)
				     history_item_free_cb, gh);
	g_hash_table_destroy (gh->priv->history_sites);
	gh->priv->history_sites = NULL;

	/* destroy sites hash table */
	g_hash_table_foreach_remove (gh->priv->history_hosts, 
				     (GHRFunc)history_item_free_cb,
				     gh);
	g_hash_table_destroy (gh->priv->history_hosts);
	gh->priv->history_hosts = NULL;
	gh->priv->last_visited_site = NULL;

        g_return_if_fail (gh->priv != NULL);

        g_free (gh->priv);

        G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
global_history_init (GlobalHistory *gh)
{
        gh->priv = g_new0 (GlobalHistoryPrivate, 1);
	gh->priv->history_sites = NULL;
	gh->priv->history_hosts = NULL;
	gh->priv->history_dirty = FALSE;
	gh->priv->expire_days = 0;

	/* load the history */
	history_load (gh);

	/* setup the periodic history saving callback */
	g_timeout_add (HISTORY_SAVE_INTERVAL, 
		       (GSourceFunc)history_periodic_save_cb, 
		       gh->priv);
}

GlobalHistory *
global_history_new (void)
{
	return GLOBAL_HISTORY (g_object_new 
			       (GLOBAL_HISTORY_TYPE, NULL));
}

static void
history_update_last_visited_site (GlobalHistory *gh, HistoryItem *hi)
{
	if ((gh->priv->last_visited_site == NULL) 
	    || (hi->last > gh->priv->last_visited_site->last))
		gh->priv->last_visited_site = hi;
}

/**
 * history_add_host: add an host to the list
 */
static HistoryItem *
history_add_host (GlobalHistory *gh, 
		  const char *name)
{
	HistoryItem *host;
	
	g_return_val_if_fail (name, NULL);

	/* allocate */
	host = g_new0 (HistoryItem, 1);

	/* fill in structure */
	host->title = g_strdup (name);
	host->url = g_strdup (name);
	host->first = 0;
	host->last = 0;
	host->visits = 0;
	
	host->priv = g_new0 (HistoryItemPrivate, 1);
	host->priv->children = NULL;
	host->priv->properties = g_hash_table_new (g_str_hash, 
						   g_str_equal);
	
	/* add to the hosts hashtable */
	g_hash_table_insert (gh->priv->history_hosts, host->url, host);

	/* return completed structure */
	return host;
}

/*
 * history_get_host: get the host folder of the url
 */
static HistoryItem *
history_get_host (GlobalHistory *gh, 
		  const char *url)
{
	GnomeVFSURI *vfs_uri = NULL;
	const char *name = NULL;
	HistoryItem *host;

	/* check args */
	g_return_val_if_fail (url, NULL);

 	/* check if it's a local file */
 	if (!g_ascii_strncasecmp (url, "file://", 7))
 	{
 		/* if so, put it in a folder named "Local files" */
 		name = _("Local files");
  	}
	else
	{
		/* parse the url as a GnomeVFS uri */
		vfs_uri = gnome_vfs_uri_new (url);
		if (vfs_uri != NULL)
		{
			name = gnome_vfs_uri_get_host_name (vfs_uri);
		}

		/* handle failure gracefully */
		if (name == NULL)
		{
			/* FIXME gnome vfs doesnt handle https correctly */
			name = _("Other");
		}
	}

	/* lookup in table */
	host = g_hash_table_lookup (gh->priv->history_hosts, name);
	if (host == NULL)
	{
		/* new host */
		host = history_add_host (gh, name);
	}

	/* free uri -- this must be done here since the hostname returned
	 * is a constant string and part of the vfs_uri structure */
	if (vfs_uri != NULL)
	{
		gnome_vfs_uri_unref (vfs_uri);
	}

	/* return the appropriate folder */
	return host;
}

/**
 * Adds a new history item to the hashtable
 */
static HistoryItem *
history_add_item (GlobalHistory *gh,
		  char *url, 
		  char *title, 
		  GTime first, 
		  GTime last, 
		  gint visits)
{
	HistoryItem *hi;
	HistoryItem *host;

	g_return_val_if_fail (IS_GLOBAL_HISTORY (gh), NULL);
	g_return_val_if_fail (url, NULL);
	g_return_val_if_fail (title, NULL);

	/* allocate */
	hi = g_new0 (HistoryItem, 1);

	/* find the parent host */
	host = history_get_host (gh, url);
	g_return_val_if_fail (host, NULL);

	/* fill in the fields */
	hi->url = url;
	hi->title = title;
	hi->first = first;
	hi->last = last;
	hi->visits = visits;
	hi->priv = g_new0 (HistoryItemPrivate, 1);
	hi->priv->children = NULL;
	hi->priv->host = host;
	hi->priv->properties = g_hash_table_new (g_str_hash, 
						 g_str_equal);
	
	/* update the host */
	host->visits += visits;

	/* first item of host */
	host->first = first;
	host->last = last;
	host->priv->children = g_slist_append (host->priv->children, hi);

	/* add to the table */
        g_hash_table_insert (gh->priv->history_sites, 
			     hi->url, hi);

	/* update the last visited item */
	history_update_last_visited_site (gh, hi);

	global_history_emit_changed_data_changed (gh);

	/* return the finished item */
	return hi;
}

void     
global_history_visited (GlobalHistory *gh,
			const char *url)
{
        HistoryItem *hi;
        GTime now;

	/* check arguments */
	g_return_if_fail (url != NULL);

	/* get current time */
        now = time (NULL);

	/* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);
        if (hi != NULL)
	{
		/* update other info */
                hi->last = now;
                hi->visits++;

		g_return_if_fail (hi->priv->host != NULL);
		
		/* update host data as well */
		hi->priv->host->last = now;
		hi->priv->host->visits++;
		g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hi);
        }
	else
	{
		/* create a new item to add to the view */
		hi = history_add_item (gh,
				       g_strdup (url), 
				       g_strdup (_("Untitled")),
				       now, now, 1);

		if (hi)
		{
			g_signal_emit (G_OBJECT (gh), global_history_signals[ADD], 0, hi);
		}
        }

	/* we have changed the history, so save at next checkpoint */
	gh->priv->history_dirty = TRUE;
}

gboolean 
global_history_is_visited (GlobalHistory *gh,
			   const char *url)
{
        return (g_hash_table_lookup (gh->priv->history_sites, url) != NULL);
}

/**
 * Helper function to locate the newer HistoryItem
 */
static void 
history_look_for_newer (gpointer key, gpointer value, GlobalHistory *gh)
{
        HistoryItem *hi = (HistoryItem *) value;
	history_update_last_visited_site (gh, hi);
}

const char *
global_history_get_last_page (GlobalHistory *gh)
{
	if (gh->priv->last_visited_site == NULL)
	{
		g_hash_table_foreach (gh->priv->history_sites, 
				      (GHFunc)history_look_for_newer, 
				      gh->priv);
	}

        if (gh->priv->last_visited_site != NULL)
	{
                return gh->priv->last_visited_site->url;
        } 
	else
	{
                return NULL;
        }
}

gboolean 
global_history_set_page_title (GlobalHistory *gh,
			       const char *url, 
			       const char *title)
{
	HistoryItem *hi;

	/* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);

	/* check its there */
	if (hi == NULL)
	{
		/* FIXME: this certainly happens, should we add the
		 * URL to the history at this point? I don't know -MattA */
		return FALSE;
	}

        /* free old locale title */
	if (hi->title != NULL)
	{
		g_free (hi->title);
	}

	hi->title = g_strdup (title);

	/* we have changed the history, so save at next checkpoint */
	gh->priv->history_dirty = TRUE;

	g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hi);
	global_history_emit_changed_data_changed (gh);
	
	return TRUE;
}

const char *
global_history_get_page_title (GlobalHistory *gh,
			       const char *url)
{
	HistoryItem *hi;

	hi = g_hash_table_lookup (gh->priv->history_sites, url);

	if (hi) return hi->title;
	else return NULL;
}


gboolean 
global_history_set_host_zoom (GlobalHistory *gh,
			      const char *url, 
			      gint zoom)
{
	HistoryItem *hi;
	HistoryItem *hh;

        hi = g_hash_table_lookup (gh->priv->history_sites, url);

	if (hi == NULL)
	{
		return FALSE;
	}

	hh = hi->priv->host;

	hh->priv->zoom = zoom;

	/* we have changed the history, so save at next checkpoint */
	gh->priv->history_dirty = TRUE;

	g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hh);
	global_history_emit_changed_data_changed (gh);
	
	return TRUE;
}

/*
 * history_get_host_zoom: gets the zoom setting for the host refernced in
 * the url.
 *
 * Returns -1 if there is no zoom set for the host.
 */
gint
global_history_get_host_zoom (GlobalHistory *gh,
			      const char *url)
{
	HistoryItem *hi;
	HistoryItem *hh;

	hi = g_hash_table_lookup (gh->priv->history_sites, url);

	if (!hi || !hi->priv->host) return -1;

	hh = hi->priv->host;

	return hh->priv->zoom;
}


gboolean 
global_history_remove_url (GlobalHistory *gh,
			   const char *url)
{
        HistoryItem *hi;

	/* check arguments */
	g_return_val_if_fail (url != NULL, FALSE);
	
       /* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);

	if (hi)
	{	
		g_signal_emit (G_OBJECT (gh), global_history_signals[REMOVE], 0, hi);
		history_item_free_cb ((gpointer)url, hi, gh);
		global_history_emit_changed_data_changed (gh);
		gh->priv->history_dirty = TRUE;
		return FALSE;
	}
	
	return FALSE;
}

void
global_history_clear (GlobalHistory *gh)
{
	GlobalHistoryPrivate *p = gh->priv;
	GHashTable *old_history_sites = p->history_sites;
	GHashTable *old_history_hosts = p->history_hosts;

	/* no last_visited_site */
	p->last_visited_site = NULL;

	p->history_hosts = g_hash_table_new (g_str_hash, g_str_equal);
	p->history_sites = g_hash_table_new (g_str_hash, g_str_equal);

	global_history_emit_changed_data_changed (gh);

	/* clear the file */
	gh->priv->history_dirty = TRUE;
	history_save (gh->priv);

	/* Destroy old history hash table */
	g_hash_table_foreach (old_history_sites, 
			      (GHFunc)history_item_free_cb, 
			      gh);
	g_hash_table_destroy (old_history_sites);

	/* destroy old hosts hash table */
	g_hash_table_foreach (old_history_hosts, 
			      (GHFunc)history_item_free_cb, 
			      gh);
	g_hash_table_destroy (old_history_hosts);

}

/**
 * history_add_host_to_list: callback to add server structure to a list
 */
static void 
history_add_host_to_list (gpointer key, HistoryItem *host, GList **list)
{
	/* add it to the list */
	*list = g_list_prepend (*list, host);
}

/**
 * global_history_get_host_list: get a fresh linked list of all the hosts
 */
GList *
global_history_get_host_list (GlobalHistory *gh)
{
	GList *list = NULL;

	g_hash_table_foreach (gh->priv->history_hosts, 
			      (GHFunc)history_add_host_to_list, &list);

	return list;
}

void 
global_history_set_page_property  (GlobalHistory *gh,
				   HistoryItem *hi,
				   const char *key,
				   const char *value)
{
	g_hash_table_replace (hi->priv->properties,
			      (gpointer)g_strdup(key),
			      (gpointer)g_strdup(value));
}

char * 
global_history_get_page_property  (GlobalHistory *gh,
				   HistoryItem *hi,
				   const gchar *key)
{
	return g_hash_table_lookup (hi->priv->properties,
				    (gpointer)key);
}

static gboolean
filter_by_word (const char *url, 
		const char *word)
{
	return (g_strrstr (url, word) != NULL);
}

static gboolean
filter_by_date (int filter_type,
		GTime atime)
{
        GDate date, current_date;
        gboolean result;

        g_date_clear (&current_date, 1);
        g_date_set_time (&current_date, time (NULL));

        g_date_clear (&date, 1);
        g_date_set_time (&date, atime);

        switch (filter_type)
        {
                /* Always */
        case 0:
                return TRUE;
                /* Today */
        case 1:
                break;
                /* Last two days */
        case 2:
                g_date_subtract_days (&current_date, 1);
                break;
                /* Last three days */
        case 3:
                g_date_subtract_days (&current_date, 2);
                break;
                /* Week */
        case 4:
                g_date_subtract_days (&current_date, 7);
                break;
                /* Month */
        case 5:
                g_date_subtract_months (&current_date, 1);
                break;
        default:
                break;
        }

        result = (g_date_compare (&date, &current_date) >= 0);

        return result;
}

static void 
history_add_url_to_list (HistoryItem *item,
			 HistoryFilter *filter,
			 GList **list)
{
	gboolean add = TRUE;
	
	if (filter != NULL)
	{
		add = ((filter_by_date (filter->type, item->last)) &&
		       (filter_by_word (item->url, filter->word)));
	}
	
	/* add it to the list */
	if (add)
	{
		*list = g_list_prepend (*list, item);
	}
}

GList *
global_history_get_urls_list (GlobalHistory *gh,
                              HistoryItem *item,
			      HistoryFilter *filter)
{
	GList *list = NULL;
	GSList *l;
	
	g_return_val_if_fail (item != NULL, NULL);
	g_return_val_if_fail (item->priv != NULL, NULL);

	for (l = item->priv->children; l != NULL; l = l->next)
	{
		HistoryItem *item = l->data;
		history_add_url_to_list (item, filter, &list);
	}
	
	return list;
}

HistoryItem *
global_history_get_host_from_site (GlobalHistory *gh,
				   HistoryItem *site)
{
	return site->priv->host;
}

static void 
global_history_autocompletion_source_foreach_aux (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hi = (HistoryItem *) value;
	gpointer *little_hack = user_data;
	((GaleonAutocompletionSourceForeachFunc) little_hack[1]) 
		(little_hack[0], hi->url, hi->title, 
		 /* very simple scoring function. Most recent entries get higher scores and each
		    visit makes the item like 1.5 days younger aprox. */
		    hi->last + (guint) (hi->visits << 17),
		    little_hack[2]);
}

static void
global_history_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
						    const gchar *basic_key)
{
	/* nothing to do here */
}

static void
global_history_autocompletion_source_foreach (GaleonAutocompletionSource *source,
					      const gchar *current_text,
					      GaleonAutocompletionSourceForeachFunc func,
					      gpointer data)
{
	gpointer little_hack[3] = { source, func, data };
	GlobalHistoryPrivate  *p = GLOBAL_HISTORY (source)->priv;
	g_hash_table_foreach (p->history_sites, global_history_autocompletion_source_foreach_aux, 
			      little_hack);
}

static void
global_history_emit_changed_data_changed (GlobalHistory *gh)
{
	g_signal_emit_by_name (gh, "data-changed");
}

