/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>  
 *
 *
 * Authors:
 *		Srinivasa Ragavan <sragavan@novell.com>
 *
 * Copyright (C) 2009 Novell, Inc. (www.novell.com)
 *
 */

#include <string.h>
#include <stdlib.h>

#include <glib-object.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "mail-sqlite-store.h"
#include <e-util/e-util.h>
#include <sqlite3.h>

#include <camel/camel-db.h>

#define d(x)

#define MAIL_SQLITE_STORE_GET_PRIVATE(o) ((MailSqliteStore *) o)->priv

struct _MailSqliteStorePrivate {
    gchar   *table;
    sqlite3 *dbh;
   
    int pos;
    sqlite3_stmt *stmt;

    int iterpos;
    int count;

    GPtrArray *dcache;
};
static int cache_count = 1000;
/* GObject implementations */
static void              mail_sqlite_store_init            (MailSqliteStore       *self);
static void              mail_sqlite_store_class_init      (MailSqliteStoreClass  *klass);
static void              mail_sqlite_store_finalize        (GObject              *obj);

/* GtkTreeModelIface implementation */
static void              mail_sqlite_store_tree_model_init (GtkTreeModelIface *iface);
static GtkTreeModelFlags mail_sqlite_store_get_flags       (GtkTreeModel      *tree_model);
static gint              mail_sqlite_store_get_n_columns   (GtkTreeModel      *tree_model);
static GType             mail_sqlite_store_get_column_type (GtkTreeModel      *tree_model,
                                                           gint               index);
static gboolean          mail_sqlite_store_get_iter        (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter,
                                                           GtkTreePath       *path);
static GtkTreePath*      mail_sqlite_store_get_path        (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter);
static void              mail_sqlite_store_get_value       (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter,
                                                           gint               column,
                                                           GValue            *value);
static gboolean          mail_sqlite_store_iter_next       (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter);
static gboolean          mail_sqlite_store_iter_children   (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter,
                                                           GtkTreeIter       *parent);
static gboolean          mail_sqlite_store_iter_has_child  (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter);
static gint              mail_sqlite_store_iter_n_children (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter);
static gboolean          mail_sqlite_store_iter_nth_child  (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter,
                                                           GtkTreeIter       *parent,
                                                           gint               n);
static gboolean          mail_sqlite_store_iter_parent     (GtkTreeModel      *tree_model,
                                                           GtkTreeIter       *iter,
                                                           GtkTreeIter       *child);

static gint mail_sqlite_store_count_rows 	(sqlite3 *sqlite, gchar *table);
#ifdef NOT_USED
static gint mail_sqlite_store_fetch_n_columns (sqlite3 *sqlite, gchar *table);
#endif

static GObjectClass *parent_class = NULL;

GType
mail_sqlite_store_get_type (void)
{
	static GType my_type = 0;
	if (!my_type) {
		static const GTypeInfo my_info = {
			sizeof(MailSqliteStoreClass),
			NULL,										 /* base init	  */
			NULL,										 /* base finalize  */
			(GClassInitFunc) mail_sqlite_store_class_init,
			NULL,										 /* class finalize */
			NULL,										 /* class data	 */
			sizeof (MailSqliteStore),
			1,											/* n_preallocs	*/
			(GInstanceInitFunc) mail_sqlite_store_init,
			NULL
		};
		my_type = g_type_register_static (G_TYPE_OBJECT, "MailSqliteStore",
										  &my_info, (GTypeFlags) 0);

		static const GInterfaceInfo tree_model_info = {
			(GInterfaceInitFunc) mail_sqlite_store_tree_model_init,
			NULL, NULL};
		g_type_add_interface_static (my_type, GTK_TYPE_TREE_MODEL,
									 &tree_model_info);
	}
	return my_type;
}

static void
mail_sqlite_store_class_init (MailSqliteStoreClass *klass)
{
	GObjectClass *gobject_class;
	gobject_class = (GObjectClass*) klass;

	parent_class = g_type_class_peek_parent (klass);
	gobject_class->finalize = mail_sqlite_store_finalize;

}

