/* This file is part of GQ
 *
 * AUTHORS
 *     Sven Herzberg  <herzi@gnome-de.org>
 *
 * Copyright (C) 2006  Sven Herzberg <herzi@gnome-de.org>
 *
 * 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.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include "gq-server-model.h"

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

struct _GqServerModelClass {
	GObjectClass base_instance;
	GType        types[GQ_SERVER_MODEL_N_COLUMNS];
};

struct GqServerModelPrivate {
	GqServerList* list;
	GList       * servers;
	gint          stamp;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_SERVER_MODEL, struct GqServerModelPrivate))

/* Iter format:
 *
 * user_data:  #GqServer
 * user_data2: the index of the server in the list
 * user_data3:
 */

/**
 * gq_server_model_new:
 * @list: a #GqServerList (never #NULL)
 *
 * Create a tree model for a given server list.
 *
 * Returns a new #GqServerModel.
 */
GtkTreeModel*
gq_server_model_new(GqServerList* list)
{
	g_return_val_if_fail(GQ_IS_SERVER_LIST(list), NULL);

	return g_object_new(GQ_TYPE_SERVER_MODEL,
			    "list", list,
			    NULL);
}

gboolean
gq_server_model_get_iter(GqServerModel const* self,
			 GtkTreeIter        * iter,
			 GqServer           * server)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(self), FALSE);
	g_return_val_if_fail(GQ_IS_SERVER_LIST(P(self)->list), FALSE);
	g_return_val_if_fail(GQ_IS_SERVER(server), FALSE);
	g_return_val_if_fail(gq_server_list_contains(P(self)->list, server), FALSE);

	return gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(self), iter, NULL,
					     gq_server_list_get_index(P(self)->list, server));
}

static gboolean
server_model_check_iter(GqServerModel const* self,
			GtkTreeIter const  * iter)
{
	return iter && iter->stamp == P(self)->stamp;
}

/**
 * gq_server_model_get_server:
 * @self: a #GqServerModel
 * @iter: a valid #GtkTreeIter obtained from this model
 *
 * Get the server represented by @iter.
 *
 * Returns the #GqServer represented by @iter (#NULL on error)
 */
GqServer*
gq_server_model_get_server(GqServerModel const* self,
			   GtkTreeIter        * iter)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(self), NULL);
	g_return_val_if_fail(server_model_check_iter(self, iter), NULL);

	return iter->user_data;
}

static void
server_model_update_connected(GqServerModel* self,
			      GParamSpec   * pspec G_GNUC_UNUSED,
			      GqServer     * server)
{
	GtkTreePath* path;
	GtkTreeIter  iter;

	g_return_if_fail(gq_server_model_get_iter(self, &iter, server));
	path = gtk_tree_model_get_path(GTK_TREE_MODEL(self), &iter);
	gtk_tree_model_row_changed(GTK_TREE_MODEL(self), path, &iter);
	gtk_tree_path_free(path);
}

static void
server_model_added_server(GqServerModel* self,
			  GqServer     * server,
			  gint           index)
{
	GtkTreePath* path = gtk_tree_path_new();
	GtkTreeIter  iter;
	gtk_tree_path_append_index(path, index);

	g_assert(gtk_tree_model_get_iter(GTK_TREE_MODEL(self), &iter, path));
	gtk_tree_model_row_inserted(GTK_TREE_MODEL(self), path, &iter);

	gtk_tree_path_free(path);

	P(self)->servers = g_list_insert(P(self)->servers, g_object_ref(server), index);
	g_signal_connect_swapped(server, "notify::connected",
				 G_CALLBACK(server_model_update_connected), self);
}

static void
server_model_removed_server(GqServerModel* self,
			    gint           index)
{
	GtkTreePath* path = gtk_tree_path_new();
	GList      * item;
	gtk_tree_path_append_index(path, index);

	item = g_list_nth(P(self)->servers, index);
	g_signal_handlers_disconnect_by_func(item->data, server_model_update_connected, self);
	g_object_unref(item->data);
	P(self)->servers = g_list_delete_link(P(self)->servers, item);

	gtk_tree_model_row_deleted(GTK_TREE_MODEL(self), path);
	gtk_tree_path_free(path);
}

static void
server_model_changed_server(GqServerModel* self,
			    GqServer     * server G_GNUC_UNUSED,
			    gint           index)
{
	GtkTreePath* path = gtk_tree_path_new();
	GtkTreeIter  iter;
	gtk_tree_path_append_index(path, index);

	g_assert(gtk_tree_model_get_iter(GTK_TREE_MODEL(self), &iter, path));
	gtk_tree_model_row_changed(GTK_TREE_MODEL(self), path, &iter);

	gtk_tree_path_free(path);
}

