/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * e-tree-table-adapter.c
 * Copyright 2000, 2001, Ximian, Inc.
 *
 * Authors:
 *   Chris Lahey <clahey@ximian.com>
 *   Chris Toshok <toshok@ximian.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License, version 2, as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <libxml/tree.h>
#include <libxml/parser.h>
#include "gal/util/e-util.h"
#include "gal/util/e-xml-utils.h"
#include "e-tree-table-adapter.h"

#define d(x)

#define INCREMENT_AMOUNT 100

static ETableModelClass *parent_class;


struct ETreeTableAdapterPriv {
	ETreeModel  *source;
	int    	     n_map;
	int    	     n_vals_allocated;
	ETreePath   *map_table;
	GHashTable  *attributes;

	guint        root_visible : 1;

	int          last_access;

	int          tree_model_pre_change_id;
	int          tree_model_no_change_id;
	int          tree_model_node_changed_id;
	int          tree_model_node_data_changed_id;
	int          tree_model_node_col_changed_id;
	int          tree_model_node_inserted_id;
	int          tree_model_node_removed_id;
	int          tree_model_node_request_collapse_id;
};

typedef struct ETreeTableAdapterNode {
	guint expanded : 1;
	guint expandable : 1;
	guint expandable_set : 1;

	/* parent/child/sibling pointers */
	guint32                num_visible_children;
} ETreeTableAdapterNode;

static ETreeTableAdapterNode *
find_node(ETreeTableAdapter *adapter, ETreePath path)
{
	ETreeTableAdapterNode *node;

	if (path == NULL)
		return NULL;

	if (e_tree_model_has_save_id(adapter->priv->source)) {
		char *save_id;
		save_id = e_tree_model_get_save_id(adapter->priv->source, path);
		node = g_hash_table_lookup(adapter->priv->attributes, save_id);
		g_free(save_id);
	} else {
		node = g_hash_table_lookup(adapter->priv->attributes, path);
	}
	if (node && !node->expandable_set) {
		node->expandable = e_tree_model_node_is_expandable(adapter->priv->source, path);
		node->expandable_set = 1;
	}

	return node;
}

static ETreeTableAdapterNode *
find_or_create_node(ETreeTableAdapter *etta, ETreePath path)
{
	ETreeTableAdapterNode *node;

	node = find_node(etta, path);

	if (!node) {
		node = g_new(ETreeTableAdapterNode, 1);
		if (e_tree_model_node_is_root(etta->priv->source, path))
			node->expanded = TRUE;
		else
			node->expanded = e_tree_model_get_expanded_default(etta->priv->source);
		node->expandable = e_tree_model_node_is_expandable(etta->priv->source, path);
		node->expandable_set = 1;
		node->num_visible_children = 0;

		if (e_tree_model_has_save_id(etta->priv->source)) {
			char *save_id;
			save_id = e_tree_model_get_save_id(etta->priv->source, path);
			g_hash_table_insert(etta->priv->attributes, save_id, node);
		} else {
			g_hash_table_insert(etta->priv->attributes, path, node);
		}
	}

	return node;
}

static gboolean
e_tree_table_adapter_node_would_be_expanded (ETreeTableAdapter *etta, ETreePath path)
{
	ETreeTableAdapterNode *node;

	if (e_tree_model_node_is_root(etta->priv->source, path) &&
	    !etta->priv->root_visible)
		return TRUE;

	node = find_node (etta, path);
	if (node)
		return node->expanded;
	else {
		if (e_tree_model_node_is_root(etta->priv->source, path))
			return TRUE;
		else
			return e_tree_model_get_expanded_default(etta->priv->source);
	}
}

static void
add_expanded_node(ETreeTableAdapter *etta, char *save_id, gboolean expanded)
{
	ETreeTableAdapterNode *node;

	node = g_hash_table_lookup(etta->priv->attributes, save_id);

	if (node) {
		node->expandable_set = 0;
		node->expanded = expanded;
		return;
	}

	node = g_new(ETreeTableAdapterNode, 1);

	node->expanded = expanded;
	node->expandable = 0;
	node->expandable_set = 0;
	node->num_visible_children = 0;

	g_hash_table_insert(etta->priv->attributes, save_id, node);
}

static void
etta_expand_to(ETreeTableAdapter *etta, int size)
{
	if (size > etta->priv->n_vals_allocated) {
		etta->priv->n_vals_allocated = MAX(etta->priv->n_vals_allocated + INCREMENT_AMOUNT, size);
		etta->priv->map_table = g_renew (ETreePath, etta->priv->map_table, etta->priv->n_vals_allocated);
	}

}

