/*
 * 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 <glib-object.h>
#include <gtk/gtk.h>

#include <glib/gi18n.h>

#include <string.h>

#include <camel/camel-folder.h>

#include <libedataserver/e-msgport.h>
#include <pthread.h>

#include "em-tree-store.h"

#define d(x)

#define EMTS(x) ((struct _EMTreeStore *)x)
#define GTK_IS_EMTS(x) (1)

#define _PRIVATE(x) (g_type_instance_get_private((GTypeInstance *)(x), em_tree_store_get_type()))

#define node_has_children(node) ((node->flags & EM_TREE_NODE_LEAF) == 0 && node->children.head != (EDListNode *)&node->children.tail)
static time_t emts_thread_time (EMTreeStore *emts, EMTreeNode *node, char **);
static gboolean emts_thread_unread (EMTreeStore *emts, EMTreeNode *node);
static void emts_prune_empty(EMTreeStore *emts, EMTreeNode *node);

struct _emts_folder {
	struct _emts_folder *next;
	struct _emts_folder *prev;

	struct _EMTreeStore *emts;

	CamelFolder *folder;
	guint32 changed_id;
	GHashTable *uid_table;
	 
	CamelFolderChangeInfo *changes;
};

struct _EMTreeStorePrivate {
	EDList folders;
	char *view;
	char *expr;

	int update_id;

	EDList changes;
	pthread_mutex_t lock;

	EDList aux;
	GHashTable *id_table;
	 guint show_children : 1;
};

static GObjectClass *emts_parent;

struct _emts_column_info emts_column_info[EMTS_COL_NUMBER] = {
	{ G_TYPE_POINTER, "message-info", "<<invalid>>" },
	{ G_TYPE_STRING, "from", N_("From")  },
	{ G_TYPE_STRING, "subject", N_("Subject") },
	{ G_TYPE_ULONG, "date", N_("Date") },
	{ G_TYPE_STRING, "to", N_("To") },
	{ G_TYPE_UINT, "flags", N_("Flags") },
	{ G_TYPE_STRING, "threadcount", N_("Thread Count")},
	{ G_TYPE_STRING, "threadpreview", N_("Thread Preview") },
	{ G_TYPE_BOOLEAN, "unread", N_("Unread") },
	{ G_TYPE_BOOLEAN, "attachment", N_("Attachment") }	
};

static EMTreeNode *
emts_node_alloc(struct _EMTreeStorePrivate *p)
{
	EMTreeNode *node = g_slice_alloc0(sizeof(EMTreeNode)); 

	e_dlist_init(&node->children);
	node->info = NULL;
	node->latest_uid = NULL;
	node->msg_ref = NULL;
	
	return node;
}

#ifdef NOT_USED
static EMTreeNode *
emts_leaf_alloc(struct _EMTreeStorePrivate *p)
{
	return emts_node_alloc(p);
}
#endif

static void
emts_node_free(struct _EMTreeStorePrivate *p, EMTreeNode *node)
{
	if (node->info)
		camel_message_info_free(node->info);

	if (node->flags & EM_TREE_NODE_LEAF)
		 g_slice_free1 (sizeof(EMTreeLeaf), node);
	else
		 g_slice_free1 (sizeof(EMTreeNode), node);
}

static int
emts_node_count (EMTreeNode *node)
{
	EMTreeNode *child;
	int  count = 0;
	
	if (node_has_children(node)) {
		child = (EMTreeNode *)node->children.head;
	
		while (child && child != (EMTreeNode *)&node->children.tail ) {
			count += emts_node_count(child);
			count++;
			child = child->next;
		}
	}
	
	return count;
}

/* Implementation */

static GtkTreeModelFlags
emts_get_flags (GtkTreeModel *tree_model)
{
	return GTK_TREE_MODEL_ITERS_PERSIST;
}

static gint
emts_get_n_columns(GtkTreeModel *tree_model)
{
	/*EMTreeStore *emts = (EMTreeStore *) tree_model;*/

	return EMTS_COL_NUMBER;
}

static GType
emts_get_column_type(GtkTreeModel *tree_model, gint index)
{
	/*EMTreeStore *emts = (EMTreeStore *)tree_model;*/

	g_return_val_if_fail(index < EMTS_COL_NUMBER && index >= 0, G_TYPE_INVALID);
	return emts_column_info[index].type;
}

static gboolean
emts_get_iter(GtkTreeModel *tree_model, GtkTreeIter  *iter, GtkTreePath  *path)
{
	EMTreeStore *emts = (EMTreeStore *)tree_model;
	GtkTreeIter parent;
	int *indices;
	int depth, i;

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

	g_return_val_if_fail(depth > 0, FALSE);

	parent.stamp = emts->stamp;
	parent.user_data = emts->root;

	if (! gtk_tree_model_iter_nth_child (tree_model, iter, &parent, indices[0]))
		return FALSE;

	for (i = 1; i < depth; i++) {
		parent = *iter;
		if (! gtk_tree_model_iter_nth_child (tree_model, iter, &parent, indices[i]))
			return FALSE;
	}

	return TRUE;
}

/* do this because prepend_index is really inefficient */
static void
emts_calc_path(EMTreeNode *node, GtkTreePath *path)
{
	if (node->parent) {
		int i = 0;
		EMTreeNode *scan;

		emts_calc_path(node->parent, path);

		scan = (EMTreeNode *)node->parent->children.head;
		while (scan->next && scan != node) {
			i++;
			scan = scan->next;
		}

		gtk_tree_path_append_index(path, i);
	}
}