static void
server_model_set_list(GqServerModel* self,
		      GqServerList * list)
{
	g_return_if_fail(GQ_IS_SERVER_MODEL(self));
	g_return_if_fail(!list || GQ_IS_SERVER_LIST(list));

	if(P(self)->list == list) {
		return;
	}

	if(P(self)->list) {
		gint i, length;
		for(i = 1, length = gq_server_list_n_servers(P(self)->list); i <= length; i++) {
			server_model_removed_server(self, length - i);
		}
		g_signal_handlers_disconnect_by_func(P(self)->list, server_model_added_server, self);
		g_signal_handlers_disconnect_by_func(P(self)->list, server_model_changed_server, self);
		g_signal_handlers_disconnect_by_func(P(self)->list, server_model_removed_server, self);
		g_object_unref(P(self)->list);
		P(self)->list = NULL;
	}

	if(list) {
		gint index, len;
		P(self)->list = g_object_ref(list);
		g_signal_connect_swapped(P(self)->list, "server-added",
					 G_CALLBACK(server_model_added_server), self);
		g_signal_connect_swapped(P(self)->list, "server-removed",
					 G_CALLBACK(server_model_removed_server), self);
		g_signal_connect_swapped(P(self)->list, "server-changed",
					 G_CALLBACK(server_model_changed_server), self);
		// FIXME: come up with an iterator based solution
		for(index = 0, len = gq_server_list_n_servers(P(self)->list); index < len; index++) {
			server_model_added_server(self, gq_server_list_get_server(P(self)->list, index), index);
		} 
	}

	g_object_notify(G_OBJECT(self), "list");
}

/* GType */
static void server_model_implement_tree(GtkTreeModelIface* iface);
G_DEFINE_TYPE_WITH_CODE(GqServerModel, gq_server_model, G_TYPE_OBJECT,
			G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, server_model_implement_tree));

enum {
	PROP_0,
	PROP_LIST
};

static void
gq_server_model_init(GqServerModel* self)
{
	P(self)->stamp = g_random_int();
}

static void
server_model_dispose(GObject* object)
{
	server_model_set_list(GQ_SERVER_MODEL(object), NULL);

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

static void
server_model_get_property(GObject   * object,
			  guint       prop_id,
			  GValue    * value,
			  GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_LIST:
		g_value_set_object(value, P(object)->list);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
server_model_set_property(GObject     * object,
			  guint         prop_id,
			  GValue const* value,
			  GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_LIST:
		server_model_set_list(GQ_SERVER_MODEL(object), g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
gq_server_model_class_init(GqServerModelClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);

	object_class->dispose      = server_model_dispose;
	object_class->get_property = server_model_get_property;
	object_class->set_property = server_model_set_property;

	g_object_class_install_property(object_class,
					PROP_LIST,
					g_param_spec_object("list",
							    _("List"),
							    _("The server list modelled by this server model"),
							    GQ_TYPE_SERVER_LIST,
							    G_PARAM_READWRITE));

	g_type_class_add_private(self_class, sizeof(struct GqServerModelPrivate));

	self_class->types[GQ_SERVER_MODEL_COL_NAME] = G_TYPE_STRING;
	self_class->types[GQ_SERVER_MODEL_COL_STATUS] = G_TYPE_STRING;
	g_return_if_fail(GQ_SERVER_MODEL_N_COLUMNS == 2);
}

/* GtkTreeModel implementation */
static GtkTreeModelFlags
server_model_get_flags(GtkTreeModel* model)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), 0);

	return GTK_TREE_MODEL_LIST_ONLY;
}

static gint
server_model_get_n_columns(GtkTreeModel* model)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), 0);

	return GQ_SERVER_MODEL_N_COLUMNS;
}

static GType
server_model_get_column_type(GtkTreeModel* model,
			     gint          column)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), G_TYPE_INVALID);
	g_return_val_if_fail(0 <= column && column < GQ_SERVER_MODEL_N_COLUMNS, G_TYPE_INVALID);

	return GQ_SERVER_MODEL_GET_CLASS(model)->types[column];
}

static void
server_model_setup_iter(GqServerModel* self,
			GtkTreeIter  * iter)
{
	iter->stamp = P(self)->stamp;
}

static void
server_model_set_iter(GqServerModel* self,
		      GtkTreeIter  * iter,
		      GqServer     * server,
		      gint           index)
{
	server_model_setup_iter(self, iter);

	iter->user_data  = server;
	iter->user_data2 = GINT_TO_POINTER(index);
}

static gboolean
server_model_iter_nth_child(GtkTreeModel* model,
			    GtkTreeIter * iter,
			    GtkTreeIter * parent,
			    gint          index)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), FALSE);
	g_return_val_if_fail(iter, FALSE);
	g_return_val_if_fail(!parent, FALSE);
	g_return_val_if_fail(GQ_IS_SERVER_LIST(P(model)->list), FALSE);

	if(0 <= index && (guint)index < gq_server_list_n_servers(P(model)->list)) {
		server_model_set_iter(GQ_SERVER_MODEL(model), iter,
				      gq_server_list_get_server(P(model)->list, index),
				      index);
		return TRUE;
	}

	return FALSE;
}

