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

/*
 *  Copyright (C) 2004 Hiroyuki Ikezoe
 *
 *  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 <ctype.h>
#include <glib/gi18n.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <estraier.h>
#include <estmtdb.h>

#include "kazehakase.h"
#include "utils/utils.h"
#include "glib-utils.h"
#include "kz-search-common.h"
#include "kz-search-hyper-estraier.h"
#include "egg-pixbuf-thumbnail.h"

#define HISTORY_INDEX "history_index.hest"


typedef struct _KzSearchHyperEstraierPrivate	KzSearchHyperEstraierPrivate;
struct _KzSearchHyperEstraierPrivate
{
	ESTMTDB *db;
	gchar *dbname;
	gchar *cache_path;
	size_t cache_path_len;
};

typedef struct _KzSearchHyperEstraierClass	KzSearchHyperEstraierClass;
struct _KzSearchHyperEstraierClass
{
	KzSearchClass parent_class;
};

#define KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_SEARCH_HYPER_ESTRAIER, KzSearchHyperEstraierPrivate))

#define KZ_SEARCH_HYPER_ESTRAIER_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), KZ_TYPE_SEARCH_HYPER_ESTRAIER, KzSearchHyperEstraierClass))
#define KZ_IS_SEARCH_HYPER_ESTRAIER_CLASS(klass) 	(G_TYPE_CHECK_CLASS_TYPE ((klass), KZ_TYPE_SEARCH_HYPER_ESTRAIER))
#define KZ_SEARCH_HYPER_ESTRAIER_GET_CLASS(obj)  	(G_TYPE_INSTANCE_GET_CLASS ((obj), KZ_TYPE_SEARCH_HYPER_ESTRAIER, KzSearchHyperEstraierClass))

/* for module */
void        kz_search_module_init		(GTypeModule *module);
void        kz_search_module_exit		(void);
KzSearch   *kz_search_module_create		(void);

/* KzSearchHyperEstraier Class */
static void   kz_search_hyper_estraier_class_init (KzSearchHyperEstraierClass *klass);
static void   kz_search_hyper_estraier_init       (KzSearchHyperEstraier *search);

/* GObject Class */
static GObject *constructor  (GType type,
                              guint n_props,
                              GObjectConstructParam *props);
static void     dispose      (GObject *object);

/* KzSearch Class */
static gchar	  *get_search_result_html     (KzSearch *search, const gchar *text);
static KzBookmark *get_search_result_bookmark (KzSearch *search, const gchar *text);
static gboolean    register_document          (KzSearch *search,
					       const gchar *uri,
					       const gchar *title,
					       const gchar *contents,
					       GTime mtime);
static gboolean    unregister_document        (KzSearch *search, const gchar *uri);
static GPid        optimize_index             (KzSearch *search);
static void        make_index                 (KzSearch *search);
static gboolean    exist_index_dir            (KzSearch *search);

static KzSearchHyperEstraier *the_kz_search_hyper_estraier = NULL;

static GObjectClass *parent_class;
static GType kz_search_hyper_estraier_type = 0;

static void
kz_search_hyper_estraier_register_type (GTypeModule *module)
{
	static const GTypeInfo kz_search_hyper_estraier_info =
	{
		sizeof (KzSearchHyperEstraierClass),
		NULL,		/* base_init */
		NULL,		/* base_finalize */
		(GClassInitFunc) kz_search_hyper_estraier_class_init,
		NULL,		/* class_finalize */
		NULL,		/* class_data */
		sizeof (KzSearchHyperEstraier),
		0,		/* n_preallocs */
		(GInstanceInitFunc) kz_search_hyper_estraier_init,
	};

	kz_search_hyper_estraier_type = g_type_module_register_type (module,
			KZ_TYPE_SEARCH,
			"KzSearchHyperEstraier",
			&kz_search_hyper_estraier_info, 0);
}

G_MODULE_EXPORT void 
kz_search_module_init (GTypeModule *module)
{
	kz_search_hyper_estraier_register_type(module);
}

G_MODULE_EXPORT void 
kz_search_module_exit (void)
{
}

G_MODULE_EXPORT KzSearch * 
kz_search_module_create (void)
{
	return kz_search_hyper_estraier_new();
}