static GtkTreePath *
emts_get_path(GtkTreeModel *tree_model, GtkTreeIter  *iter)
{
	GtkTreePath *path;
	
	g_return_val_if_fail (iter != NULL, NULL);
	g_return_val_if_fail (iter->user_data != NULL, NULL);
	g_return_val_if_fail (iter->stamp == EMTS (tree_model)->stamp, NULL);

	path = gtk_tree_path_new();
	emts_calc_path((EMTreeNode *)iter->user_data, path);

	return path;
}

static void
emts_get_value(GtkTreeModel *tree_model, GtkTreeIter  *iter, gint column, GValue *value)
{
	 EMTreeNode *node;
	 struct _emts_folder *f;
	 struct _EMTreeStorePrivate *p = _PRIVATE(tree_model);
	 gboolean sel = g_object_get_data ((GObject *)tree_model, "sel") != NULL; 
	 f = (struct _emts_folder *)p->folders.head;
	/*EMTreeStore *emts = (EMTreeStore *)tree_model;*/
	g_return_if_fail(iter != NULL);
	g_return_if_fail(column < EMTS_COL_NUMBER);

	g_value_init(value, emts_column_info[column].type);

	node = (EMTreeNode *)iter->user_data;
	if (node->info == NULL) {
		switch((emts_col_t)column) {
		case EMTS_COL_MESSAGEINFO:
			g_value_set_pointer(value, node->info);
			break;
		case EMTS_COL_SUBJECT:
		case EMTS_COL_FROM:
		case EMTS_COL_TO:
			g_value_set_string(value, "<unset>");
			break;			 
		case EMTS_COL_DATE:
			g_value_set_ulong(value, 0);
			break;			 
		case EMTS_COL_UNREAD:
		case EMTS_COL_ATTACHMENT:	
			 g_value_set_boolean (value, FALSE);
			break;			 
		case EMTS_COL_FLAGS:
		case EMTS_COL_THREADPREVIEW:	 
		case EMTS_COL_THREADCOUNT: {
			g_value_set_string(value, "");
			break;
		}
		case EMTS_COL_NUMBER:
			abort();
			break;
		}
	} else {
		switch((emts_col_t)column) {
		case EMTS_COL_MESSAGEINFO:
			/* FIXME: need to ref the info? */
			g_value_set_pointer(value, node->info);
			break;
		case EMTS_COL_SUBJECT: 
			g_value_set_string(value, camel_message_info_subject(node->info));
			break;
		case EMTS_COL_UNREAD: 
			 g_value_set_boolean(value, emts_thread_unread((EMTreeStore *)tree_model, node));
			break;
		case EMTS_COL_ATTACHMENT:
			 g_value_set_boolean(value, (camel_message_info_flags(node->info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
			break;
		case EMTS_COL_FROM:
		case EMTS_COL_TO:  {
			 char **str = NULL, *newstr = NULL;
			 gboolean unread = emts_thread_unread((EMTreeStore *)tree_model, node);
			 
			 if (column == EMTS_COL_FROM) {
				 if (camel_message_info_from(node->info))
					 str  = g_strsplit (camel_message_info_from(node->info), "<", 2);
			 } else {
				 if (camel_message_info_to(node->info))
				 	str  = g_strsplit (camel_message_info_to(node->info), "<", 2);
			 }
			 newstr = g_strdup_printf ("%s%s%s", unread ? "<b>" : "", str ? *str : "", unread ? "</b>" : "");
			 
				  
			 g_value_set_string(value, newstr);
			g_strfreev(str);
			g_free (newstr);
			break;
		}
		case EMTS_COL_THREADCOUNT: {
			 int cnt = emts_node_count(node);
			 char *newstr = NULL;
			 extern char *scolor_fg_sel;
			 
			 if (cnt)
				  newstr = g_strdup_printf ("<span  foreground='%s' size=\"small\"> %d more in thread </span>", scolor_fg_sel, cnt);
			 else
				  newstr = NULL;
			 
				  
			 g_value_set_string(value, newstr ? newstr : "");
			g_free (newstr);
			break;			 
		}
		case EMTS_COL_THREADPREVIEW:{
			 EMTreeNode *lnode = node->latest_uid ? g_hash_table_lookup(f->uid_table, node->latest_uid) : NULL;
			 char *preview = NULL;
			 char *str = NULL;
			 gboolean nline = TRUE;
			 
			 /* We don't bother to fetch the preview of latest message, if not idenfied. Its ok.*/
			 if (lnode && lnode->info)
				 preview = (char *)camel_message_info_preview (lnode->info);
			 if ((!preview || !*preview )&& node->info)
				 preview = (char *)camel_message_info_preview (node->info);
			 /* FIXME: This should be done at the backend/camel. Doing here is expensive, but just ok for 0.001 release*/
			 if (preview && *preview)
				  nline = strchr (preview, '\n') != NULL;
			 str  = g_markup_printf_escaped (_("<span foreground=\"%s\" >%s%s</span>"), sel ? "#d3d7cf" : "#808080", (preview && *preview) ? preview : _("Message not yet downloaded  or preview could not be constructed\n"), nline ? "" : "\n");
			 g_value_set_string(value, str);
			 g_free(str);
			 break;
		}
		case EMTS_COL_FLAGS: {
			 g_value_set_uint(value, (unsigned int)camel_message_info_flags(node->info));
			 break;
		}
		case EMTS_COL_DATE: {
//			g_value_set_ulong(value, (unsigned long)camel_message_info_date_sent(node->info));
			 char *uid= NULL;
			 g_value_set_ulong(value, (unsigned long)emts_thread_time((EMTreeStore *)tree_model, node, &uid));
			 if (!node->latest_uid)
				  node->latest_uid = uid;
			break;
		}
		case EMTS_COL_NUMBER:
			abort();
			break;
		}
	}
}

static gboolean
emts_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
	EMTreeNode *node;

	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (iter->user_data != NULL, FALSE);

	/* FIXME: check stamp? */

	/* TODO: how can we ignore placeholder nodes but keep them around anyway??? */

	node = iter->user_data;
	node = node->next;
	if (node->next) {
		if (!node)
			g_assert(0);
		iter->user_data = node;
		return TRUE;
	}

	return FALSE;
}

static gboolean
emts_iter_child (GtkTreeModel *tree_model, GtkTreeIter  *iter, GtkTreeIter  *parent)
{
	 EMTreeStore *emts = (EMTreeStore *)tree_model;
	 struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	 EMTreeNode *node;

	/* FIXME: check stamp? */

	if (!p->show_children)
		 return FALSE;
	if (parent)
		node = parent->user_data;
	else
		node = ((EMTreeStore *)tree_model)->root;

	if (node_has_children(node)) {
		iter->stamp = EMTS (tree_model)->stamp;
		iter->user_data = node->children.head;
		return TRUE;
	}

	return FALSE;
}

static gboolean
emts_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter  *iter)
{
	EMTreeNode *node;
	EMTreeStore *emts = (EMTreeStore *)tree_model;
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	/* FIXME: check stamp? */

	if (!p->show_children)
		 return FALSE;
	
	node = iter->user_data;

	return node_has_children(node);
}

static gint
emts_iter_n_child(GtkTreeModel *tree_model, GtkTreeIter  *iter)
{
	EMTreeNode *node;
	int i = 0;

	/* FIXME: check stamp? */

	if (iter == NULL)
		node = ((EMTreeStore *)tree_model)->root;
	else
		node = iter->user_data;

	if (node_has_children(node)) {
		node = (EMTreeNode *)node->children.head;
		while (node->next) {
			i++;
			node = node->next;
		}
	}

	return i;
}

static gboolean
emts_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
{
	EMTreeNode *node;

	if (parent)
		node = parent->user_data;
	else
		node = ((EMTreeStore *)tree_model)->root;

	if (node_has_children(node)) {
		node = (EMTreeNode *)node->children.head;
		while (node->next) {
			if (n == 0) {
				iter->user_data = node;
				return TRUE;
			}
			n--;
			node = node->next;
		}
	}

	return FALSE;
}

static gboolean
emts_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
{
	EMTreeNode *parent;

	parent = ((EMTreeNode *)child->user_data)->parent;

	if (parent != ((EMTreeStore *)tree_model)->root) {
		iter->user_data = parent;
		/*iter->stamp = EMTS (tree_model)->stamp;*/
		return TRUE;
	}

	return FALSE;
}

/* This only frees external references, the actual nodes are in the memchunks */
static void
emts_node_free_rec(struct _EMTreeStorePrivate *p, EMTreeNode *node)
{
	EMTreeNode *child, *nchild;

	if (node_has_children(node)) {
		child = (EMTreeNode *)node->children.head;
		nchild = child->next;
		while (nchild) {
			emts_node_free_rec(p, child);
			child = nchild;
			nchild = nchild->next;
		}
	}

	if (node->info)
		camel_message_info_free(node->info);
}

static void
emts_finalise(GObject *o)
{
	EMTreeStore *emts = (EMTreeStore *)o;
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	struct _emts_folder *f;

	if (p->update_id)
		g_source_remove(p->update_id);

	printf("Tree store finalise = %p\n", emts);

	while ( ( f = (struct _emts_folder *)e_dlist_remhead(&p->folders)) ) {
		camel_object_remove_event(f->folder, f->changed_id);
		camel_object_unref(f->folder);
		g_free(f);
	}

	pthread_mutex_destroy(&p->lock);

	emts_node_free_rec(p, emts->root);
	/* FIXME: Free real nodes */
	#warning "free real nodes"

	g_hash_table_destroy(p->id_table);

	((GObjectClass *)emts_parent)->finalize(o);
}

static void
emts_class_init(EMTreeStoreClass *klass)
{
	((GObjectClass *)klass)->finalize = emts_finalise;

	g_type_class_add_private(klass, sizeof(struct _EMTreeStorePrivate));
}

static guint
emts_id_hash(void *key)
{
	CamelSummaryMessageID *id = (CamelSummaryMessageID *)key;

	return id->id.part.lo;
}

static gint
emts_id_equal(void *a, void *b)
{
	return ((CamelSummaryMessageID *)a)->id.id == ((CamelSummaryMessageID *)b)->id.id;
}

#ifdef NOT_USED
static int
sort_cb (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
{
	char *aname, *bname;
	ulong t1, t2;
	int rv = -2;

	gtk_tree_model_get (model, a, EMTS_COL_DATE, &t1, -1);
	gtk_tree_model_get (model, b, EMTS_COL_DATE, &t2, -1);

	return t1-t2;

}
#endif

static void
emts_init(EMTreeStore *emts)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);

	e_dlist_init(&p->folders);

	p->id_table = g_hash_table_new((GHashFunc)emts_id_hash, (GCompareFunc)emts_id_equal);

	pthread_mutex_init(&p->lock, NULL);
	e_dlist_init(&p->changes);

	emts->root = emts_node_alloc(p);
}

