/*
    GQ -- a GTK-based LDAP client
    Copyright (C) 1998-2003 Bert Vermeulen
    Copyright (C) 2002-2003 Peter Stamfest
    Copyright (C) 2006      Sven Herzberg

    This program is released under the Gnu General Public License with
    the additional exemption that compiling, linking, and/or using
    OpenSSL is allowed.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#undef SERVER_LIST_DEBUG

#include "gq-server-list.h"

#include "gq-marshallers.h"
#include "gq-server.h"
#include "gq-utilities.h" // FIXME: for close_connection()

struct _GQServerListPrivate {
	GList* servers;
	GHashTable* servers_by_name;
	GHashTable* servers_by_canonical_name;
	GHashTable* names_by_servers;
};
#define P(self) (GQ_SERVER_LIST(self)->priv)

/* FIXME: think whether we'd like to have really this way */
static GQServerList* instance = NULL;

enum {
	SIGNAL_ADDED,
	SIGNAL_CHANGED,
	SIGNAL_REMOVED,
	N_SIGNALS
};

static guint server_list_signals[N_SIGNALS] = {0};

static void
server_list_notify_name(GqServerList* self,
			GParamSpec  * pspec  G_GNUC_UNUSED,
			GqServer    * server)
{
	gint index = gq_server_list_get_index(self, server);
	gchar* old_name;
	g_signal_emit(self, server_list_signals[SIGNAL_CHANGED], 0, server, index);

	// FIXME: and adjust the mapping of the items
	old_name = g_strdup(g_hash_table_lookup(P(self)->names_by_servers, server));
	g_hash_table_remove(P(self)->names_by_servers, server);
	g_hash_table_remove(P(self)->servers_by_name,  old_name);
	g_hash_table_remove(P(self)->servers_by_canonical_name, server->canon_name);

	g_hash_table_insert(self->priv->servers_by_name, (gchar*)gq_server_get_name(server), server);
	g_hash_table_insert(self->priv->servers_by_canonical_name, server->canon_name, server);
	g_hash_table_insert(P(self)->names_by_servers, server, g_strdup(gq_server_get_name(server)));
	g_free(old_name);
}

void
gq_server_list_add(GQServerList* self, GqServer* server)
{
	g_return_if_fail(GQ_IS_SERVER_LIST(self));
	g_return_if_fail(GQ_IS_SERVER(server));
	g_return_if_fail(!gq_server_list_contains(self, server));
#ifdef SERVER_LIST_DEBUG
	g_print("%s(%p, %p)\n", __PRETTY_FUNCTION__, self, server);
#endif
	self->priv->servers = g_list_prepend(self->priv->servers, g_object_ref(server));
	g_hash_table_insert(self->priv->servers_by_name, (gchar*)gq_server_get_name(server), server);
	g_hash_table_insert(self->priv->servers_by_canonical_name, server->canon_name, server);
	g_hash_table_insert(P(self)->names_by_servers, server, g_strdup(gq_server_get_name(server)));

	g_signal_connect_swapped(server, "notify::name",
				 G_CALLBACK(server_list_notify_name), self);

	g_signal_emit(self, server_list_signals[SIGNAL_ADDED], 0, server, 0);
}

gboolean
gq_server_list_contains(GQServerList const* self, GqServer const* server) {
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), FALSE);
	g_return_val_if_fail(GQ_IS_SERVER(server), FALSE);

	return gq_server_list_get_by_name(self, gq_server_get_name(server)) == server;
}

static void
gsl_foreach(gpointer server, gpointer data) {
	gpointer* self_func_and_data = data;
	((GQServerListForeachFunc)self_func_and_data[1])(GQ_SERVER_LIST(self_func_and_data[0]), GQ_SERVER(server), self_func_and_data[2]);
}

void
gq_server_list_foreach(GQServerList* self, GQServerListForeachFunc func, gpointer user_data) {
	gpointer self_func_and_data[3] = {
		self,
		func,
		user_data
	};
	g_return_if_fail(GQ_IS_SERVER_LIST(self));
	g_return_if_fail(func);

	g_list_foreach(self->priv->servers, gsl_foreach, self_func_and_data);
}

GQServerList*
gq_server_list_get(void) {
	return g_object_new(GQ_TYPE_SERVER_LIST, NULL);
}

GqServer*
gq_server_list_get_by_canon_name(GQServerList const* self, gchar const* canonical_name) {
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), NULL);
	g_return_val_if_fail(canonical_name && *canonical_name, NULL);

	return g_hash_table_lookup(self->priv->servers_by_canonical_name, canonical_name);
}

GqServer*
gq_server_list_get_by_name(GQServerList const* self, gchar const* name) {
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), NULL);
	g_return_val_if_fail(name && *name, NULL);

	return g_hash_table_lookup(self->priv->servers_by_name, name);
}