static void
mail_sqlite_store_tree_model_init (GtkTreeModelIface *iface)
{
	iface->get_flags	   = mail_sqlite_store_get_flags;
	iface->get_n_columns   = mail_sqlite_store_get_n_columns;
	iface->get_column_type = mail_sqlite_store_get_column_type;
	iface->get_iter        = mail_sqlite_store_get_iter;
	iface->get_path        = mail_sqlite_store_get_path;
	iface->get_value	   = mail_sqlite_store_get_value;
	iface->iter_next	   = mail_sqlite_store_iter_next;
	iface->iter_children   = mail_sqlite_store_iter_children;
	iface->iter_has_child  = mail_sqlite_store_iter_has_child;
	iface->iter_n_children = mail_sqlite_store_iter_n_children;
	iface->iter_nth_child  = mail_sqlite_store_iter_nth_child;
	iface->iter_parent     = mail_sqlite_store_iter_parent;
}

static void
mail_sqlite_store_init (MailSqliteStore *self)
{
	MailSqliteStorePrivate *priv;
	
	self->n_columns = 0;
	self->stamp = g_random_int ();

	priv = g_new0 (MailSqliteStorePrivate, 1);
	self->priv = priv;
	
	priv->pos = -1;
	priv->iterpos = -1;
	priv->dbh = NULL;
	priv->stmt = NULL;

	priv->dcache = g_ptr_array_new ();
}

static void
mail_sqlite_store_finalize (GObject *self)
{
	MailSqliteStorePrivate *priv;

	g_return_if_fail (MAIL_IS_SQLITE_STORE (self));

	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	if (priv->dbh)
		sqlite3_close (priv->dbh);

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

static GtkTreeModelFlags
mail_sqlite_store_get_flags (GtkTreeModel *tree_model)
{
	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model),
						  (GtkTreeModelFlags) 0);

	return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
}

static gint
mail_sqlite_store_get_n_columns (GtkTreeModel *tree_model)
{
	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), 0);
	return NUM_COLS;
}

static GType
mail_sqlite_store_get_column_type (GtkTreeModel *tree_model, gint index)
{
	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model),
						  G_TYPE_INVALID);
	g_return_val_if_fail (index < MAIL_SQLITE_STORE (tree_model)->n_columns
						  && index >= 0, G_TYPE_INVALID);
	return G_TYPE_STRING;
}

static gboolean
mail_sqlite_store_get_iter (GtkTreeModel *tree_model,
						   GtkTreeIter  *iter,
						   GtkTreePath  *path)
{
	MailSqliteStore		*self;
	MailSqliteStorePrivate *priv;
	gint				  *indices, depth;

	g_assert (MAIL_IS_SQLITE_STORE (tree_model));
	g_assert (path != NULL);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	indices = gtk_tree_path_get_indices (path);
	depth = gtk_tree_path_get_depth (path);

	g_assert (depth == 1);
	
	d(printf("get_iter for level %d %d\n", indices[0], priv->fpos));

	priv->iterpos = indices[0];
	iter->stamp = self->stamp;
	iter->user_data = GINT_TO_POINTER(indices[0]);
	iter->user_data2 = NULL;
	iter->user_data3 = NULL;

	return TRUE;
}


static GtkTreePath*
mail_sqlite_store_get_path (GtkTreeModel *tree_model,
						   GtkTreeIter  *iter)
{
	MailSqliteStore        *self;
	MailSqliteStorePrivate *priv;
	GtkTreePath           *path;

	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), NULL);
	g_return_val_if_fail (iter != NULL, NULL);
	g_return_val_if_fail ((int)iter->user_data != -1, NULL);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path, (int)iter->user_data);

	return path;
}