GType
kz_search_hyper_estraier_get_type (void)
{
	return kz_search_hyper_estraier_type;
}

static void
kz_search_hyper_estraier_class_init (KzSearchHyperEstraierClass *klass)
{
	GObjectClass *object_class;
	KzSearchClass *search_class;

	parent_class = g_type_class_peek_parent (klass);
	object_class = (GObjectClass *) klass;
	search_class = (KzSearchClass *) klass;

	object_class->constructor = constructor;
	object_class->dispose     = dispose;
	
	search_class->get_search_result_html     = get_search_result_html;
	search_class->get_search_result_bookmark = get_search_result_bookmark;
	search_class->register_document          = register_document;
	search_class->unregister_document        = unregister_document;
	search_class->optimize_index             = optimize_index;
	search_class->make_index                 = make_index;
	search_class->exist_index_dir            = exist_index_dir;

	g_type_class_add_private (object_class, sizeof(KzSearchHyperEstraierPrivate));
}


static void
kz_search_hyper_estraier_init (KzSearchHyperEstraier *search)
{
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	priv->db = NULL;
	priv->cache_path = g_build_filename(g_get_home_dir(), HISTORY_DIR, NULL);
	priv->dbname = g_build_filename(g_get_home_dir(), "."PACKAGE, HISTORY_INDEX, NULL);

	priv->cache_path_len = strlen(priv->cache_path);
}

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

	if (!the_kz_search_hyper_estraier) 
	{
		GObjectClass *klass = G_OBJECT_CLASS(parent_class);
		object = klass->constructor(type, n_props, props);
		the_kz_search_hyper_estraier = KZ_SEARCH_HYPER_ESTRAIER(object);
	}
	else
	{
		object = g_object_ref(G_OBJECT(the_kz_search_hyper_estraier));
	}
	return object;
}