static gboolean
server_model_get_iter(GtkTreeModel* model, GtkTreeIter* iter, GtkTreePath* path)
{
	g_return_val_if_fail(gtk_tree_path_get_depth(path) == 1, FALSE);
	return server_model_iter_nth_child(model, iter, NULL, gtk_tree_path_get_indices(path)[0]);
}

static GtkTreePath*
server_model_get_path(GtkTreeModel* model, GtkTreeIter* iter)
{
	GtkTreePath* path;

	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), NULL);
	g_return_val_if_fail(server_model_check_iter(GQ_SERVER_MODEL(model), iter), NULL);

	path = gtk_tree_path_new();
	gtk_tree_path_append_index(path, GPOINTER_TO_INT(iter->user_data2));

	return path;
}

static void
server_model_get_value(GtkTreeModel* model,
		       GtkTreeIter * iter,
		       gint          column,
		       GValue      * value)
{
	g_return_if_fail(GQ_IS_SERVER_MODEL(model));
	g_return_if_fail(server_model_check_iter(GQ_SERVER_MODEL(model), iter));

	g_value_init(value, server_model_get_column_type(model, column));

	switch(column) {
	case GQ_SERVER_MODEL_COL_NAME:
		g_value_set_string(value,
				   gq_server_get_name(gq_server_model_get_server(GQ_SERVER_MODEL(model),
										 iter)));
		break;
	case GQ_SERVER_MODEL_COL_STATUS:
		g_value_set_string(value, gq_server_is_connected(gq_server_model_get_server(GQ_SERVER_MODEL(model), iter))
				   ? GTK_STOCK_CONNECT : GTK_STOCK_DISCONNECT);
		break;
	default:
		g_warning("Invalid column index %d for GqServerModel", column);
		break;
	}
}

static gboolean
server_model_iter_next(GtkTreeModel* model, GtkTreeIter* iter)
{
	gint index;

	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), FALSE);
	g_return_val_if_fail(server_model_check_iter(GQ_SERVER_MODEL(model), iter), FALSE);

	index = GPOINTER_TO_INT(iter->user_data2);
	index++;

	if(0 <= index && (guint)index < gq_server_list_n_servers(P(model)->list)) {
		server_model_set_iter(GQ_SERVER_MODEL(model), iter,
				      gq_server_list_get_server(P(model)->list, index),
				      index);
		return TRUE;
	}

	return FALSE;
}

static gboolean
server_model_iter_children(GtkTreeModel* model,
			   GtkTreeIter * iter,
			   GtkTreeIter * parent)
{
	return server_model_iter_nth_child(model, iter, parent, 0);
}

static gboolean
server_model_iter_has_child(GtkTreeModel* model, GtkTreeIter* iter)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), FALSE);

	return iter ? FALSE : TRUE;
}

static gint
server_model_iter_n_children(GtkTreeModel* model, GtkTreeIter* iter)
{
	g_return_val_if_fail(GQ_IS_SERVER_MODEL(model), 0);
	g_return_val_if_fail(!iter, 0);

	return gq_server_list_n_servers(P(model)->list);
}

static gboolean
server_model_iter_parent(GtkTreeModel* model G_GNUC_UNUSED,
			 GtkTreeIter * iter  G_GNUC_UNUSED,
			 GtkTreeIter * child G_GNUC_UNUSED)
{
	return FALSE;
}

static void
server_model_ref_node(GtkTreeModel* model,
		      GtkTreeIter * iter)
{
	g_return_if_fail(GQ_IS_SERVER_MODEL(model));
	g_return_if_fail(server_model_check_iter(GQ_SERVER_MODEL(model), iter));

	g_object_ref(iter->user_data);
}

static void
server_model_unref_node(GtkTreeModel* model,
			GtkTreeIter * iter)
{
	g_return_if_fail(GQ_IS_SERVER_MODEL(model));
	g_return_if_fail(server_model_check_iter(GQ_SERVER_MODEL(model), iter));

	g_object_unref(iter->user_data);
}

static void
server_model_implement_tree(GtkTreeModelIface* iface)
{
	iface->get_flags       = server_model_get_flags;
	iface->get_n_columns   = server_model_get_n_columns;
	iface->get_column_type = server_model_get_column_type;
	iface->get_iter        = server_model_get_iter;
	iface->get_path        = server_model_get_path;
	iface->get_value       = server_model_get_value;
	iface->iter_next       = server_model_iter_next;
	iface->iter_children   = server_model_iter_children;
	iface->iter_has_child  = server_model_iter_has_child;
	iface->iter_n_children = server_model_iter_n_children;
	iface->iter_nth_child  = server_model_iter_nth_child;
	iface->iter_parent     = server_model_iter_parent;
	iface->ref_node        = server_model_ref_node;
	iface->unref_node      = server_model_unref_node;
}