static char *
filter_date (time_t date)
{
	time_t nowdate = time(NULL);
	time_t yesdate;
	struct tm then, now, yesterday;
	char buf[26];
	gboolean done = FALSE;

	if (date == 0)
		return g_strdup (_("?"));

	localtime_r (&date, &then);
	localtime_r (&nowdate, &now);
	if (then.tm_mday == now.tm_mday &&
	    then.tm_mon == now.tm_mon &&
	    then.tm_year == now.tm_year) {
		e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
		done = TRUE;
	}
	if (!done) {
		yesdate = nowdate - 60 * 60 * 24;
		localtime_r (&yesdate, &yesterday);
		if (then.tm_mday == yesterday.tm_mday &&
		    then.tm_mon == yesterday.tm_mon &&
		    then.tm_year == yesterday.tm_year) {
			e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
			done = TRUE;
		}
	}
	if (!done) {
		int i;
		for (i = 2; i < 7; i++) {
			yesdate = nowdate - 60 * 60 * 24 * i;
			localtime_r (&yesdate, &yesterday);
			if (then.tm_mday == yesterday.tm_mday &&
			    then.tm_mon == yesterday.tm_mon &&
			    then.tm_year == yesterday.tm_year) {
				e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
				done = TRUE;
				break;
			}
		}
	}
	if (!done) {
		if (then.tm_year == now.tm_year) {
			e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
		} else {
			e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
		}
	}
#if 0
#ifdef CTIME_R_THREE_ARGS
	ctime_r (&date, buf, 26);
#else
	ctime_r (&date, buf);
#endif
#endif

	return g_strdup (buf);
}

static void
mail_sqlite_store_get_value (GtkTreeModel *tree_model,
							GtkTreeIter  *iter,
							gint		  column,
							GValue	   *value)
{
	MailSqliteStore		*self;
	MailSqliteStorePrivate *priv;
	MailSqliteRecord *precord;
	guint pos = (int)iter->user_data;
	g_return_if_fail (MAIL_IS_SQLITE_STORE (tree_model));
	g_return_if_fail (iter != NULL || (int)iter->user_data != -1);
	g_return_if_fail (column < MAIL_SQLITE_STORE (tree_model)->n_columns);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	if (priv->dcache->pdata[pos] == NULL) {
		if (pos != priv->pos -1) {
			gboolean forward = TRUE;

			if (pos < priv->pos)
				forward = FALSE;

			d(printf("Moving cursor from %d to %d: \n", priv->pos, pos));
			if (!forward) {
				int tpos;

				if (pos == 0 || pos - cache_count < 0)
					tpos = 0;
				else
					tpos = pos - cache_count;
				sqlite3_reset (priv->stmt);
				sqlite3_bind_int (priv->stmt, 1, -1);
				sqlite3_bind_int (priv->stmt, 2, tpos );
				sqlite3_step (priv->stmt);
				priv->pos = tpos;
			} else {	
				while (pos != priv->pos) {
					int ret = sqlite3_step (priv->stmt);
					if (ret == SQLITE_DONE)
						break;
					priv->pos++;
					if (!forward && priv->pos == pos - cache_count)
						break;
				}
			}

			if (forward && pos != priv->pos)
				g_assert(0);

			while ( (forward && priv->pos != pos + cache_count) || (!forward && priv->pos != pos+1)) {
				MailSqliteRecord *record;
				int ret;

				if (priv->dcache->pdata[priv->pos] == NULL) {
					char **txt;
					record = g_slice_alloc (sizeof(MailSqliteRecord));
					record->uid = g_strdup((char *)sqlite3_column_text (priv->stmt, 0));
					txt  = g_strsplit ((char *)sqlite3_column_text (priv->stmt, 1), "<", 2);
					record->from = g_strdup (*txt);
					g_strfreev (txt);
					record->subject = g_strdup_printf("%s",sqlite3_column_text (priv->stmt, 2));
					record->date =sqlite3_column_int (priv->stmt, 3) ?  filter_date(sqlite3_column_int(priv->stmt, 3)) : NULL;
					/* FIXME: Verify this flags conversion */
					record->flags = sqlite3_column_int(priv->stmt, 4);					
					priv->dcache->pdata[priv->pos] = record;
				}

				ret = sqlite3_step (priv->stmt);
				if (ret == SQLITE_DONE) {
					sqlite3_reset (priv->stmt);
					priv->pos = -1;
					break;
				}
				priv->pos++;
			}

		}
	}

	precord = priv->dcache->pdata[pos];
	if (column < COL_FLAGS ) {
		g_value_init (value, G_TYPE_STRING);

		if (column == COL_UID)
			g_value_set_string (value, precord->uid);
		if (column == COL_FROM)
			g_value_set_string (value, precord->from);
		if (column == COL_SUBJECT)
			g_value_set_string (value, precord->subject);
		if (column == COL_DATE)
			g_value_set_string (value, precord->date);
	} else if (column == COL_FLAGS) {
		g_value_init (value, G_TYPE_UINT);
		g_value_set_uint (value, precord->flags);
	} else if (column == COL_RECORD) {
		g_value_init (value, G_TYPE_POINTER);
		g_value_set_pointer (value, precord);		
	}
}

