/* proto_tree_model.c
 *
 * $Id$
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * 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
 * of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 */

/* This code was originally based on the GTK+ Tree View tutorial at
 * http://scentric.net/tutorial */

#include "config.h"

#include <string.h>

#include <gtk/gtk.h>
#include <glib.h>

#include <epan/addr_resolv.h>
#include <epan/proto.h>

static GObjectClass *parent_class = NULL;

typedef struct {
	GObjectClass parent_class;

} ProtoTreeModelClass;

struct proto_tree_model {
	GObject parent; /** MUST be first */

	/** Random integer to check whether an iter belongs to our model. */
	gint stamp;

	proto_tree *protocol_tree;
	int with_hidden;

	gboolean resolv_forced;
	e_addr_resolve resolv_flags;
};

#include "proto_tree_model.h"

static GtkTreeModelFlags
proto_tree_model_get_flags(GtkTreeModel *tree_model)
{
	g_return_val_if_fail(PROTO_IS_TREE(tree_model), (GtkTreeModelFlags)0);

	return GTK_TREE_MODEL_ITERS_PERSIST;
}

static gint
proto_tree_model_get_n_columns(GtkTreeModel *tree_model)
{
	g_return_val_if_fail(PROTO_IS_TREE(tree_model), 0);

	return 2;
}

static GType
proto_tree_model_get_column_type(GtkTreeModel *tree_model, gint idx)
{
	g_return_val_if_fail(PROTO_IS_TREE(tree_model), G_TYPE_INVALID);
	g_return_val_if_fail(idx == 0 || idx == 1, G_TYPE_INVALID);

	switch (idx) {
		case 0:
			return G_TYPE_STRING;
		case 1:
			return G_TYPE_POINTER;
	}
	/* never here */
	return G_TYPE_INVALID;
}

static gboolean
proto_tree_model_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
{
	ProtoTreeModel *model;
	proto_node *node;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), FALSE);
	model = (ProtoTreeModel *) tree_model;

	if (parent) {
		g_return_val_if_fail(parent->stamp == model->stamp, FALSE);
		node = parent->user_data;
	} else
		node = model->protocol_tree;

	if (!node)
		return FALSE;

	node = node->first_child;
	while (node != NULL) {
		if (model->with_hidden || !PROTO_ITEM_IS_HIDDEN(node)) {
			if (!n)
				break;
			n--;
		}
		node = node->next;
	}

	/* not found? */
	if (!node)
		return FALSE;

	iter->stamp = model->stamp;
	iter->user_data = node;
	return TRUE;
}

static gboolean
proto_tree_model_get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path)
{
	gint *indices, depth;

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

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

	g_return_val_if_fail(depth > 0, FALSE);

	if (!proto_tree_model_iter_nth_child(tree_model, iter, NULL, indices[0]))
		return FALSE;

	while (--depth) {
		indices++;
		if (!proto_tree_model_iter_nth_child(tree_model, iter, iter, *indices))
			return FALSE;
	}
	return TRUE;
}

static char *
fi_get_string(field_info *fi)
{
	gchar         label_str[ITEM_LABEL_LENGTH];
	gchar        *label_ptr;

	if (!fi->rep) {
		label_ptr = label_str;
		proto_item_fill_label(fi, label_str);
	} else 
		label_ptr = fi->rep->representation;

	if (FI_GET_FLAG(fi, FI_GENERATED)) {
		if (FI_GET_FLAG(fi, FI_HIDDEN))
			label_ptr = g_strdup_printf("<[%s]>", label_ptr);
		else
			label_ptr = g_strdup_printf("[%s]", label_ptr);

	} else if (FI_GET_FLAG(fi, FI_HIDDEN))
		label_ptr = g_strdup_printf("<%s>", label_ptr);
	else
		label_ptr = g_strdup(label_ptr);

	return label_ptr;
}