static void
dispose (GObject *object)
{
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(object);
	
	if (priv->db)
	{
		int ecode, ret;
		ret = est_mtdb_close(priv->db, &ecode);
		if (!ret)
			g_warning("db close error: %s", est_err_msg(ecode));
	}
	if (priv->dbname)
		g_free(priv->dbname);
	if (priv->cache_path)
		g_free(priv->cache_path);

	priv->db = NULL;
	priv->dbname = NULL;
	priv->cache_path = NULL;

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


KzSearch *
kz_search_hyper_estraier_new (void)
{
	return KZ_SEARCH(g_object_new(KZ_TYPE_SEARCH_HYPER_ESTRAIER, NULL));
}

static gchar *
create_search_result_html (KzSearch *search, const gchar *text)
{
	ESTMTDB *db;
	ESTCOND *cond;
	CBLIST *highlights;
	int ecode, *results, n_results, i, ret;
	gchar *except_word, *tmp;
	gchar **texts;
	GString *html, *phrase;
	gint num_summary = 128, max_results = 20, half_of_summary;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	db = est_mtdb_open(priv->dbname, ESTDBREADER, &ecode);
	if (!db)
	{
		g_warning("db open error: %s", est_err_msg(ecode));
		return NULL;
	}

	cond = est_cond_new();
	texts = g_strsplit(text, " ", -1);
	tmp = g_strjoinv(" " ESTOPISECT " ", texts);

	highlights = cblistopen();
	for (i = 0; texts[i]; i++)
	{
		cblistpush(highlights, texts[i], -1);
	}

	g_strfreev(texts);
	phrase = g_string_new(tmp);

	except_word = KZ_CONF_GET_STR("History", "except_keyword");
	if (except_word && *except_word)
	{
		texts = g_strsplit(except_word, ",", -1);
		g_free(except_word);
		i = 0;
		while (texts[i])
		{
			phrase = g_string_append(phrase, " " ESTOPDIFF " ");
			phrase = g_string_append(phrase, texts[i]);
			i++;
		}
		g_strfreev(texts);
	}
	est_cond_set_phrase(cond, phrase->str);
	g_string_free(phrase, TRUE);

	KZ_CONF_GET("History", "num_summary", num_summary, INT);
	KZ_CONF_GET("History", "max_results", max_results, INT);
	half_of_summary = num_summary / 2;

	results = est_mtdb_search(db, cond, &n_results, NULL);

	html = g_string_sized_new(0);

	g_string_append(html, DTD"\n");
	g_string_append(html, "<html>\n");
	g_string_append(html, HEAD);
	g_string_append(html, "<body>\n");

	g_string_append_printf(html, "<h1>Search results for %s</h1>",
			       text);

	for (i = 0; i < MIN(n_results, max_results); i++)
	{
		ESTDOC *doc;
		const gchar *title, *uri, *date;
		gchar *desc, *cache_str = NULL;
		const gchar *cache_link, *thumb_uri, *thumb_filename;

		doc = est_mtdb_get_doc(db, results[i], 0);
		if (!doc)
			continue;

		cache_link = est_doc_attr(doc, ESTDATTRURI);
		uri = create_uri_from_filename(cache_link + 
					       strlen("file://") + priv->cache_path_len);
		thumb_filename = egg_pixbuf_get_thumb_filename(uri,
							       EGG_PIXBUF_THUMB_LARGE);
		thumb_uri = g_strdup_printf("history-search:?image=%s",
					    thumb_filename);
		title = est_doc_attr(doc, ESTDATTRTITLE);
		date = est_doc_attr(doc, ESTDATTRMDATE);

		desc = est_doc_make_snippet(doc, highlights, num_summary, half_of_summary, half_of_summary);

		if (g_file_test(cache_link+strlen("file://"), G_FILE_TEST_EXISTS))
			cache_str = g_strdup_printf("<span class=\"cache\"><a href=\"%s\">cache</a></span>\n", cache_link);
		else
			cache_str = g_strdup("");

		g_string_append_printf(html,
				       CONTENT,
				       uri,
				       title,
				       thumb_uri, /* thumbnail */
				       desc,
				       uri,
				       cache_str,
				       date);
		g_free(cache_str);
		g_free(desc);
		est_doc_delete(doc);
	}
	free(results);

	cblistclose(highlights);
	est_cond_delete(cond);

	ret = est_mtdb_close(db, &ecode);
	if (!ret)
		g_warning("db close error: %s", est_err_msg(ecode));

	g_string_append_printf(html, FOOTER, _EST_PROJURL, "Hyper Estraier", _EST_VERSION);
	g_string_append(html, "</body></html>");

	return g_string_free(html, FALSE);
}


gchar *
get_search_result_html (KzSearch *search, const gchar *text)
{
	if (!text) return NULL;

	return create_search_result_html(search, text);
}

static gchar *
get_document_encoding (const gchar *contents)
{
	gchar *encoding = NULL;
	gchar *p;

	if (!contents) return NULL;

	p = (gchar*)contents;
	while ((p = strstr(p, "<meta ")))
	{
		gchar *end;
		p = strstr(p, "http-equiv=\"");
		if (!p) break;
		
		p+=12;
		end = strchr(p, '"');
		if (!end) break;

		if (g_ascii_strncasecmp(p, "content-type", end - p))
			continue;

		p = end;
		/* negligent */
		p = strstr(p, "charset=");				
		if (!p) break; 
		p+=8;
		end = strchr(p, ';');
		if (!end)
			end = strchr(p, '"');
		if (!end) break;
	
		encoding = g_strndup(p, end - p);
		break;
	}

	return encoding;
}

gboolean
register_document (KzSearch *search, const gchar *uri, const gchar *title, const gchar *contents, GTime mtime)
{
	ESTMTDB *db;
	ESTDOC *doc;
	int ret, ecode, success;
	gchar *time_str, *filename, *text = NULL;
	gchar *tmp_filename, *tmp_path;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	db = est_mtdb_open(priv->dbname, ESTDBWRITER | ESTDBCREAT, &ecode);
	if (!db)
	{
		g_warning("db open error: %s", est_err_msg(ecode));
		return FALSE;
	}

	doc = est_doc_new();

	tmp_filename = create_filename_with_path_from_uri(uri);	
	tmp_path = g_build_filename(priv->cache_path, tmp_filename, NULL);
	filename = g_strdup_printf("file://%s", tmp_path);
	g_free(tmp_path);
	g_free(tmp_filename);
	est_doc_add_attr(doc, ESTDATTRURI, filename);
	g_free(filename);

	if (title)
		est_doc_add_attr(doc, ESTDATTRTITLE, title);

	time_str = cbdatestrwww(mtime, 0);
	est_doc_add_attr(doc, ESTDATTRMDATE, time_str);
	g_free(time_str);

	text = html_to_text(contents);
	if (text)
	{
		est_doc_add_text(doc, text);
		g_free(text);
	}

	success = est_mtdb_put_doc(db, doc, ESTPDCLEAN);
	if (!success)
		g_warning("register error: %s", est_err_msg(ecode));

	est_doc_delete(doc);

	ret = est_mtdb_close(db, &ecode);
	if (!ret)
		g_warning("db close error: %s", est_err_msg(ecode));

	return (success != 0) ? TRUE : FALSE;
}

gboolean
unregister_document (KzSearch *search, const gchar *uri)
{
	ESTMTDB *db;
	int id, ret, ecode, success;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	db = est_mtdb_open(priv->dbname, ESTDBREADER, &ecode);
	if (!db)
	{
		g_warning("db open error: %s", est_err_msg(ecode));
		return FALSE;
	}

	id = est_mtdb_uri_to_id(db, uri);
	if (id == -1)
		return FALSE;
	success = est_mtdb_out_doc(db, id, ESTODCLEAN);

	ret = est_mtdb_close(db, &ecode);
	if (!ret)
		g_warning("db close error: %s", est_err_msg(ecode));

	return (success != 0) ? TRUE : FALSE;
}

static GPid
optimize_index (KzSearch *search)
{
	ESTMTDB *db;
	int ret, ecode;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	db = est_mtdb_open(priv->dbname, ESTDBREADER, &ecode);
	if (!db)
	{
		g_warning("db open error: %s", est_err_msg(ecode));
		return FALSE;
	}

	est_mtdb_optimize(db, 0);

	ret = est_mtdb_close(db, &ecode);
	if (!ret)
		g_warning("db close error: %s", est_err_msg(ecode));
#warning FIXME! return GSource or something.
	return 0;
}

static KzBookmark *
get_search_result_bookmark (KzSearch *search, const gchar *text)
{
	/* not implemented yet */
	return NULL;
}

static void
register_documents_in_path (KzSearch *search, const gchar *path)
{
	GDir *gd;
	const gchar *file;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	gd = g_dir_open(path, 0, NULL);
	if (!gd)
		return;

	while ((file = g_dir_read_name (gd)))
	{
		gchar *new_path = g_build_filename (path, file, NULL);
		if (g_file_test(new_path, G_FILE_TEST_IS_DIR))
		{
			register_documents_in_path(search, new_path);
		}
		else
		{
			gchar *uri, *contents;
			gchar *encoding = NULL;
			GTime mtime;
			struct stat st;

			g_file_get_contents(new_path, &contents, NULL, NULL);
			g_stat(new_path, &st);
			mtime = st.st_mtime;
			uri = create_uri_from_filename(new_path + strlen("file://") + priv->cache_path_len);

			encoding = get_document_encoding(contents);
			if (!encoding)
				encoding = g_strdup(est_enc_name(contents, strlen(contents), ESTLANGJA));
	
			if (encoding && strcmp(encoding, "UTF-8"))
			{
				gchar *utf8_contents;
				utf8_contents = g_convert(contents, -1,
							  "UTF-8", encoding,
							  NULL, NULL,
							  NULL);
				kz_search_register_document(search, uri, NULL, utf8_contents, mtime);
				g_free(utf8_contents);
			}
			else
				kz_search_register_document(search, uri, NULL, contents, mtime);
 
 			if (encoding)
				g_free(encoding);
			g_free(uri);
			g_free(contents);
		}
		g_free(new_path);
	}
	g_dir_close (gd);
}

static void
make_index (KzSearch *search)
{
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	register_documents_in_path(search, priv->cache_path);
}

static gboolean
exist_index_dir(KzSearch *search)
{
	gboolean exist = FALSE;
	KzSearchHyperEstraierPrivate *priv = KZ_SEARCH_HYPER_ESTRAIER_GET_PRIVATE(search);

	exist = g_file_test(priv->dbname, G_FILE_TEST_IS_DIR);

	return exist;
}