static void
emts_tree_model_init(GtkTreeModelIface *iface)
{
	iface->get_flags = emts_get_flags;
	iface->get_n_columns = emts_get_n_columns;
	iface->get_column_type = emts_get_column_type;
	iface->get_iter = emts_get_iter;
	iface->get_path = emts_get_path;
	iface->get_value = emts_get_value;
	iface->iter_next = emts_iter_next;
	iface->iter_children = emts_iter_child;
	iface->iter_has_child = emts_iter_has_child;
	iface->iter_n_children = emts_iter_n_child;
	iface->iter_nth_child = emts_iter_nth_child;
	iface->iter_parent = emts_iter_parent;
}

GType
em_tree_store_get_type (void)
{
	static GType tree_store_type = 0;

	if (!tree_store_type) {
		static const GTypeInfo tree_store_info = {
			sizeof (EMTreeStoreClass),
			NULL, NULL,
			(GClassInitFunc) emts_class_init,
			NULL, NULL,
			sizeof (EMTreeStore), 0,
			(GInstanceInitFunc) emts_init,
		};

		static const GInterfaceInfo tree_model_info = {
			(GInterfaceInitFunc) emts_tree_model_init,
			NULL,
			NULL
		};
		emts_parent = g_type_class_ref(G_TYPE_OBJECT);
		tree_store_type = g_type_register_static(G_TYPE_OBJECT, "EMTreeStore", &tree_store_info, 0);

		g_type_add_interface_static(tree_store_type, GTK_TYPE_TREE_MODEL, &tree_model_info);
	}

	return tree_store_type;
}