static void
proto_tree_model_get_value(GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value)
{
	ProtoTreeModel *model;
	proto_node *node;
	field_info *fi;

	g_return_if_fail(PROTO_IS_TREE(tree_model));
	model = (ProtoTreeModel *) tree_model;

	g_return_if_fail(iter != NULL);
	g_return_if_fail(iter->stamp == model->stamp);
	g_return_if_fail(column == 0 || column == 1);

	node = iter->user_data;
	fi = PNODE_FINFO(node);

	/* dissection with an invisible proto tree? */
	g_assert(fi);

	switch (column) {
		case 0:
		{
			g_value_init(value, G_TYPE_STRING);
			if (model->resolv_forced) {
				e_addr_resolve old_flags = gbl_resolv_flags;

				gbl_resolv_flags = model->resolv_flags;
				g_value_take_string(value, fi_get_string(fi));
				gbl_resolv_flags = old_flags;

			} else
				g_value_take_string(value, fi_get_string(fi));
			break;
		}

		case 1:
			g_value_init(value, G_TYPE_POINTER);
			g_value_set_pointer(value, fi);
			break;
	}
}

static gboolean
proto_tree_model_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
	ProtoTreeModel *model;
	proto_node *current;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), FALSE);
	model = (ProtoTreeModel *) tree_model;

	g_return_val_if_fail(iter->stamp == model->stamp, FALSE);

	current = iter->user_data;
	current = current->next;
	while (current) {
		if (model->with_hidden || !PROTO_ITEM_IS_HIDDEN(current)) {
			iter->user_data = current;
			return TRUE;
		}
		current = current->next;
	}
	return FALSE;
}

static gboolean
proto_tree_model_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
{
	return proto_tree_model_iter_nth_child(tree_model, iter, parent, 0);
}

static gint
proto_tree_model_iter_n_children(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
	ProtoTreeModel *model;
	proto_node *node;
	gint count;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), 0);
	model = (ProtoTreeModel *) tree_model;

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

	if (iter) {
		g_return_val_if_fail(iter->stamp == model->stamp, 0);
		node = iter->user_data;
	} else
		node = model->protocol_tree;

	if (!node)
		return 0;

	count = 0;
	node = node->first_child;
	while (node != NULL) {
		if (model->with_hidden || !PROTO_ITEM_IS_HIDDEN(node))
			count++;
		node = node->next;
	}
	return count;
}

static GtkTreePath *
proto_tree_model_get_path(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
	ProtoTreeModel *model;
	GtkTreePath *path;
	proto_node *node;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), NULL);
	model = (ProtoTreeModel *) tree_model;

	g_return_val_if_fail(iter != NULL, NULL);
	g_return_val_if_fail(iter->stamp == model->stamp, NULL);

	node = iter->user_data;
	g_return_val_if_fail(node != model->protocol_tree, NULL);

	path = gtk_tree_path_new();
	do {
		proto_node *cur = node;
		proto_node *node_i;
		int pos;

		node = node->parent;

		pos = 0;
		for (node_i = node->first_child; node_i; node_i = node_i->next) {
			if (model->with_hidden || !PROTO_ITEM_IS_HIDDEN(node_i)) {
				if (node_i == cur)
					break;
				pos++;
			}
		}

		g_assert(node_i != NULL);
		gtk_tree_path_prepend_index(path, pos);
	} while (node != model->protocol_tree);

	return path;
}

static gboolean
proto_tree_model_iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter)
{
	/* optimized version of:
	 *    return proto_tree_model_iter_n_children(tree_model, iter) != 0;
	 * synchronize when changed!
	 */
	ProtoTreeModel *model;
	proto_node *node;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), FALSE);
	model = (ProtoTreeModel *) tree_model;

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

	if (iter) {
		g_return_val_if_fail(iter->stamp == model->stamp, FALSE);
		node = iter->user_data;
	} else
		node = model->protocol_tree;

	if (!node)
		return FALSE;

	node = node->first_child;
	while (node != NULL) {
		if (model->with_hidden || !PROTO_ITEM_IS_HIDDEN(node))
			return TRUE;
		node = node->next;
	}
	return FALSE;
}