static void
etta_update_parent_child_counts(ETreeTableAdapter *etta, ETreePath path, int change)
{
	for (path = e_tree_model_node_get_parent(etta->priv->source, path);
	     path;
	     path = e_tree_model_node_get_parent(etta->priv->source, path)) {
		ETreeTableAdapterNode *node = find_or_create_node(etta, path);
		node->num_visible_children += change;
	}
	etta->priv->n_map += change;
}

static int
find_next_node_maybe_deleted(ETreeTableAdapter *adapter, int row)
{
	ETreePath path = adapter->priv->map_table[row];
	if (path) {
		ETreeTableAdapterNode *current = find_node (adapter, path);

		row += (current ? current->num_visible_children : 0) + 1;
		if (row >= adapter->priv->n_map)
			return -1;
		return row;
	} else
		return -1;
}

static int
find_first_child_node_maybe_deleted(ETreeTableAdapter *adapter, int row)
{
	if (row != -1) {
		ETreePath path = adapter->priv->map_table[row];
		if (e_tree_table_adapter_node_would_be_expanded (adapter, path)) {
			row ++;
			if (row >= adapter->priv->n_map)
				return -1;
			return row;
		} else
			return -1;
	} else
		return 0;
}

static int
find_next_node(ETreeTableAdapter *adapter, int row)
{
	ETreePath path = adapter->priv->map_table[row];
	if (path) {
		ETreePath next_sibling = e_tree_model_node_get_next(adapter->priv->source, path);
		ETreeTableAdapterNode *current = find_node (adapter, path);
		if (next_sibling) {
			row += (current ? current->num_visible_children : 0) + 1;
			if (row > adapter->priv->n_map)
				return -1;
			return row;
		} else
			return -1;
	} else
		return -1;
}

static int
find_first_child_node(ETreeTableAdapter *adapter, int row)
{
	if (row != -1) {
		ETreePath path = adapter->priv->map_table[row];
		ETreePath first_child = e_tree_model_node_get_first_child(adapter->priv->source, path);
		if (first_child && e_tree_table_adapter_node_is_expanded (adapter, path)) {
			row ++;
			if (row > adapter->priv->n_map)
				return -1;
			return row;
		} else
			return -1;
	} else
		return 0;
}

static int
find_child_row_num_maybe_deleted(ETreeTableAdapter *etta, int row, ETreePath path)
{
	row = find_first_child_node_maybe_deleted(etta, row);

	while (row != -1 && path != etta->priv->map_table[row]) {
		row = find_next_node_maybe_deleted(etta, row);
	}

	return row;
}

static int
find_row_num(ETreeTableAdapter *etta, ETreePath path)
{
	int depth;
	ETreePath *sequence;
	int i;
	int row;

	if (etta->priv->map_table == NULL)
		return -1;
	if (etta->priv->n_map == 0)
		return -1;

	if (path == NULL)
		return -1;

	if (etta->priv->last_access != -1) {
		int end = MIN(etta->priv->n_map, etta->priv->last_access + 10);
		int start = MAX(0, etta->priv->last_access - 10);
		int initial = MAX (MIN (etta->priv->last_access, end), start);
		for (i = initial; i < end; i++) {
			if(etta->priv->map_table[i] == path) {
				d(g_print("Found last access %d at row %d. (find_row_num)\n", etta->priv->last_access, i));
				return i;
			}
		}
		for (i = initial - 1; i >= start; i--) {
			if(etta->priv->map_table[i] == path) {
				d(g_print("Found last access %d at row %d. (find_row_num)\n", etta->priv->last_access, i));
				return i;
			}
		}
	}


	depth = e_tree_model_node_depth(etta->priv->source, path);

	sequence = g_new(ETreePath, depth + 1);

	sequence[0] = path;

	for (i = 0; i < depth; i++) {
		sequence[i + 1] = e_tree_model_node_get_parent(etta->priv->source, sequence[i]);

		if (!e_tree_table_adapter_node_is_expanded (etta, sequence[i + 1])) {
			g_free(sequence);
			return -1;
		}
	}

	row = 0;

	for (i = depth; i >= 0; i --) {
		while (row != -1 && row < etta->priv->n_map && sequence[i] != etta->priv->map_table[row]) {
			row = find_next_node(etta, row);
		}
		if (row >= etta->priv->n_map)
			break;
		if (row == -1)
			break;
		if (i == 0)
			break;
		row = find_first_child_node(etta, row);
	}
	g_free (sequence);

	if (row >= etta->priv->n_map)
		row = -1;

	d(g_print("Didn't find last access %d. Setting to %d. (find_row_num)\n", etta->priv->last_access, row));
	etta->priv->last_access = row;
	return row;
}