static gboolean
mail_sqlite_store_iter_next (GtkTreeModel *tree_model,
							GtkTreeIter  *iter)
{
	MailSqliteStore		  *self;
	MailSqliteStorePrivate *priv;

	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), FALSE);
	if (iter == NULL || (int)iter->user_data == -1)
	  return FALSE;

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	d(printf("Iter next: %d to %d: Count %d\n", priv->iterpos, priv->iterpos+1, priv->count));

	priv->iterpos++;
	if (priv->iterpos == priv->count) {
		priv->iterpos--;
		return FALSE;
	}

	iter->user_data = GINT_TO_POINTER(priv->iterpos);
	iter->user_data2 = NULL;
	iter->user_data3 = NULL;

	return TRUE;
}

static gboolean
mail_sqlite_store_iter_children (GtkTreeModel *tree_model,
								GtkTreeIter  *iter,
								GtkTreeIter  *parent)
{
	MailSqliteStore		*self;
	MailSqliteStorePrivate *priv;

	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), FALSE);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);
	d(printf("tree iter_children  %d %d \n", iter, parent));

	priv->iterpos++;
	if (priv->iterpos == priv->count) {
		priv->iterpos--;
		return FALSE;
	}

	iter->stamp = self->stamp;
	iter->user_data = GINT_TO_POINTER(priv->iterpos);
	iter->user_data2 = NULL;
	iter->user_data3 = NULL;
	return TRUE;
}

static gboolean
mail_sqlite_store_iter_has_child (GtkTreeModel *tree_model,
							GtkTreeIter  *iter)
{
	return FALSE;
}

static gint
mail_sqlite_store_iter_n_children (GtkTreeModel *tree_model,
								  GtkTreeIter  *iter)
{
	MailSqliteStore		*self;
	MailSqliteStorePrivate *priv;

	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), -1);
	g_return_val_if_fail (iter == NULL || (int)iter->user_data != -1, -1);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);
	d(printf("tree iter_n_children  \n"));

	if (!iter) {
		return priv->count;
	}

	return 0;
}

static gboolean
mail_sqlite_store_iter_nth_child (GtkTreeModel *tree_model,
								 GtkTreeIter  *iter,
								 GtkTreeIter  *parent,
								 gint		  n)
{
	MailSqliteStore		*self;
	MailSqliteStorePrivate *priv;

	g_return_val_if_fail (MAIL_IS_SQLITE_STORE (tree_model), FALSE);

	self = MAIL_SQLITE_STORE (tree_model);
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	if (parent)
		return FALSE;

	if (n > priv->count)
		return FALSE;

	d(printf("iter_nth_child: Moving from %d to %d...\t", priv->fpos, n));
	iter->stamp = self->stamp;
	iter->user_data = GINT_TO_POINTER(n);
	iter->user_data2 = NULL;
	iter->user_data3 = NULL;

	return TRUE;
}

