/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            camel-kolab-imapx-conn-manager.c
 *
 *  2012-01-10, 17:07:28
 *  Copyright 2012, Christian Hilberg
 *  <hilberg@unix-ag.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 main.c; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/

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

#include "camel-kolab-imapx-server.h"

#include "camel-kolab-imapx-conn-manager.h"

#include <camel/providers/imapx/camel-imapx-conn-manager-defs.h>
#include <camel/providers/imapx/camel-imapx-conn-manager-friend.h>
#include <camel/providers/imapx/camel-imapx-extd-conn-manager-friend.h>

/*----------------------------------------------------------------------------*/

G_DEFINE_TYPE (CamelKolabIMAPXConnManager, camel_kolab_imapx_conn_manager, CAMEL_TYPE_IMAPX_EXTD_CONN_MANAGER)

/*----------------------------------------------------------------------------*/
/* object init */

static void
camel_kolab_imapx_conn_manager_init (CamelKolabIMAPXConnManager *self)
{
	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self));
}

static void
camel_kolab_imapx_conn_manager_dispose (GObject *object)
{
	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (object));

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

static void
camel_kolab_imapx_conn_manager_finalize (GObject *object)
{
	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (object));

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

/*----------------------------------------------------------------------------*/
/* local statics */

static void
kolab_imapx_conn_manager_conn_shutdown (CamelIMAPXServer *is,
                                        CamelIMAPXConnManager *con_man)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (is));
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (con_man));

	camel_imapx_extd_conn_manager_conn_shutdown (is, con_man);
}

static void
kolab_imapx_conn_manager_conn_update_select (CamelIMAPXServer *is,
                                             const gchar *selected_folder,
                                             CamelIMAPXConnManager *con_man)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (is));
	g_assert (selected_folder != NULL);
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (con_man));

	camel_imapx_extd_conn_manager_conn_update_select (is,
	                                                  selected_folder,
	                                                  con_man);
}

static CamelIMAPXServer*
kolab_imapx_conn_manager_new_connection_unlocked (CamelIMAPXConnManager *con_man,
                                                  const gchar *folder_name,
                                                  GCancellable *cancellable,
                                                  GError **error)
{
	/* modified dupe of imapx_conn_manager_get_connection() */

	CamelKolabIMAPXServer *ks = NULL;
	CamelIMAPXServer *is = NULL;
	CamelIMAPXStore *imapx_store = NULL;
	CamelStore *store = con_man->priv->store;
	CamelService *service = NULL;
	struct _ConnectionInfo *cinfo = NULL;
	gboolean success = FALSE;

	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (con_man));
	/* folder_name may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	/* Caller must be holding CON_WRITE_LOCK. */

	service = CAMEL_SERVICE (store);

	imapx_store = CAMEL_IMAPX_STORE (store);

	camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);

	/* Check if we got cancelled while we were waiting. */
	if (g_cancellable_set_error_if_cancelled (cancellable, error))
		goto exit;

	ks = camel_kolab_imapx_server_new (CAMEL_KOLAB_IMAPX_STORE (store));
	is = CAMEL_IMAPX_SERVER (ks);

	/* XXX As part of the connect operation the CamelIMAPXServer will
	 *     have to call camel_session_authenticate_sync(), but it has
	 *     no way to pass itself through in that call so the service
	 *     knows which CamelIMAPXServer is trying to authenticate.
	 *
	 *     IMAPX is the only provider that does multiple connections
	 *     like this, so I didn't want to pollute the CamelSession and
	 *     CamelService authentication APIs with an extra argument.
	 *     Instead we do this little hack so the service knows which
	 *     CamelIMAPXServer to act on in its authenticate_sync() method.
	 *
	 *     Because we're holding the CAMEL_SERVICE_REC_CONNECT_LOCK
	 *     we should not have multiple IMAPX connections trying to
	 *     authenticate at once, so this should be thread-safe.
	 */
	imapx_store->authenticating_server = g_object_ref (is);
	success = camel_imapx_extd_server_connect (is, cancellable, error);
	g_object_unref (imapx_store->authenticating_server);
	imapx_store->authenticating_server = NULL;

	if (!success) {
		g_object_unref (ks);
		is = NULL;
		goto exit;
	}

	g_signal_connect (ks, "shutdown",
	                  G_CALLBACK (kolab_imapx_conn_manager_conn_shutdown), con_man);
	g_signal_connect (ks, "select_changed",
	                  G_CALLBACK (kolab_imapx_conn_manager_conn_update_select), con_man);

	cinfo = camel_imapx_conn_manager_connection_info_new (is);

	if (folder_name != NULL)
		camel_imapx_conn_manager_connection_info_insert_folder_name (cinfo, folder_name);

	/* Takes ownership of the ConnectionInfo. */
	con_man->priv->connections = g_list_prepend (con_man->priv->connections, cinfo);

	c(is->tagprefix, "Created new connection for %s and total connections %d \n", folder_name, g_list_length (con_man->priv->connections));

 exit:
	camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);

	return is;
}