gint
gq_server_list_get_index(GqServerList const* self,
			 GqServer const    * server)
{
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), -1);
	g_return_val_if_fail(GQ_IS_SERVER(server), -1);

	return g_list_index(P(self)->servers, server);
}

GqServer*
gq_server_list_get_server(GQServerList const* self, guint server) {
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), NULL);
	g_return_val_if_fail(server < gq_server_list_n_servers(self), NULL);

	return GQ_SERVER(g_list_nth_data(self->priv->servers, server));
}

guint
gq_server_list_n_servers(GQServerList const* self) {
	g_return_val_if_fail(GQ_IS_SERVER_LIST(self), 0);

	return g_hash_table_size(self->priv->servers_by_name);
}

void
gq_server_list_remove(GQServerList* self, GqServer* server)
{
	gint old_index;
#ifdef SERVER_LIST_DEBUG
	g_print("%s(%p, %p)\n", __PRETTY_FUNCTION__, self, server);
#endif
	g_return_if_fail(GQ_IS_SERVER_LIST(self));
	g_return_if_fail(GQ_IS_SERVER(server));
	g_return_if_fail(gq_server_list_contains(self, server));

	old_index = gq_server_list_get_index(self, server);

	g_signal_handlers_disconnect_by_func(server, server_list_notify_name, self);
	self->priv->servers = g_list_remove(self->priv->servers, server);
	g_hash_table_remove(self->priv->servers_by_name, gq_server_get_name(server));
	g_hash_table_remove(self->priv->servers_by_canonical_name, server->canon_name);

	g_signal_emit(self, server_list_signals[SIGNAL_REMOVED], 0, old_index);
}

/* GType */
G_DEFINE_TYPE(GQServerList, gq_server_list, G_TYPE_OBJECT);

static void
gq_server_list_init(GQServerList* self) {
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, GQ_TYPE_SERVER_LIST, GQServerListPrivate);
	self->priv->servers_by_name           = g_hash_table_new(g_str_hash, g_str_equal);
	self->priv->servers_by_canonical_name = g_hash_table_new(g_str_hash, g_str_equal);
	self->priv->names_by_servers          = g_hash_table_new_full(g_direct_hash, g_direct_equal,
								      NULL, g_free);
}

static GObject*
gsl_constructor(GType type, guint n_params, GObjectConstructParam* params) {
	if(!instance) {
		GObject* retval = G_OBJECT_CLASS(gq_server_list_parent_class)->constructor(type, n_params, params);
		instance = GQ_SERVER_LIST(retval);
		return retval;
	} else {
		GObject* retval = G_OBJECT(instance);
		g_object_freeze_notify(retval); // to avoid a warning; glib bug 315053
		// FIXME: think whether or not we should ref the thing here
		return retval;
	}
}

static void
gsl_finalize_per_server(GqServer* server) {
	close_connection(server, TRUE);
	g_object_unref(server);
}

static void
gsl_finalize(GObject* object) {
	GQServerList* self = GQ_SERVER_LIST(object);

	instance = NULL;

	g_list_foreach(self->priv->servers, (GFunc)gsl_finalize_per_server, NULL);
	g_list_free(self->priv->servers);
	self->priv->servers = NULL;

	g_hash_table_destroy(self->priv->servers_by_name);
	self->priv->servers_by_name = NULL;
	g_hash_table_destroy(self->priv->servers_by_canonical_name);
	self->priv->servers_by_canonical_name = NULL;

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

static void
gq_server_list_class_init(GQServerListClass* self_class)
{
	GObjectClass* go_class = G_OBJECT_CLASS(self_class);

	/* GObjectClass */
	go_class->constructor = gsl_constructor;
	go_class->finalize    = gsl_finalize;

	/* GQServerListClass */
	g_type_class_add_private(self_class, sizeof(GQServerListPrivate));

	server_list_signals[SIGNAL_ADDED] =
		g_signal_new("server-added", GQ_TYPE_SERVER_LIST,
			     0, 0,
			     NULL, NULL,
			     gq_marshal_VOID__OBJECT_INT,
			     G_TYPE_NONE, 2,
			     GQ_TYPE_SERVER,
			     G_TYPE_INT);
	server_list_signals[SIGNAL_CHANGED] =
		g_signal_new("server-changed", GQ_TYPE_SERVER_LIST,
			     0, 0,
			     NULL, NULL,
			     gq_marshal_VOID__OBJECT_INT,
			     G_TYPE_NONE, 2,
			     GQ_TYPE_SERVER,
			     G_TYPE_INT);
	server_list_signals[SIGNAL_REMOVED] =
		g_signal_new("server-removed", GQ_TYPE_SERVER_LIST,
			     0, 0,
			     NULL, NULL,
			     g_cclosure_marshal_VOID__INT,
			     G_TYPE_NONE, 1,
			     G_TYPE_INT);
}