#if d(!)0
static void
dump_info_rec(EMTreeNode *node, GString *pre)
{
	int len = pre->len;

	g_string_append(pre, "  ");
	printf("%p: %s%s\n", node, pre->str, node->info?camel_message_info_subject(node->info):"<unset>");
	if (node_has_children(node)) {
		node = node->children.head;
		while (node->next) {
			dump_info_rec(node, pre);
			node = node->next;
		}
	}
	g_string_truncate(pre, len);
}

static void
dump_info(EMTreeNode *root)
{
	GString *pre = g_string_new("");

	dump_info_rec(root, pre);
	g_string_free(pre, TRUE);
}
#endif

/* This is used along with prune_empty to build the initial model, before the model is set on any tree
   It should match the old thread algorithm exactly */
static void
emts_insert_info_base(EMTreeStore *emts, CamelMessageInfo *mi, struct _emts_folder *f)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	EMTreeNode *match;
	int j;
	const CamelSummaryMessageID *mid;
	const CamelSummaryReferences *references;

	mid = camel_message_info_message_id(mi);
	if (mid
	    && (match = g_hash_table_lookup(p->id_table, mid))
	    && match->info == NULL) {
		/* We already have this node, just fill it in */
		match->info = mi;
	} else {
		/* Allocating a new node, always 'parent' to root to start with */
		match = emts_node_alloc(p);
		match->info = mi;
		match->msg_ref = (gpointer) mid;
		match->latest_uid = mi->uid;
		if (mid
		    && g_hash_table_lookup(p->id_table, mid) == NULL)
			g_hash_table_insert(p->id_table, (void *)mid, match);
		match->parent = emts->root;
		e_dlist_addtail(&emts->root->children, (EDListNode *)match);
	}
	camel_message_info_ref(mi);

	g_hash_table_insert(f->uid_table, (void *)camel_message_info_uid(mi), match);

	references = camel_message_info_references(mi);
	if (references) {
		EMTreeNode *node, *parent;

		node = match;

		/* make sure the tree from root to the message exists */
		for (j=0;j<references->size;j++) {
			/* should never be empty, but just incase */
			if (references->references[j].id.id == 0)
				continue;

			/* If no existing parent, create one, always put it under root to start with */
			parent = g_hash_table_lookup(p->id_table, &references->references[j]);
			if (parent == NULL) {
				parent = emts_node_alloc(p);
				g_hash_table_insert(p->id_table, (void *)&references->references[j], parent);
				parent->parent = emts->root;
				parent->msg_ref = (gpointer) &references->references[j], parent;
				e_dlist_addtail(&emts->root->children, (EDListNode *)parent);
			}

			/* parent changed, then re-parent to the correct one */
			if (parent != node) {
				if (node->parent != parent) {
					node->parent = parent;
					e_dlist_remove((EDListNode *)node);
					e_dlist_addtail(&parent->children, (EDListNode *)node);
				}
				node = parent;
			}
		}
	}
}

/* This is used to incrementally update the model as changes come in.
   It takes some short-cuts since we no longer have the auxillairy place-holders
   to fill out the full root tree.  It will work if messages arrive in the right order however */