/*----------------------------------------------------------------------------*/
/* class functions */

static CamelStore*
kolab_imapx_conn_manager_get_store (CamelIMAPXConnManager *self)
{
	CamelStore *store = NULL;

	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self));

	store = camel_imapx_extd_conn_manager_get_store (self);
	g_assert (CAMEL_IS_KOLAB_IMAPX_STORE (store));

	return store;
}

static CamelIMAPXServer*
kolab_imapx_conn_manager_get_connection (CamelIMAPXConnManager *self,
                                         const gchar *foldername,
                                         GCancellable *cancellable,
                                         GError **err)
{
	/* modified dupe of function in parent class */

	CamelIMAPXServer *is = NULL;

	g_return_val_if_fail (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self), NULL);
	/* foldername may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	/* Hold the writer lock while we requisition a CamelIMAPXServer
	 * to prevent other threads from adding or removing connections. */
	CON_WRITE_LOCK (self);

	/* Check if we got cancelled while waiting for the lock. */
	if (!g_cancellable_set_error_if_cancelled (cancellable, err)) {
		is = camel_imapx_conn_manager_find_connection_unlocked (self, foldername);
		if (is == NULL)
			is = kolab_imapx_conn_manager_new_connection_unlocked (self,
			                                                       foldername,
			                                                       cancellable,
			                                                       err);
	}

	CON_WRITE_UNLOCK (self);

	return is;
}

static void
kolab_imapx_conn_manager_close_connections (CamelIMAPXConnManager *self)
{
	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self));

	camel_imapx_extd_conn_manager_close_connections (self);
}

static GList*
kolab_imapx_conn_manager_get_connections (CamelIMAPXConnManager *self)
{
	GList *cn = NULL;

	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self));

	cn = camel_imapx_extd_conn_manager_get_connections (self);

	/* TODO type-check list elements? */

	return cn;
}

static void
kolab_imapx_conn_manager_update_con_info (CamelIMAPXConnManager *self,
                                          CamelIMAPXServer *server,
                                          const gchar *foldername)
{
	g_assert (CAMEL_IS_KOLAB_IMAPX_CONN_MANAGER (self));
	g_assert (CAMEL_IS_KOLAB_IMAPX_SERVER (server));
	g_assert (foldername != NULL);

	camel_imapx_extd_conn_manager_update_con_info (self,
	                                               server,
	                                               foldername);
}

/*----------------------------------------------------------------------------*/
/* class init */

static void
camel_kolab_imapx_conn_manager_class_init (CamelKolabIMAPXConnManagerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GObjectClass *gobj_p_class = NULL;
	CamelIMAPXConnManagerClass *manager_class = CAMEL_IMAPX_CONN_MANAGER_CLASS (klass);
	CamelIMAPXExtdConnManagerClass *parent_class = CAMEL_IMAPX_EXTD_CONN_MANAGER_CLASS (klass);

	gobj_p_class = G_OBJECT_CLASS (camel_kolab_imapx_conn_manager_parent_class);
	object_class->set_property = gobj_p_class->set_property;
	object_class->get_property = gobj_p_class->get_property;

	object_class->dispose = camel_kolab_imapx_conn_manager_dispose;
	object_class->finalize = camel_kolab_imapx_conn_manager_finalize;

	manager_class->get_connection = kolab_imapx_conn_manager_get_connection;

	parent_class->get_store = kolab_imapx_conn_manager_get_store;
	parent_class->close_connections = kolab_imapx_conn_manager_close_connections;
	parent_class->get_connections = kolab_imapx_conn_manager_get_connections;
	parent_class->update_con_info = kolab_imapx_conn_manager_update_con_info;

	/* duped from parent */
	g_object_class_install_property (object_class,
	                                 PROP_STORE,
	                                 g_param_spec_object ("store",
	                                                      "Store",
	                                                      "The CamelStore to which we belong",
	                                                      CAMEL_TYPE_STORE,
	                                                      G_PARAM_READWRITE |
	                                                      G_PARAM_CONSTRUCT_ONLY |
	                                                      G_PARAM_STATIC_STRINGS));
}

/*----------------------------------------------------------------------------*/
/* API functions */

CamelKolabIMAPXConnManager*
camel_kolab_imapx_conn_manager_new (CamelKolabIMAPXStore *store)
{
	CamelKolabIMAPXConnManager *self = NULL;

	g_return_val_if_fail (CAMEL_IS_KOLAB_IMAPX_STORE (store), NULL);

	self = g_object_new (CAMEL_TYPE_KOLAB_IMAPX_CONN_MANAGER,
	                     "store",
	                     CAMEL_STORE (store),
	                     NULL);
	return self;
}

/*----------------------------------------------------------------------------*/
