/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            camel-imapx-extd-conn-manager.c
 *
 *  2011-12-20, 16:04:28
 *  Copyright 2011, 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-imapx-utils.h"

#include "camel-imapx-extd-conn-manager.h"
#include "camel-imapx-conn-manager-defs.h"
#include "camel-imapx-conn-manager-friend.h"

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

G_DEFINE_TYPE (CamelIMAPXExtdConnManager, camel_imapx_extd_conn_manager, CAMEL_TYPE_IMAPX_CONN_MANAGER)

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

static void
camel_imapx_extd_conn_manager_init (CamelIMAPXExtdConnManager *self)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));
}

static void
camel_imapx_extd_conn_manager_dispose (GObject *object)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (object));

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

static void
camel_imapx_extd_conn_manager_finalize (GObject *object)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (object));

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

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

static void
extd_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_conn_manager_conn_shutdown (is, con_man);
}

static void
extd_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_conn_manager_conn_update_select (is,
	                                             selected_folder,
	                                             con_man);
}

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

	CamelIMAPXExtdServer *es = 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_IMAPX_EXTD_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;

	es = camel_imapx_extd_server_new (CAMEL_IMAPX_EXTD_STORE (store));
	is = CAMEL_IMAPX_SERVER (es);

	/* 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 (es);
		is = NULL;
		goto exit;
	}

	g_signal_connect (es, "shutdown",
	                  G_CALLBACK (extd_conn_manager_conn_shutdown), con_man);
	g_signal_connect (es, "select_changed",
	                  G_CALLBACK (extd_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*
imapx_extd_conn_manager_get_store (CamelIMAPXConnManager *self)
{
	CamelStore *store = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));

	store = camel_imapx_conn_manager_get_store (self);
	g_assert (CAMEL_IS_IMAPX_EXTD_STORE (store));

	return store;
}

static CamelIMAPXServer*
imapx_extd_conn_manager_get_connection (CamelIMAPXConnManager *self,
                                        const gchar *foldername,
                                        GCancellable *cancellable,
                                        GError **err)
{
	/* modified dupe of imapx_conn_manager_get_connection() */

	CamelIMAPXServer *is = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_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 = extd_conn_manager_new_connection_unlocked (self,
			                                                foldername,
			                                                cancellable,
			                                                err);
	}

	CON_WRITE_UNLOCK (self);

	return is;
}

static void
imapx_extd_conn_manager_close_connections (CamelIMAPXConnManager *self)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));

	camel_imapx_conn_manager_close_connections (CAMEL_IMAPX_CONN_MANAGER (self));
}

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

	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));

	cn = camel_imapx_conn_manager_get_connections (self);

	/* TODO type-check list elements? */

	return cn;
}

static void
imapx_extd_conn_manager_update_con_info (CamelIMAPXConnManager *self,
                                         CamelIMAPXServer *server,
                                         const gchar *foldername)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (server));
	g_assert (foldername != NULL);

	camel_imapx_conn_manager_update_con_info (self,
	                                          server,
	                                          foldername);
}

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

static void
camel_imapx_extd_conn_manager_class_init (CamelIMAPXExtdConnManagerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GObjectClass *gobj_p_class = NULL;
	CamelIMAPXConnManagerClass *parent_class = CAMEL_IMAPX_CONN_MANAGER_CLASS (klass);

	gobj_p_class = G_OBJECT_CLASS (camel_imapx_extd_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_imapx_extd_conn_manager_dispose;
	object_class->finalize = camel_imapx_extd_conn_manager_finalize;

	parent_class->get_connection = imapx_extd_conn_manager_get_connection;

	klass->get_store = imapx_extd_conn_manager_get_store;
	klass->close_connections = imapx_extd_conn_manager_close_connections;
	klass->get_connections = imapx_extd_conn_manager_get_connections;
	klass->update_con_info = imapx_extd_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 */

CamelIMAPXExtdConnManager*
camel_imapx_extd_conn_manager_new (CamelIMAPXExtdStore *store)
{
	CamelIMAPXExtdConnManager *self = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_STORE (store), NULL);

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

CamelStore*
camel_imapx_extd_conn_manager_get_store (CamelIMAPXConnManager *self)
{
	CamelStore *store = NULL;
	CamelIMAPXExtdConnManagerClass *klass = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_CONN_MANAGER_GET_CLASS (self);
	store = klass->get_store (self);

	return store;
}

void
camel_imapx_extd_conn_manager_close_connections (CamelIMAPXConnManager *self)
{
	CamelIMAPXExtdConnManagerClass *klass = NULL;

	g_return_if_fail (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));

	klass = CAMEL_IMAPX_EXTD_CONN_MANAGER_GET_CLASS (self);
	klass->close_connections (self);
}

GList*
camel_imapx_extd_conn_manager_get_connections (CamelIMAPXConnManager *self)
{
	GList *cn = NULL;
	CamelIMAPXExtdConnManagerClass *klass = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_CONN_MANAGER_GET_CLASS (self);
	cn = klass->get_connections (self);

	return cn;
}

void
camel_imapx_extd_conn_manager_update_con_info (CamelIMAPXConnManager *self,
                                               CamelIMAPXServer *server,
                                               const gchar *foldername)
{
	CamelIMAPXExtdConnManagerClass *klass = NULL;

	g_return_if_fail (CAMEL_IS_IMAPX_EXTD_CONN_MANAGER (self));

	klass = CAMEL_IMAPX_EXTD_CONN_MANAGER_GET_CLASS (self);
	klass->update_con_info (self, server, foldername);
}

/*----------------------------------------------------------------------------*/
/* "friend" API */

#include "camel-imapx-extd-conn-manager-friend.h"

void
camel_imapx_extd_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));

	extd_conn_manager_conn_shutdown (is, con_man);
}

void
camel_imapx_extd_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));

	extd_conn_manager_conn_update_select (is,
	                                      selected_folder,
	                                      con_man);
}

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