static  gboolean
emts_insert_info_incr(EMTreeStore *emts, CamelMessageInfo *mi, struct _emts_folder *f)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	EMTreeNode *match;
	int j;
	GtkTreeIter iter;
	GtkTreePath *path;
	const CamelSummaryMessageID *mid;
	const CamelSummaryReferences *references;
	gboolean ret = FALSE;

	printf("inserting new: '%s'\n", camel_message_info_subject(mi));

	/* Allocating a new node, always 'parent' to root to start with */
	match = emts_node_alloc(p);
	match->info = mi;
	match->latest_uid = mi->uid;
	camel_message_info_ref(mi);
	mid = camel_message_info_message_id(mi);
	match->msg_ref = (CamelSummaryMessageID *)mid;
	if (mid
	    && g_hash_table_lookup(p->id_table, mid) == NULL)
		g_hash_table_insert(p->id_table, (void *)mid, match);
	match->parent = emts->root;


	g_hash_table_insert(f->uid_table, (void *)camel_message_info_uid(mi), match);

	references = camel_message_info_references(mi);
	if (references) {
		EMTreeNode *node, *parent;

		node = match;

		/* Search for a parent, if we have one already - otherwise it stays at the root */
		for (j=0;j<references->size;j++) {
			if (references->references[j].id.id != 0
			    && (parent = g_hash_table_lookup(p->id_table, &references->references[j]))
			    && parent != node) {
				EMTreeNode *tmpnode;

				if (NULL == parent->info)
				{
					printf(" found an empty parent, pruning\n");
					emts_prune_empty (emts, parent);
					continue;
				}

				printf(" found parent, reparenting\n");
				node->parent = parent;
				//e_dlist_remove((EDListNode *)node);
				e_dlist_addtail(&parent->children, (EDListNode *)node);
				tmpnode = parent;
				while (tmpnode != NULL && tmpnode != emts->root)
				{
					tmpnode->latest_uid = mi->uid;
					tmpnode = tmpnode->parent;
				}
				ret = TRUE;
				break;
			}
		}
		if (j == references->size) {
			/* It didnt add to parent. Better add to root.*/	
			//e_dlist_remove((EDListNode *)node);
			e_dlist_addtail(&emts->root->children, (EDListNode *)node);
		}
	} else {
		//e_dlist_remove((EDListNode *)match);
		em_tree_insert_sorted_with_data (&emts->root->children, (EDListNode *)match, (GFunc)emts->func, emts);
	}

	iter.user_data = match;
	iter.stamp = emts->stamp;
	path = emts_get_path((GtkTreeModel *)emts, &iter);
	if (match->parent == emts->root)
		 gtk_tree_model_row_inserted((GtkTreeModel *)emts, path, &iter);
	printf("Inserted at: %s\n", gtk_tree_path_to_string(path));
	gtk_tree_path_free(path);

	return ret;
}



/* This removes all empty nodes */
static void
emts_prune_empty(EMTreeStore *emts, EMTreeNode *node)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	EMTreeNode *child, *next, *save;

	/* FIXME: MUST MUST MUST - Must remove the ID hash table entries for these nodes too */
	next = node->next;
	while (next) {
		if (node->info == NULL) {
			if (!node_has_children(node)) {
				e_dlist_remove((EDListNode *)node);
				g_hash_table_remove (p->id_table, node->msg_ref);
				emts_node_free(p, node);
				node = next;
				next = node->next;
			} else {

				while ((child = (EMTreeNode *)e_dlist_remhead(&node->children))) {
					 //Pull the children to get to next has children cleanup
/*					 child->next = next->next;
					 next->next->prev = child;
					 next->next = child;
					 child->prev = next;
					 */

					child->next = next;
					child->prev = next->prev;
					next->prev->next = child;
					next->prev = child;
					child->parent = node->parent; 
				}
				save = node;
				node = node->next;
				next = node->next;
				e_dlist_remove((EDListNode *)save);
				g_hash_table_remove (p->id_table, save->msg_ref);
				emts_node_free(p, save);
			}
		} else {
			if (node_has_children(node))
				emts_prune_empty(emts, (EMTreeNode *)node->children.head);
			node = next;
			next = node->next;
		}
	}
}

static void
emts_remove_info(EMTreeStore *emts, const char *uid, struct _emts_folder *f)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	EMTreeNode *c, *d, *node;
	GtkTreePath *path = NULL, *tpath;
	GtkTreeIter iter;
	
	node = g_hash_table_lookup(f->uid_table, uid);
	if (node == NULL)
		return;

	g_hash_table_remove(f->uid_table, uid);

	iter.stamp = emts->stamp;
	iter.user_data = node;
	path = emts_get_path((GtkTreeModel *)emts, &iter);
	e_dlist_remove((EDListNode *)node);
	
	if ( (node->parent == emts->root) || p->show_children)
		 gtk_tree_model_row_deleted((GtkTreeModel*)emts, path);

	if (node_has_children(node)) {
		c = (EMTreeNode *)e_dlist_remhead(&node->children);
		c->prev = node->prev;
		node->prev->next = c;
		c->next = node->next;
		node->next->prev = c;
		c->parent = node->parent;

		iter.stamp = emts->stamp;
		iter.user_data = c;
		tpath = emts_get_path((GtkTreeModel *)emts, &iter);
		if ( (c->parent == emts->root) || p->show_children)
			 gtk_tree_model_row_inserted((GtkTreeModel*)emts, tpath, &iter);
		gtk_tree_path_free(tpath);

		while ( (d = (EMTreeNode *)e_dlist_remhead(&node->children)) ) {
			d->parent = c;
			e_dlist_addtail(&c->children, (EDListNode *)d);

			iter.stamp = emts->stamp;
			iter.user_data = d;
			tpath = emts_get_path((GtkTreeModel *)emts, &iter);
			if (p->show_children)
				 gtk_tree_model_row_inserted((GtkTreeModel*)emts, tpath, &iter);
			gtk_tree_path_free(tpath);
		}

		if (!p->show_children && c->parent == emts->root ) {
			 iter.stamp = emts->stamp;
			 iter.user_data = c;
			 tpath = emts_get_path((GtkTreeModel *)emts, &iter);
			
			 gtk_tree_model_row_changed((GtkTreeModel*)emts, tpath, &iter);
			 gtk_tree_path_free(tpath);
		}
		
	}

	/*
	if (!node_has_children(node->parent)) {
		gtk_tree_path_up(path);
		iter.user_data = node->parent;
		gtk_tree_model_row_has_child_toggled((GtkTreeModel*)emts, path, &iter);
	}
	*/
	gtk_tree_path_free(path);

	emts_node_free(p, node);
}