static int
array_size_from_path(ETreeTableAdapter *etta, ETreePath path)
{
	int size = 1;

	if (e_tree_table_adapter_node_is_expanded (etta, path)) {
		ETreePath children;

		for (children = e_tree_model_node_get_first_child(etta->priv->source, path); 
		     children; 
		     children = e_tree_model_node_get_next(etta->priv->source, children)) {
			size += array_size_from_path(etta, children);
		}
	}

	return size;
}

static int
fill_array_from_path(ETreeTableAdapter *etta, ETreePath *array, ETreePath path)
{
	ETreeTableAdapterNode *node = NULL;
	int index = 0;

	array[index] = path;

	index ++;

	node = find_node(etta, path);

	if (e_tree_table_adapter_node_is_expanded (etta, path)) {
		ETreePath children;

		if (!node)
			node = find_or_create_node(etta, path);

		for (children = e_tree_model_node_get_first_child(etta->priv->source, path); 
		     children; 
		     children = e_tree_model_node_get_next(etta->priv->source, children)) {
			index += fill_array_from_path(etta, array + index, children);
		}
	}

	if (node)
		node->num_visible_children = index - 1;

	return index;
}

static void
free_string (gpointer key, gpointer value, gpointer data)
{
	g_free(key);
}

static void
etta_dispose (GObject *object)
{
	ETreeTableAdapter *etta = E_TREE_TABLE_ADAPTER (object);

	if (etta->priv->source) {
		if (e_tree_model_has_save_id(etta->priv->source))
			g_hash_table_foreach(etta->priv->attributes, free_string, NULL);

		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_pre_change_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_no_change_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_changed_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_data_changed_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_col_changed_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_inserted_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_removed_id);
		g_signal_handler_disconnect (G_OBJECT (etta->priv->source),
				             etta->priv->tree_model_node_request_collapse_id);

		g_object_unref (etta->priv->source);
		etta->priv->source = NULL;

		etta->priv->tree_model_pre_change_id = 0;
		etta->priv->tree_model_no_change_id = 0;
		etta->priv->tree_model_node_changed_id = 0;
		etta->priv->tree_model_node_data_changed_id = 0;
		etta->priv->tree_model_node_col_changed_id = 0;
		etta->priv->tree_model_node_inserted_id = 0;
		etta->priv->tree_model_node_removed_id = 0;
		etta->priv->tree_model_node_request_collapse_id = 0;
	}

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

static void
etta_finalize (GObject *object)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *) object;

	g_hash_table_destroy (etta->priv->attributes);
	g_free (etta->priv->map_table);

	g_free (etta->priv);
	etta->priv = NULL;

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

static int
etta_column_count (ETableModel *etm)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_column_count (etta->priv->source);
}

static gboolean
etta_has_save_id (ETableModel *etm)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_has_save_id (etta->priv->source);
}

static gchar *
etta_get_save_id (ETableModel *etm, int row)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	if (etta->priv->root_visible)
		return e_tree_model_get_save_id (etta->priv->source, etta->priv->map_table [row]);
	else
		return e_tree_model_get_save_id (etta->priv->source, etta->priv->map_table [row + 1]);
}

static gboolean
etta_has_change_pending (ETableModel *etm)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_has_change_pending (etta->priv->source);
}


static int
etta_row_count (ETableModel *etm)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	if (etta->priv->root_visible)
		return etta->priv->n_map;
	else {
		if (etta->priv->n_map > 0)
			return etta->priv->n_map - 1;
		else
			return 0;
	}
}

static void *
etta_value_at (ETableModel *etm, int col, int row)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	switch (col) {
	case -1:
		if (etta->priv->root_visible)
			return etta->priv->map_table [row];
		else
			return etta->priv->map_table [row + 1];
	case -2:
		return etta->priv->source;
	case -3:
		return etta;
	default:
		if (etta->priv->root_visible)
			return e_tree_model_value_at (etta->priv->source, etta->priv->map_table [row], col);
		else
			return e_tree_model_value_at (etta->priv->source, etta->priv->map_table [row + 1], col);
	}
}