static gboolean
proto_tree_model_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
{
	ProtoTreeModel *model;
	proto_node *node;

	g_return_val_if_fail(PROTO_IS_TREE(tree_model), FALSE);
	model = (ProtoTreeModel *) tree_model;

	g_return_val_if_fail(iter != NULL, FALSE);
	g_return_val_if_fail(child->stamp == model->stamp, FALSE);

	node = child->user_data;
	if (node->parent == model->protocol_tree)
		return FALSE;
	g_return_val_if_fail(node->parent != NULL, FALSE);
	iter->stamp = model->stamp;
	iter->user_data = node->parent;
	return TRUE;
}

static void
proto_tree_model_tree_init(GtkTreeModelIface *iface)
{
	iface->get_flags = proto_tree_model_get_flags;
	iface->get_n_columns = proto_tree_model_get_n_columns;
	iface->get_column_type = proto_tree_model_get_column_type;
	iface->get_iter = proto_tree_model_get_iter;
	iface->get_path = proto_tree_model_get_path;
	iface->get_value = proto_tree_model_get_value;
	iface->iter_next = proto_tree_model_iter_next;
	iface->iter_children = proto_tree_model_iter_children;
	iface->iter_has_child = proto_tree_model_iter_has_child;
	iface->iter_n_children = proto_tree_model_iter_n_children;
	iface->iter_nth_child = proto_tree_model_iter_nth_child;
	iface->iter_parent = proto_tree_model_iter_parent;
}

static void
proto_tree_model_init(ProtoTreeModel *model)
{
	/* To check whether an iter belongs to our model. */
	model->stamp = g_random_int();
}

static void
_class_finalize(GObject *object)
{
	/* must chain up - finalize parent */
	(*parent_class->finalize)(object);
}

static void
proto_tree_model_class_init(ProtoTreeModelClass *klass)
{
	GObjectClass *object_class;

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

	object_class->finalize = _class_finalize;
}

GType 
proto_tree_model_get_type(void)
{
	static GType proto_tree_type = 0;

	if (proto_tree_type == 0) {
		static const GTypeInfo proto_tree_info = {
			sizeof(ProtoTreeModelClass),
			NULL, /* base_init */
			NULL, /* base_finalize */
			(GClassInitFunc) proto_tree_model_class_init,
			NULL, /* class finalize */
			NULL, /* class_data */
			sizeof(ProtoTreeModel),
			0, /* n_preallocs */
			(GInstanceInitFunc) proto_tree_model_init,
			NULL /* value_table */
		};

		static const GInterfaceInfo tree_model_info = {
			(GInterfaceInitFunc) proto_tree_model_tree_init,
			NULL,
			NULL
		};

		/* Register the new derived type with the GObject type system */
		proto_tree_type = g_type_register_static(G_TYPE_OBJECT,
							  "ProtoTreeModel",
							  &proto_tree_info,
							  (GTypeFlags)0);

		g_type_add_interface_static(proto_tree_type,
						GTK_TYPE_TREE_MODEL,
						&tree_model_info);
	}
	return proto_tree_type;
}

void 
proto_tree_model_force_resolv(ProtoTreeModel *model, const e_addr_resolve *flags)
{
	model->resolv_forced = TRUE;
	model->resolv_flags  = *flags;
}

ProtoTreeModel *
proto_tree_model_new(proto_tree *protocol_tree, int display_hidden_proto_items)
{
	ProtoTreeModel *model;

	model = (ProtoTreeModel *) g_object_new(PROTO_TYPE_TREE, NULL);

	g_assert(model != NULL);
	model->protocol_tree = protocol_tree;
	model->with_hidden   = display_hidden_proto_items;
	model->resolv_forced = FALSE;

	return model;
}