static void
emts_sort_view (EMTreeStore *emts, struct _emts_folder *f)
{
	 GPtrArray *nodes;
	 int i;
	 EMTreeNode *node = (EMTreeNode *)emts->root->children.head;
	 
	 nodes = g_ptr_array_new ();
	 i=0;
	 while (node && node != (EMTreeNode *)&emts->root->children.tail) {
		  g_ptr_array_add (nodes, node);
		  node = node->next;
	 }

	 g_ptr_array_sort_with_data (nodes, (GCompareDataFunc)emts->func , emts);
	 emts->root->children.head = NULL;
	 emts->root->children.tail = NULL;
	 emts->root->children.tailpred = NULL;
	 e_dlist_init (&emts->root->children);
	 for (i=0; i < nodes->len; i++) {
		  e_dlist_addtail(&emts->root->children, (EDListNode *)nodes->pdata[i]);
	 }
}

void
em_tree_store_resort (EMTreeStore *emts)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	struct _emts_folder *f;

	f = (struct _emts_folder *)p->folders.head;
	emts_sort_view (emts,f);
}

void
em_tree_insert_sorted_with_data (EDList *list,
				 gpointer data,
				 GFunc func,
				 gpointer user_data)
{
	EMTreeNode *current_node = (EMTreeNode *) data;
	gint cmp = 1;
	EDListNode *head = list->head, *node;
	
	g_return_if_fail ((gpointer)func != NULL);

	if (list->head == (EDListNode *)&list->tail) {
		 e_dlist_addtail(list, (EDListNode *)current_node);
		 return;
	}

	node = head;

	//cmp = ((GCompareDataFunc) func) (&current_node, &node, user_data);

	while (node && (node != (EDListNode *)&list->tail) && (cmp > 0)) {
		cmp = ((GCompareDataFunc) func) (&current_node, &node, user_data);
		node = node->next;
	}
	
	if (node == (EDListNode *)&list->tail) {
		 e_dlist_addtail(list, (EDListNode *)current_node);
		 return;
	}

	if (node == list->head) {
		 e_dlist_addhead (list, (EDListNode *)current_node);
		 return;
	}
	
	if (node->prev) {
		node->prev->next = (EDListNode *)current_node;
		current_node->prev = (EMTreeNode *)node->prev;
	}

	current_node->next = (EMTreeNode *)node;
	node->prev = (EDListNode *)current_node;
}

static gboolean
emts_folder_changed_idle(void *data)
{
	EMTreeStore *emts = data;
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	struct _emts_folder *f, *n;
	int i;
	gboolean resort = FALSE;

	pthread_mutex_lock(&p->lock);
	f = (struct _emts_folder *)p->folders.head;
	n = f->next;
	while (n) {

		d(printf("Changed added=%d removed=%d changed=%d\n", f->changes->uid_added->len, f->changes->uid_removed->len, f->changes->uid_changed->len));
		for (i=0;i<f->changes->uid_added->len;i++) {
			CamelMessageInfo *mi;
			const char *uid = f->changes->uid_added->pdata[i];

			mi = camel_folder_get_message_info (f->folder, uid);
			if (g_hash_table_lookup(f->uid_table, camel_message_info_uid(mi)) == NULL)
				resort = emts_insert_info_incr(emts, mi, f) || resort;
		}

		for (i=0;i<f->changes->uid_removed->len;i++) {
			CamelMessageInfo *mi;
			const char *uid= f->changes->uid_removed->pdata[i];

			mi = camel_folder_get_message_info (f->folder, uid);			
			emts_remove_info(emts, uid, f);
		}

		for (i=0;i<f->changes->uid_changed->len;i++) {
			CamelMessageInfo *mi;
			const char *uid = f->changes->uid_changed->pdata[i];
			EMTreeNode *node;

			mi = camel_folder_get_message_info (f->folder, uid);			
			if (!mi)
				continue;
			if ((node = g_hash_table_lookup(f->uid_table, camel_message_info_uid(mi)))) {
				GtkTreeIter iter;
				GtkTreePath *path;

				iter.stamp = emts->stamp;
				iter.user_data = node;
				
				if (camel_message_info_flags(mi) & CAMEL_MESSAGE_DELETED)
					emts_remove_info(emts, uid, f);
				else {
					path = emts_get_path((GtkTreeModel*)emts, &iter);
					gtk_tree_model_row_changed((GtkTreeModel*)emts, path, &iter);
					gtk_tree_path_free(path);
				}
			} else {
				resort = emts_insert_info_incr(emts, mi, f) || resort;
			}

			camel_message_info_free (mi);
		}

		camel_folder_change_info_clear(f->changes);
		f = n;
		n = n->next;
	}

	p->update_id = 0;
	pthread_mutex_unlock(&p->lock);
	
	if (resort)
		em_tree_store_resort (emts);
	return FALSE;
}