static void
etta_set_value_at (ETableModel *etm, int col, int row, const void *val)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	etta->priv->last_access = row;
	d(g_print("h) Setting last_access to %d\n", row));
	if (etta->priv->root_visible)
		e_tree_model_set_value_at (etta->priv->source, etta->priv->map_table [row], col, val);
	else
		e_tree_model_set_value_at (etta->priv->source, etta->priv->map_table [row + 1], col, val);
}

static gboolean
etta_is_cell_editable (ETableModel *etm, int col, int row)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	if (etta->priv->root_visible)
		return e_tree_model_node_is_editable (etta->priv->source, etta->priv->map_table [row], col);
	else
		return e_tree_model_node_is_editable (etta->priv->source, etta->priv->map_table [row + 1], col);
}

static void
etta_append_row (ETableModel *etm, ETableModel *source, int row)
{
#if 0
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;
	e_table_model_append_row (etta->priv->source, source, row);
#endif
}

static void *
etta_duplicate_value (ETableModel *etm, int col, const void *value)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_duplicate_value (etta->priv->source, col, value);
}

static void
etta_free_value (ETableModel *etm, int col, void *value)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	e_tree_model_free_value (etta->priv->source, col, value);
}

static void *
etta_initialize_value (ETableModel *etm, int col)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_initialize_value (etta->priv->source, col);
}

static gboolean
etta_value_is_empty (ETableModel *etm, int col, const void *value)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_value_is_empty (etta->priv->source, col, value);
}

static char *
etta_value_to_string (ETableModel *etm, int col, const void *value)
{
	ETreeTableAdapter *etta = (ETreeTableAdapter *)etm;

	return e_tree_model_value_to_string (etta->priv->source, col, value);
}

static void
etta_class_init (ETreeTableAdapterClass *klass)
{
	ETableModelClass *table_class   = (ETableModelClass *) klass;
	GObjectClass *object_class      = (GObjectClass *) klass;

	parent_class                    = g_type_class_peek_parent (klass);
	
	object_class->dispose           = etta_dispose;
	object_class->finalize          = etta_finalize;

	table_class->column_count       = etta_column_count;
	table_class->row_count          = etta_row_count;
	table_class->append_row         = etta_append_row;

	table_class->value_at           = etta_value_at;
	table_class->set_value_at       = etta_set_value_at;
	table_class->is_cell_editable   = etta_is_cell_editable;

	table_class->has_save_id        = etta_has_save_id;
	table_class->get_save_id        = etta_get_save_id;

	table_class->has_change_pending = etta_has_change_pending;
	table_class->duplicate_value    = etta_duplicate_value;
	table_class->free_value         = etta_free_value;
	table_class->initialize_value   = etta_initialize_value;
	table_class->value_is_empty     = etta_value_is_empty;
	table_class->value_to_string    = etta_value_to_string;
}

static void
etta_init (ETreeTableAdapter *etta)
{
	etta->priv                                      = g_new(ETreeTableAdapterPriv, 1);

	etta->priv->source                              = NULL;

	etta->priv->n_map                               = 0;
	etta->priv->n_vals_allocated                    = 0;
	etta->priv->map_table                           = NULL;
	etta->priv->attributes                          = NULL;

	etta->priv->root_visible                        = TRUE;

	etta->priv->last_access                         = 0;

	etta->priv->tree_model_pre_change_id            = 0;
	etta->priv->tree_model_no_change_id             = 0;
	etta->priv->tree_model_node_changed_id          = 0;
	etta->priv->tree_model_node_data_changed_id     = 0;
	etta->priv->tree_model_node_col_changed_id      = 0;
	etta->priv->tree_model_node_inserted_id         = 0;
	etta->priv->tree_model_node_removed_id          = 0;
	etta->priv->tree_model_node_request_collapse_id = 0;
}

E_MAKE_TYPE(e_tree_table_adapter, "ETreeTableAdapter", ETreeTableAdapter, etta_class_init, etta_init, E_TABLE_MODEL_TYPE)