static gboolean
mail_sqlite_store_iter_parent (GtkTreeModel *tree_model,
							  GtkTreeIter  *iter,
							  GtkTreeIter  *child)
{
	return FALSE;
}

GtkTreeModel*
mail_sqlite_store_new (void)
{
	return GTK_TREE_MODEL (g_object_new (MAIL_TYPE_SQLITE_STORE, NULL));
}

void
mail_sqlite_store_set_folder (MailSqliteStore  *self, CamelFolder *folder, CamelException *ex)
{
	MailSqliteStorePrivate *priv;
	char *query;
	int ret;

	g_return_if_fail (MAIL_IS_SQLITE_STORE (self));
	if (!folder)
		return;
	priv = MAIL_SQLITE_STORE_GET_PRIVATE (self);
	g_assert (priv);

	/* Just point to th handle */
	priv->dbh = folder->parent_store->cdb_r->db;
	priv->table = g_strdup (folder->full_name);
	self->n_columns = NUM_COLS; 
	priv->count = mail_sqlite_store_count_rows (priv->dbh, priv->table);
	priv->stmt = NULL;
	
	query = sqlite3_mprintf("select uid,mail_from,subject,dreceived,flags from %Q  order by dreceived desc  limit ?1 offset ?2", priv->table);
	ret = sqlite3_prepare_v2 (priv->dbh, query, -1, &priv->stmt, NULL);
	if (ret != 0) {
		printf("Error  %s\n", sqlite3_errmsg(priv->dbh));
		return;
	}
	sqlite3_bind_int (priv->stmt, 1, -1);
	sqlite3_bind_int (priv->stmt, 2, 0);
	sqlite3_free (query);
	
	ret = sqlite3_step (priv->stmt);
	if (ret != SQLITE_DONE)
		priv->pos = 0;
	
	g_ptr_array_set_size (priv->dcache, priv->count);
	for (ret = 0; ret < priv->count; ret++)
		priv->dcache->pdata[ret] = NULL;
}

static gint
mail_sqlite_store_count_rows_cb (gpointer   user_data,
                          gint       n_columns,
                          gchar    **values,
                          gchar	   **columns)
{
	gint *count = user_data;

	if (n_columns > 0)
		(*count) = atoi (values[0]);

	return SQLITE_OK;
}

/**
 * mail_sqlite_store_count_rows:
 * @sqlite: A sqlite3 handle.
 * @table: The table name to fetch from.
 *
 * Returns the number of rows found in the table or -1 if there was an error.
 **/
static gint
mail_sqlite_store_count_rows (sqlite3 *sqlite, gchar *table)
{
	gchar *query = NULL;
	gint   count = -1;

	g_return_val_if_fail (sqlite != NULL, -1);
	g_return_val_if_fail (table != NULL,  -1);

	query = sqlite3_mprintf("SELECT COUNT(*) FROM %Q where read = 0 or read = 1", table);
	sqlite3_exec (sqlite, query, mail_sqlite_store_count_rows_cb, &count, NULL);
	sqlite3_free (query);

	return count;
}

#ifdef NOT_USED
static gint
mail_sqlite_store_fetch_n_columns_cb (gpointer   user_data,
                               gint	      n_columns,
                               gchar    **values,
                               gchar    **columns)
{
	gint *n = user_data;
	(*n) = (*n) + 1;
	return SQLITE_OK;
}

/**
 * mail_sqlite_store_fetch_n_columns:
 * @sqlite: A sqlite3 handle.
 * @table: Name of table to select from.
 *
 * Retuns the number of columns found in table, including oid.
 **/
static gint
mail_sqlite_store_fetch_n_columns (sqlite3 *sqlite, gchar *table)
{
	gint   n = 0;
	gchar *query;

	query = g_strdup_printf ("PRAGMA table_info('%s')", table);
	sqlite3_exec (sqlite, query, mail_sqlite_store_fetch_n_columns_cb, &n, NULL);
	g_free (query);

	n += 1; /* oid */
	return n;
}
#endif