static void
emts_folder_changed(CamelObject *o, void *event, void *data)
{
	CamelFolderChangeInfo *changes = event;
	struct _emts_folder *f = data;
	struct _EMTreeStorePrivate *p = _PRIVATE(f->emts);

	printf("Folder '%s' changed\n", ((CamelFolder *)o)->full_name);

	if (changes) {
		pthread_mutex_lock(&p->lock);
		if (f->changes == NULL)
			f->changes = camel_folder_change_info_new();
		camel_folder_change_info_cat(f->changes, changes);
		if (p->update_id == 0)
			p->update_id = g_idle_add(emts_folder_changed_idle, f->emts);
		pthread_mutex_unlock(&p->lock);
	}
}

void
em_tree_store_add_folder (EMTreeStore *emts, CamelFolder *folder, const char *expr, gboolean prune)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);	 
	 struct _emts_folder *f = g_malloc(sizeof(*f)); 
	 GPtrArray *uids;
	 CamelException ex;
	 int j;
	 
	 camel_exception_init (&ex);
	 
	 e_dlist_addtail(&p->folders, (EDListNode *)f);
	 f->uid_table = g_hash_table_new(g_str_hash, g_str_equal);
	 f->folder = folder;
	 camel_object_ref(f->folder);
	 f->emts = emts;
	 f->changed_id = camel_object_hook_event(f->folder, "folder_changed", emts_folder_changed, f);
	 f->changes = NULL;
	 
	 uids = camel_folder_search_by_expression (f->folder, expr, &ex);
	 if (camel_exception_is_set (&ex)) {
		  g_warning ("error: %s\n", camel_exception_get_description (&ex));
		  camel_exception_clear (&ex);
	 }

	 if (uids->len > camel_folder_summary_cache_size (f->folder->summary)){
		  camel_folder_summary_set_need_preview (f->folder->summary, TRUE);
		  camel_folder_summary_reload_from_db (f->folder->summary, &ex);
	 }
	 if (camel_exception_is_set (&ex)) {
		  g_warning ("error: %s\n", camel_exception_get_description (&ex));
				camel_exception_clear (&ex);
	 }
	 for (j=0; j<uids->len; j++) {
		  CamelMessageInfo *mi = camel_folder_get_message_info (f->folder, uids->pdata[j]);
		  emts_insert_info_base(emts, mi, f);
	 }

	 if (prune) {
		  emts_prune_empty(emts, (EMTreeNode *)emts->root->children.head);
		  emts_sort_view (emts, f);
	 }
}

void
em_tree_store_search_folder (EMTreeStore *emts, CamelFolder *folder, const char *expr, gboolean prune)
{
	 struct _EMTreeStorePrivate *p = _PRIVATE(emts);	 
	 GPtrArray *uids;
	 CamelException ex;
	 int j;
	 struct _emts_folder *f = (struct _emts_folder *)p->folders.head;
	 
	 camel_exception_init (&ex);
	 
  	 emts_node_free_rec(p, emts->root);
	 #warning "free real nodes"
	 g_hash_table_destroy(p->id_table);

	 p->id_table = g_hash_table_new((GHashFunc)emts_id_hash, (GCompareFunc)emts_id_equal);
	 emts->root = emts_node_alloc(p);

	 g_hash_table_destroy (f->uid_table);
	 f->uid_table = g_hash_table_new(g_str_hash, g_str_equal);
	 
	 uids = camel_folder_search_by_expression (f->folder, expr, &ex);
	 if (camel_exception_is_set (&ex)) {
		  g_warning ("error: %s\n", camel_exception_get_description (&ex));
		  camel_exception_clear (&ex);
	 }
	 printf("Search has %d mails\n", uids ? uids->len : 0);
 
	 if (uids->len > camel_folder_summary_cache_size (f->folder->summary)){
		  camel_folder_summary_set_need_preview (f->folder->summary, TRUE);
		  camel_folder_summary_reload_from_db (f->folder->summary, &ex);
	 }
	 if (camel_exception_is_set (&ex)) {
		  g_warning ("error: %s\n", camel_exception_get_description (&ex));
				camel_exception_clear (&ex);
	 }
	 for (j=0; j<uids->len; j++) {
		  CamelMessageInfo *mi = camel_folder_get_message_info (f->folder, uids->pdata[j]);
		  printf("Inserting %s\n", camel_message_info_subject(mi));
		  emts_insert_info_base(emts, mi, f);
	 }

	 if (prune) {
		  emts_prune_empty(emts, (EMTreeNode *)emts->root->children.head);
		  emts_sort_view (emts, f);
	 }
}

void
em_tree_store_set_folders(EMTreeStore *emts, GPtrArray *folders, const char *expr)
{
	int i;
	
	printf("tree store new = %p\n", emts);

	for (i=0;i<folders->len;i++) {
		 em_tree_store_add_folder (emts, folders->pdata[i], expr, FALSE);
	}

	emts_prune_empty(emts, (EMTreeNode *)emts->root->children.head);

	return;
}

EMTreeStore *
em_tree_store_new(gboolean show_children)
{
	EMTreeStore *emts = (EMTreeStore *)g_object_new(em_tree_store_get_type(), NULL);
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);

	p->show_children = show_children;
	
	return emts;
}

int em_tree_store_get_iter(EMTreeStore *emts, GtkTreeIter *iter, const char *uid)
{
	struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	struct _emts_folder *f;

	f = (struct _emts_folder *)p->folders.head;
 
       iter->user_data = g_hash_table_lookup(f->uid_table, uid);
       iter->stamp = emts->stamp;
 
        return iter->user_data != NULL;
}