static void
etta_proxy_pre_change (ETreeModel *etm, ETreeTableAdapter *etta)
{
	e_table_model_pre_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_no_change (ETreeModel *etm, ETreeTableAdapter *etta)
{
	e_table_model_no_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_changed (ETreeModel *etm, ETreePath path, ETreeTableAdapter *etta)
{
	if (e_tree_model_node_is_root(etm, path)) {
		int size;

		size = array_size_from_path(etta, path);
		etta_expand_to(etta, size);
		etta->priv->n_map = size;
		fill_array_from_path(etta, etta->priv->map_table, path);
	} else {
		int row = find_row_num(etta, path);
		int size;
		int old_size;
		ETreeTableAdapterNode *node;

		if (row == -1) {
			e_table_model_no_change(E_TABLE_MODEL(etta));
			return;
		}

		size = array_size_from_path(etta, path);

		node = find_node(etta, path);
		if (node)
			old_size = node->num_visible_children + 1;
		else
			old_size = 1;

		etta_expand_to(etta, etta->priv->n_map + size - old_size);

		memmove(etta->priv->map_table + row + size,
			etta->priv->map_table + row + old_size,
			(etta->priv->n_map - row - old_size) * sizeof (ETreePath));
		fill_array_from_path(etta, etta->priv->map_table + row, path);
		etta_update_parent_child_counts(etta, path, size - old_size);
	}

	e_table_model_changed(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_data_changed (ETreeModel *etm, ETreePath path, ETreeTableAdapter *etta)
{
	int row = find_row_num(etta, path);
	if (row != -1) {
		if (etta->priv->root_visible)
			e_table_model_row_changed(E_TABLE_MODEL(etta), row);
		else if (row != 0)
			e_table_model_row_changed(E_TABLE_MODEL(etta), row - 1);
		else
			e_table_model_no_change(E_TABLE_MODEL(etta));
	} else
		e_table_model_no_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_col_changed (ETreeModel *etm, ETreePath path, int col, ETreeTableAdapter *etta)
{
	int row = find_row_num(etta, path);
	if (row != -1) {
		if (etta->priv->root_visible)
			e_table_model_cell_changed(E_TABLE_MODEL(etta), col, row);
		else if (row != 0)
			e_table_model_cell_changed(E_TABLE_MODEL(etta), col, row - 1);
		else
			e_table_model_no_change(E_TABLE_MODEL(etta));
	} else
		e_table_model_no_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_inserted (ETreeModel *etm, ETreePath parent, ETreePath child, ETreeTableAdapter *etta)
{
	int row;

	if (e_tree_model_node_is_root(etm, child)) {
		row = 0;
	} else {
		ETreePath children;
		int parent_row;
		ETreeTableAdapterNode *parent_node;

		parent_row = find_row_num(etta, parent);
		if (parent_row == -1) {
			e_table_model_no_change(E_TABLE_MODEL(etta));
			return;
		}

		parent_node = find_or_create_node(etta, parent);
		if (parent_node->expandable != e_tree_model_node_is_expandable(etta->priv->source, parent)) {
			e_table_model_pre_change(E_TABLE_MODEL(etta));
			parent_node->expandable = e_tree_model_node_is_expandable(etta->priv->source, parent);
			parent_node->expandable_set = 1;
			if (etta->priv->root_visible)
				e_table_model_row_changed(E_TABLE_MODEL(etta), parent_row);
			else if (parent_row != 0)
				e_table_model_row_changed(E_TABLE_MODEL(etta), parent_row - 1);
			else
				e_table_model_no_change(E_TABLE_MODEL(etta));
		}

		if (!e_tree_table_adapter_node_is_expanded (etta, parent)) {
			e_table_model_no_change(E_TABLE_MODEL(etta));
			return;
		}

		row = find_first_child_node(etta, parent_row);
		children = e_tree_model_node_get_first_child(etta->priv->source, parent);

		while (row != -1 &&
		       row <= parent_row + parent_node->num_visible_children &&
		       children != NULL &&
		       children == etta->priv->map_table[row]) {
			children = e_tree_model_node_get_next(etta->priv->source, children);
			row = find_next_node(etta, row);
		}
	}

	if (row != -1) {
		int size;

		size = array_size_from_path(etta, child);

		etta_expand_to(etta, etta->priv->n_map + size);

		memmove(etta->priv->map_table + row + size,
			etta->priv->map_table + row,
			(etta->priv->n_map - row) * sizeof (ETreePath));

		fill_array_from_path(etta, etta->priv->map_table + row, child);
		etta_update_parent_child_counts(etta, child, size);

		if (etta->priv->root_visible)
			e_table_model_rows_inserted(E_TABLE_MODEL(etta), row, size);
		else if (row != 0)
			e_table_model_rows_inserted(E_TABLE_MODEL(etta), row - 1, size);
		else
			e_table_model_rows_inserted(E_TABLE_MODEL(etta), 0, size - 1);
	} else
		e_table_model_no_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_removed (ETreeModel *etm, ETreePath parent, ETreePath child, int old_position, ETreeTableAdapter *etta)
{
	int parent_row = find_row_num(etta, parent);
	int row = find_child_row_num_maybe_deleted(etta, parent_row, child);
	ETreeTableAdapterNode *parent_node = find_node(etta, parent);
	if (parent_row != -1 && parent_node) {
		if (parent_node->expandable != e_tree_model_node_is_expandable(etta->priv->source, parent)) {
			e_table_model_pre_change(E_TABLE_MODEL(etta));
			parent_node->expandable = e_tree_model_node_is_expandable(etta->priv->source, parent);
			if (etta->priv->root_visible)
				e_table_model_row_changed(E_TABLE_MODEL(etta), parent_row);
			else if (parent_row != 0)
				e_table_model_row_changed(E_TABLE_MODEL(etta), parent_row - 1);
			else
				e_table_model_no_change(E_TABLE_MODEL(etta));
		}
	}

	if (row != -1) {
		ETreeTableAdapterNode *node = find_node(etta, child);
		int to_remove = (node ? node->num_visible_children : 0) + 1;

		memmove(etta->priv->map_table + row,
			etta->priv->map_table + row + to_remove,
			(etta->priv->n_map - row - to_remove) * sizeof (ETreePath));

		if (parent_node)
			parent_node->num_visible_children -= to_remove;
		if (parent)
			etta_update_parent_child_counts(etta, parent, - to_remove);

		if (etta->priv->root_visible)
			e_table_model_rows_deleted(E_TABLE_MODEL(etta), row, to_remove);
		else if (row != 0)
			e_table_model_rows_deleted(E_TABLE_MODEL(etta), row - 1, to_remove);
		else
			e_table_model_rows_deleted(E_TABLE_MODEL(etta), 0, to_remove - 1);
	} else
		e_table_model_no_change(E_TABLE_MODEL(etta));
}

static void
etta_proxy_node_request_collapse (ETreeModel *etm, ETreePath node, ETreeTableAdapter *etta)
{
	e_tree_table_adapter_node_set_expanded(etta, node, FALSE);
}

ETableModel *
e_tree_table_adapter_construct (ETreeTableAdapter *etta, ETreeModel *source)
{
	ETreePath root;

	etta->priv->source = source;
	g_object_ref (source);

	if (e_tree_model_has_save_id(source))
		etta->priv->attributes = g_hash_table_new(g_str_hash, g_str_equal);
	else
		etta->priv->attributes = g_hash_table_new(NULL, NULL);

	root = e_tree_model_get_root (source);

	if (root) {
		etta->priv->n_map = array_size_from_path(etta, root);
		etta->priv->n_vals_allocated = etta->priv->n_map;
		etta->priv->map_table = g_new(ETreePath, etta->priv->n_map);
		fill_array_from_path(etta, etta->priv->map_table, root);
	}

	etta->priv->tree_model_pre_change_id            = g_signal_connect (G_OBJECT (source), "pre_change",
									    G_CALLBACK (etta_proxy_pre_change), etta);
	etta->priv->tree_model_no_change_id             = g_signal_connect (G_OBJECT (source), "no_change",
									    G_CALLBACK (etta_proxy_no_change), etta);
	etta->priv->tree_model_node_changed_id          = g_signal_connect (G_OBJECT (source), "node_changed",
									    G_CALLBACK (etta_proxy_node_changed), etta);
	etta->priv->tree_model_node_data_changed_id     = g_signal_connect (G_OBJECT (source), "node_data_changed",
									    G_CALLBACK (etta_proxy_node_data_changed), etta);
	etta->priv->tree_model_node_col_changed_id      = g_signal_connect (G_OBJECT (source), "node_col_changed",
									    G_CALLBACK (etta_proxy_node_col_changed), etta);
	etta->priv->tree_model_node_inserted_id         = g_signal_connect (G_OBJECT (source), "node_inserted",
									    G_CALLBACK (etta_proxy_node_inserted), etta);
	etta->priv->tree_model_node_removed_id          = g_signal_connect (G_OBJECT (source), "node_removed",
									    G_CALLBACK (etta_proxy_node_removed), etta);
	etta->priv->tree_model_node_request_collapse_id = g_signal_connect (G_OBJECT (source), "node_request_collapse",
									    G_CALLBACK (etta_proxy_node_request_collapse), etta);

	return E_TABLE_MODEL (etta);
}

ETableModel *
e_tree_table_adapter_new (ETreeModel *source)
{
	ETreeTableAdapter *etta = g_object_new (E_TREE_TABLE_ADAPTER_TYPE, NULL);

	e_tree_table_adapter_construct (etta, source);

	return (ETableModel *) etta;
}

typedef struct {
	xmlNode *root;
	ETreeModel *tree;
} TreeAndRoot;

static void
save_expanded_state_func (gpointer keyp, gpointer value, gpointer data)
{
	gchar *key = keyp;
	ETreeTableAdapterNode *node = value;
	TreeAndRoot *tar = data;
	xmlNode *root = tar->root;
	ETreeModel *etm = tar->tree;
	xmlNode *xmlnode;

	if (node->expanded != e_tree_model_get_expanded_default(etm)) {
		xmlnode = xmlNewChild (root, NULL, "node", NULL);
		e_xml_set_string_prop_by_name(xmlnode, "id", key);
	}
}

void
e_tree_table_adapter_save_expanded_state (ETreeTableAdapter *etta, const char *filename)
{
	ETreeTableAdapterPriv *priv;
	TreeAndRoot tar;
	xmlDocPtr doc;
	xmlNode *root;
	
	g_return_if_fail(etta != NULL);

	priv = etta->priv; 

	doc = xmlNewDoc ("1.0");
	root = xmlNewDocNode (doc, NULL,
			      (xmlChar *) "expanded_state",
			      NULL);
	xmlDocSetRootElement (doc, root);

	e_xml_set_integer_prop_by_name (root, "vers", 2);
	e_xml_set_bool_prop_by_name (root, "default", e_tree_model_get_expanded_default (priv->source));

	tar.root = root;
	tar.tree = etta->priv->source;
	
	g_hash_table_foreach (priv->attributes,
			      save_expanded_state_func,
			      &tar);
	
	xmlSaveFile (filename, doc);
	
	xmlFreeDoc (doc);
}

void
e_tree_table_adapter_load_expanded_state (ETreeTableAdapter *etta, const char *filename)
{
	ETreeTableAdapterPriv *priv;
	xmlDoc *doc;
	xmlNode *root;
	xmlNode *child;
	int vers;
	gboolean model_default, saved_default;
	int size;
	ETreePath path;

	g_return_if_fail(etta != NULL);

	priv = etta->priv;

	doc = xmlParseFile (filename);
	if (!doc)
		return;

	root = xmlDocGetRootElement (doc);
	if (root == NULL || strcmp (root->name, "expanded_state")) {
		xmlFreeDoc (doc);
		return;
	}

	vers = e_xml_get_integer_prop_by_name_with_default (root, "vers", 0);
	if (vers > 2) {
		xmlFreeDoc (doc);
		return;
	}
	model_default = e_tree_model_get_expanded_default (priv->source);
	saved_default = e_xml_get_bool_prop_by_name_with_default (root, "default", !model_default);
	if (saved_default != model_default) {
		xmlFreeDoc (doc);
		return;
	}

	e_table_model_pre_change(E_TABLE_MODEL(etta));

	e_table_model_pre_change(E_TABLE_MODEL(etta));

	for (child = root->xmlChildrenNode; child; child = child->next) {
		char *id;

		if (strcmp (child->name, "node")) {
			d(g_warning ("unknown node '%s' in %s", child->name, filename));
			continue;
		}

		id = e_xml_get_string_prop_by_name_with_default (child, "id", "");

		if (!strcmp(id, "")) {
			g_free(id);
			continue;
		}

		add_expanded_node(etta, id, !model_default);
	}
	

	xmlFreeDoc (doc);

	path = e_tree_model_get_root (etta->priv->source);
	size = array_size_from_path(etta, path);
	etta_expand_to(etta, size);
	etta->priv->n_map = size;
	fill_array_from_path(etta, etta->priv->map_table, path);

	e_table_model_changed (E_TABLE_MODEL (etta));
}

void         e_tree_table_adapter_root_node_set_visible (ETreeTableAdapter *etta, gboolean visible)
{
	if (etta->priv->root_visible == visible)
		return;

	e_table_model_pre_change (E_TABLE_MODEL(etta));

	etta->priv->root_visible = visible;
	if (!visible) {
		ETreePath root = e_tree_model_get_root(etta->priv->source);
		if (root)
			e_tree_table_adapter_node_set_expanded(etta, root, TRUE);
	}
	e_table_model_changed(E_TABLE_MODEL(etta));
}

void         e_tree_table_adapter_node_set_expanded (ETreeTableAdapter *etta, ETreePath path, gboolean expanded)
{
	ETreeTableAdapterNode *node;
	int row;

	if (!expanded && e_tree_model_node_is_root (etta->priv->source, path) && !etta->priv->root_visible)
		return;

	node = find_or_create_node(etta, path);

	if (expanded == node->expanded)
		return;

	node->expanded = expanded;
		
	row = find_row_num(etta, path);
	if (row != -1) {
		e_table_model_pre_change (E_TABLE_MODEL(etta));

		if (etta->priv->root_visible) {
			e_table_model_pre_change (E_TABLE_MODEL(etta));
			e_table_model_row_changed(E_TABLE_MODEL(etta), row);
		} else if (row != 0) {
			e_table_model_pre_change (E_TABLE_MODEL(etta));
			e_table_model_row_changed(E_TABLE_MODEL(etta), row - 1);
		}

		if (expanded) {
			int num_children = array_size_from_path(etta, path) - 1;
			etta_expand_to(etta, etta->priv->n_map + num_children);
			memmove(etta->priv->map_table + row + 1 + num_children,
				etta->priv->map_table + row + 1,
				(etta->priv->n_map - row - 1) * sizeof (ETreePath));
			fill_array_from_path(etta, etta->priv->map_table + row, path);
			etta_update_parent_child_counts(etta, path, num_children);
			if (num_children != 0) {
				if (etta->priv->root_visible)
					e_table_model_rows_inserted(E_TABLE_MODEL(etta), row + 1, num_children);
				else
					e_table_model_rows_inserted(E_TABLE_MODEL(etta), row, num_children);
			} else
				e_table_model_no_change(E_TABLE_MODEL(etta));
		} else {
			int num_children = node->num_visible_children;
			g_assert (etta->priv->n_map >= row + 1 + num_children);
			memmove(etta->priv->map_table + row + 1,
				etta->priv->map_table + row + 1 + num_children,
				(etta->priv->n_map - row - 1 - num_children) * sizeof (ETreePath));
			node->num_visible_children = 0;
			etta_update_parent_child_counts(etta, path, - num_children);
			if (num_children != 0) {
				if (etta->priv->root_visible)
					e_table_model_rows_deleted(E_TABLE_MODEL(etta), row + 1, num_children);
				else
					e_table_model_rows_deleted(E_TABLE_MODEL(etta), row, num_children);
			} else
				e_table_model_no_change(E_TABLE_MODEL(etta));
		}
	}
}

void         e_tree_table_adapter_node_set_expanded_recurse (ETreeTableAdapter *etta, ETreePath path, gboolean expanded)
{
	ETreePath children;

	e_tree_table_adapter_node_set_expanded(etta, path, expanded);

	for (children = e_tree_model_node_get_first_child(etta->priv->source, path); 
	     children; 
	     children = e_tree_model_node_get_next(etta->priv->source, children)) {
		e_tree_table_adapter_node_set_expanded_recurse(etta, children, expanded);
	}
}

ETreePath    e_tree_table_adapter_node_at_row (ETreeTableAdapter *etta, int row)
{
	if (row < 0)
		return NULL;
	if (etta->priv->root_visible) {
		if (row < etta->priv->n_map)
			return etta->priv->map_table[row];
	} else {
		if (row + 1 < etta->priv->n_map)
			return etta->priv->map_table[row + 1];
	}
	return NULL;
}

int    e_tree_table_adapter_row_of_node (ETreeTableAdapter *etta, ETreePath path)
{
	if (etta->priv->root_visible)
		return find_row_num(etta, path);
	else {
		int row_num = find_row_num (etta, path);
		if (row_num != -1)
			return row_num - 1;
		else
			return row_num;
	}
}

gboolean     e_tree_table_adapter_root_node_is_visible(ETreeTableAdapter *etta)
{
	return etta->priv->root_visible;
}

void         e_tree_table_adapter_show_node (ETreeTableAdapter *etta, ETreePath path)
{
	ETreePath parent;

	parent = e_tree_model_node_get_parent(etta->priv->source, path);

	while (parent) {
		e_tree_table_adapter_node_set_expanded(etta, parent, TRUE);
		parent = e_tree_model_node_get_parent(etta->priv->source, parent);
	}
}

gboolean     e_tree_table_adapter_node_is_expanded (ETreeTableAdapter *etta, ETreePath path)
{
	if (!e_tree_model_node_is_expandable (etta->priv->source, path))
		return FALSE;

	return e_tree_table_adapter_node_would_be_expanded (etta, path);
}