static time_t
emts_thread_time (EMTreeStore *emts, EMTreeNode *node, char **ret_uid)
{

	 EMTreeNode *n;
	 time_t dsent = 0;

	 if (node->info) {
		  dsent  = camel_message_info_date_sent (node->info);
		  *ret_uid = (char *)node->info->uid;
	 }
	 n = (EMTreeNode *)node->children.head;
	 while (n && n != (EMTreeNode *)&node->children.tail) {
		  if (!node_has_children(n)) {
			   
			   if (camel_message_info_date_sent(n->info) > dsent) {
					dsent = camel_message_info_date_sent(n->info);
					*ret_uid = (char *)n->info->uid;
			   }
			   n = n->next;
		  } else {
			   char *child_uid = NULL;
			   time_t child_time = emts_thread_time (emts, (EMTreeNode *)n->children.head, &child_uid);
			   
			   if (child_time > dsent) {
					dsent = child_time;
					*ret_uid = child_uid;
			   }
			   if (camel_message_info_date_sent(n->info) > dsent) {
					dsent = camel_message_info_date_sent(n->info);
					*ret_uid = (char *)n->info->uid;
			   }
			   n = n->next;
		  }
	 }

	 return dsent;
}

static gboolean
emts_thread_unread (EMTreeStore *emts, EMTreeNode *node)
{

	 EMTreeNode *n;
	 gboolean unread = FALSE;

	 if (node->info) {
		  unread = (camel_message_info_flags (node->info) & CAMEL_MESSAGE_SEEN) ? FALSE: TRUE;;
	 }
	 
	 n = (EMTreeNode *)node->children.head;
	 while (n && n != (EMTreeNode *)&node->children.tail && !unread) {
		  unread = (camel_message_info_flags (n->info) & CAMEL_MESSAGE_SEEN) ? FALSE : TRUE;
		  if (unread)
			   break;
		  if (node_has_children(n)) {			   
			   unread = emts_thread_unread (emts, (EMTreeNode *)n->children.head);
			   if (unread)
					break;
		  }
		  n = n->next;
	 }

	 return unread;
}

time_t
em_tree_store_thread_time (EMTreeStore *emts, const char *uid)
{
	 struct _emts_folder *f;
	 struct _EMTreeStorePrivate *p = _PRIVATE(emts);
	 EMTreeNode *node;
	 time_t ret;
	 char *ret_uid = NULL;
	 
	 f = (struct _emts_folder *)p->folders.head;
	 node  = g_hash_table_lookup(f->uid_table, uid);
	 ret = emts_thread_time (emts, node, &ret_uid);
	 node->latest_uid = ret_uid;
	 return ret;
}

int
em_tree_store_count_children (EMTreeStore *emts, GtkTreeIter *iter)
{
	 EMTreeNode *node = (EMTreeNode *) iter->user_data;

	 /* We count only the children */
	 return emts_node_count (node);
}

static int
sort_dsent (EMTreeNode **n1, EMTreeNode **n2, EMTreeStore *emts)
{
	return camel_message_info_date_sent ((*n1)->info) - camel_message_info_date_sent ((*n2)->info);
}

static void
emts_child_tree (EMTreeStore *emts, EMTreeNode *node, GPtrArray *array)
{
	 EMTreeNode *n;

	 if (node->info)
		  g_ptr_array_add (array, node);
			   
	 n = (EMTreeNode *)node->children.head;
	 while (n && n != (EMTreeNode *)&node->children.tail) {
		  if (node_has_children(n))
			   emts_child_tree (emts, (EMTreeNode *)n->children.head, array);
		  if (n->info)
			   g_ptr_array_add (array, n);
		  n = n->next;
	 }

	 return ;
}

GPtrArray *
em_tree_store_get_child_sorted_tree (EMTreeStore  *emts, GtkTreeIter *iter)
{
	 EMTreeNode *node = (EMTreeNode *) iter->user_data;
	 GPtrArray *nodes = g_ptr_array_new ();
	 int i;
	 
	 emts_child_tree (emts, node, nodes);
	 g_ptr_array_sort_with_data (nodes, (GCompareDataFunc)sort_dsent, emts);
	 for (i=0; i<nodes->len; i++) {
		  nodes->pdata[i] = (gpointer)camel_message_info_uid (((EMTreeNode *)nodes->pdata[i])->info);
	 }

	 return nodes;
}

GPtrArray *
em_tree_store_get_child_infos (EMTreeStore  *emts, GtkTreeIter *iter)
{
	 EMTreeNode *node = (EMTreeNode *) iter->user_data;
	 GPtrArray *nodes = g_ptr_array_new ();
	 int i;
	 
	 emts_child_tree (emts, node, nodes);
	 for (i=0; i<nodes->len; i++) {
		  nodes->pdata[i] =  ((EMTreeNode *)nodes->pdata[i])->info;
	 }
	 return nodes;
}

const char *
em_tree_store_get_uid_from_iter (EMTreeStore  *emts, GtkTreeIter *iter)
{
	 EMTreeNode *node = (EMTreeNode *) iter->user_data;

	 return node->info->uid;
}

const CamelMessageInfo *
em_tree_store_get_info_from_iter (EMTreeStore  *emts, GtkTreeIter *iter)
{
	 EMTreeNode *node = (EMTreeNode *) iter->user_data;

	 return node->info;
}

CamelMessageInfo *
em_tree_store_get_info_from_path_string (EMTreeStore  *emts, const char *path)
{
	 GtkTreeIter iter;
	 EMTreeNode *node;
	 gtk_tree_model_get_iter_from_string ((GtkTreeModel *)emts, &iter, path);
	 node = iter.user_data;

	 return node->info;
}
