/*
 * e-mail-store-utils.c
 *
 * 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.
 *
 * 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 Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "evolution-config.h"

#include "e-mail-folder-utils.h"
#include "e-mail-utils.h"

#include "e-mail-store-utils.h"

#include <glib/gi18n-lib.h>

typedef struct _AsyncContext AsyncContext;

struct _AsyncContext {
	gchar *full_name;
};

static void
async_context_free (AsyncContext *context)
{
	g_free (context->full_name);

	g_slice_free (AsyncContext, context);
}

gboolean
e_mail_store_create_folder_sync (CamelStore *store,
                                 const gchar *full_name,
                                 GCancellable *cancellable,
                                 GError **error)
{
	CamelFolderInfo *folder_info;
	gchar *copied_full_name;
	gchar *display_name;
	const gchar *parent;
	gboolean success = TRUE;

	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
	g_return_val_if_fail (full_name != NULL, FALSE);

	copied_full_name = g_strdup (full_name);
	display_name = strrchr (copied_full_name, '/');
	if (display_name == NULL) {
		display_name = copied_full_name;
		parent = "";
	} else {
		*display_name++ = '\0';
		parent = copied_full_name;
	}

	folder_info = camel_store_create_folder_sync (
		store, parent, display_name, cancellable, error);

	g_free (copied_full_name);

	if (folder_info == NULL)
		return FALSE;

	if (CAMEL_IS_SUBSCRIBABLE (store))
		success = camel_subscribable_subscribe_folder_sync (
			CAMEL_SUBSCRIBABLE (store),
			full_name, cancellable, error);

	camel_folder_info_free (folder_info);

	return success;
}

/* Helper for e_mail_store_create_folder() */
static void
mail_store_create_folder_thread (GSimpleAsyncResult *simple,
                                 GObject *source_object,
                                 GCancellable *cancellable)
{
	AsyncContext *context;
	GError *local_error = NULL;

	context = g_simple_async_result_get_op_res_gpointer (simple);

	e_mail_store_create_folder_sync (
		CAMEL_STORE (source_object),
		context->full_name,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

void
e_mail_store_create_folder (CamelStore *store,
                            const gchar *full_name,
                            gint io_priority,
                            GCancellable *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *context;

	g_return_if_fail (CAMEL_IS_STORE (store));
	g_return_if_fail (full_name != NULL);

	context = g_slice_new0 (AsyncContext);
	context->full_name = g_strdup (full_name);

	simple = g_simple_async_result_new (
		G_OBJECT (store), callback, user_data,
		e_mail_store_create_folder);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, mail_store_create_folder_thread,
		io_priority, cancellable);

	g_object_unref (simple);
}

gboolean
e_mail_store_create_folder_finish (CamelStore *store,
                                   GAsyncResult *result,
                                   GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (store),
		e_mail_store_create_folder), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/* Helper for e_mail_store_go_offline() */
static void
mail_store_go_offline_thread (GSimpleAsyncResult *simple,
                              GObject *source_object,
                              GCancellable *cancellable)
{
	GError *local_error = NULL;

	e_mail_store_go_offline_sync (
		CAMEL_STORE (source_object), cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

gboolean
e_mail_store_go_offline_sync (CamelStore *store,
                              GCancellable *cancellable,
                              GError **error)
{
	CamelService *service;
	const gchar *display_name;
	gboolean success = TRUE;

	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);

	service = CAMEL_SERVICE (store);

	display_name = camel_service_get_display_name (service);
	if (display_name == NULL || *display_name == '\0')
		display_name = G_OBJECT_TYPE_NAME (service);

	camel_operation_push_message (
		cancellable, _("Disconnecting from “%s”"), display_name);

	if (CAMEL_IS_OFFLINE_STORE (store)) {
		success = camel_offline_store_set_online_sync (
			CAMEL_OFFLINE_STORE (store),
			FALSE, cancellable, error);

	} else {
		success = camel_service_disconnect_sync (
			service, TRUE, cancellable, error);
	}

	camel_operation_pop_message (cancellable);

	return success;
}

void
e_mail_store_go_offline (CamelStore *store,
                         gint io_priority,
                         GCancellable *cancellable,
                         GAsyncReadyCallback callback,
                         gpointer user_data)
{
	GSimpleAsyncResult *simple;

	g_return_if_fail (CAMEL_IS_STORE (store));

	simple = g_simple_async_result_new (
		G_OBJECT (store), callback,
		user_data, e_mail_store_go_offline);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_run_in_thread (
		simple, mail_store_go_offline_thread,
		io_priority, cancellable);

	g_object_unref (simple);
}

gboolean
e_mail_store_go_offline_finish (CamelStore *store,
                                GAsyncResult *result,
                                GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (store), e_mail_store_go_offline), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

gboolean
e_mail_store_go_online_sync (CamelStore *store,
                             GCancellable *cancellable,
                             GError **error)
{
	CamelService *service;
	const gchar *display_name;
	gboolean success = TRUE;

	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);

	service = CAMEL_SERVICE (store);

	display_name = camel_service_get_display_name (service);
	if (display_name == NULL || *display_name == '\0')
		display_name = G_OBJECT_TYPE_NAME (service);

	camel_operation_push_message (
		cancellable, _("Reconnecting to “%s”"), display_name);

	if (CAMEL_IS_OFFLINE_STORE (store))
		success = camel_offline_store_set_online_sync (
			CAMEL_OFFLINE_STORE (store),
			TRUE, cancellable, error);

	camel_operation_pop_message (cancellable);

	return success;
}

/* Helper for e_mail_store_go_online() */
static void
mail_store_go_online_thread (GSimpleAsyncResult *simple,
                             GObject *source_object,
                             GCancellable *cancellable)
{
	GError *local_error = NULL;

	e_mail_store_go_online_sync (
		CAMEL_STORE (source_object), cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

void
e_mail_store_go_online (CamelStore *store,
                        gint io_priority,
                        GCancellable *cancellable,
                        GAsyncReadyCallback callback,
                        gpointer user_data)
{
	GSimpleAsyncResult *simple;

	g_return_if_fail (CAMEL_IS_STORE (store));

	simple = g_simple_async_result_new (
		G_OBJECT (store), callback,
		user_data, e_mail_store_go_online);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_run_in_thread (
		simple, mail_store_go_online_thread,
		io_priority, cancellable);

	g_object_unref (simple);
}

gboolean
e_mail_store_go_online_finish (CamelStore *store,
                               GAsyncResult *result,
                               GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (store), e_mail_store_go_online), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/* Helper for e_mail_store_prepare_for_offline() */
static void
mail_store_prepare_for_offline_thread (GSimpleAsyncResult *simple,
                                       GObject *source_object,
                                       GCancellable *cancellable)
{
	CamelService *service;
	const gchar *display_name;
	GError *local_error = NULL;

	service = CAMEL_SERVICE (source_object);

	display_name = camel_service_get_display_name (service);
	if (display_name == NULL || *display_name == '\0')
		display_name = G_OBJECT_TYPE_NAME (service);

	camel_operation_push_message (
		cancellable, _("Preparing account “%s” for offline"),
		display_name);

	if (CAMEL_IS_OFFLINE_STORE (service))
		camel_offline_store_prepare_for_offline_sync (
			CAMEL_OFFLINE_STORE (service),
			cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);

	camel_operation_pop_message (cancellable);
}

void
e_mail_store_prepare_for_offline (CamelStore *store,
                                  gint io_priority,
                                  GCancellable *cancellable,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
	GSimpleAsyncResult *simple;

	g_return_if_fail (CAMEL_IS_STORE (store));

	simple = g_simple_async_result_new (
		G_OBJECT (store), callback, user_data,
		e_mail_store_prepare_for_offline);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_run_in_thread (
		simple, mail_store_prepare_for_offline_thread,
		io_priority, cancellable);

	g_object_unref (simple);
}

gboolean
e_mail_store_prepare_for_offline_finish (CamelStore *store,
                                         GAsyncResult *result,
                                         GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (store),
		e_mail_store_prepare_for_offline), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

static gboolean
mail_store_save_setup_key (CamelStore *store,
			   ESource *source,
			   const gchar *extension_name,
			   const gchar *property_name,
			   const gchar *type_id,
			   const gchar *value)
{
	gpointer extension;
	GObjectClass *klass;

	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
	if (source)
		g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
	g_return_val_if_fail (extension_name != NULL, FALSE);
	g_return_val_if_fail (property_name != NULL, FALSE);
	g_return_val_if_fail (value != NULL, FALSE);

	if (!source)
		return FALSE;

	extension = e_source_get_extension (source, extension_name);
	if (!extension) {
		g_warning ("%s: Cannot find extension '%s'", G_STRFUNC, extension_name);
		return FALSE;
	}

	klass = G_OBJECT_GET_CLASS (extension);
	g_return_val_if_fail (klass != NULL, FALSE);

	if (!g_object_class_find_property (klass, property_name)) {
		g_warning ("%s: Extension '%s' doesn't have property '%s'", G_STRFUNC, extension_name, property_name);
		return FALSE;
	}

	if (!type_id || g_str_equal (type_id, "s")) {
		g_object_set (extension, property_name, value, NULL);
	} else if (g_str_equal (type_id, "b")) {
		gboolean val;

		val = g_strcmp0 (value, "false") != 0 && g_strcmp0 (value, "0") != 0;

		g_object_set (extension, property_name, val, NULL);
	} else if (g_str_equal (type_id, "i")) {
		gint val;

		val = (gint) g_ascii_strtoll (value, NULL, 10);

		g_object_set (extension, property_name, val, NULL);
	} else if (g_str_equal (type_id, "f")) {
		gchar *folder_uri;

		folder_uri = e_mail_folder_uri_build (store, value);
		g_object_set (extension, property_name, folder_uri, NULL);
		g_free (folder_uri);
	} else {
		g_warning ("%s: Unknown type identifier '%s' provided", G_STRFUNC, type_id);
		return FALSE;
	}

	return TRUE;
}

gboolean
e_mail_store_save_initial_setup_sync (CamelStore *store,
				      GHashTable *save_setup,
				      ESource *collection_source,
				      ESource *account_source,
				      ESource *submission_source,
				      ESource *transport_source,
				      gboolean write_sources,
				      GCancellable *cancellable,
				      GError **error)
{
	gboolean collection_changed = FALSE;
	gboolean account_changed = FALSE;
	gboolean submission_changed = FALSE;
	gboolean transport_changed = FALSE;
	gboolean success = TRUE;
	GHashTableIter iter;
	gpointer key, value;

	g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
	g_return_val_if_fail (save_setup != NULL, FALSE);
	g_return_val_if_fail (E_IS_SOURCE (account_source), FALSE);

	if (!g_hash_table_size (save_setup))
		return TRUE;

	/* The key name consists of up to four parts: Source:Extension:Property[:Type]
	   Source can be 'Collection', 'Account', 'Submission', 'Transport', 'Backend'
	   Extension is any extension name; it's up to the key creator to make sure
	   the extension belongs to that particular Source.
	   Property is a property name in the Extension.
	   Type is an optional letter describing the type of the value; if not set, then
	   string is used. Available values are: 'b' for boolean, 'i' for integer,
	   's' for string, 'f' for folder full path.
	   All the part values are case sensitive.
	*/

	g_hash_table_iter_init (&iter, save_setup);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		gchar **keys;

		keys = g_strsplit (key, ":", -1);
		if (g_strv_length (keys) < 3 || g_strv_length (keys) > 4) {
			g_warning ("%s: Incorrect store setup key, expects 3 or 4 parts, but %d given in '%s'",
				G_STRFUNC, g_strv_length (keys), (const gchar *) key);
		} else if (g_str_equal (keys[0], "Collection")) {
			if (mail_store_save_setup_key (store, collection_source, keys[1], keys[2], keys[3], value))
				collection_changed = TRUE;
		} else if (g_str_equal (keys[0], "Account")) {
			if (mail_store_save_setup_key (store, account_source, keys[1], keys[2], keys[3], value))
				account_changed = TRUE;
		} else if (g_str_equal (keys[0], "Submission")) {
			if (mail_store_save_setup_key (store, submission_source, keys[1], keys[2], keys[3], value))
				submission_changed = TRUE;
		} else if (g_str_equal (keys[0], "Transport")) {
			if (mail_store_save_setup_key (store, transport_source, keys[1], keys[2], keys[3], value))
				transport_changed = TRUE;
		} else if (g_str_equal (keys[0], "Backend")) {
			ESource *backend_source = NULL;

			if (collection_source && e_source_has_extension (collection_source, keys[1]))
				backend_source = collection_source;
			else if (account_source && e_source_has_extension (account_source, keys[1]))
				backend_source = account_source;

			if (mail_store_save_setup_key (store, backend_source, keys[1], keys[2], keys[3], value))
				transport_changed = TRUE;
		} else {
			g_warning ("%s: Unknown source name '%s' given in '%s'", G_STRFUNC, keys[0], (const gchar *) key);
		}
	}

	if (write_sources) {
		if (transport_changed && success && e_source_get_writable (transport_source))
			success = e_source_write_sync (transport_source, cancellable, error);
		if (submission_changed && success && e_source_get_writable (submission_source))
			success = e_source_write_sync (submission_source, cancellable, error);
		if (account_changed && success && e_source_get_writable (account_source))
			success = e_source_write_sync (account_source, cancellable, error);
		if (collection_changed && success && e_source_get_writable (collection_source))
			success = e_source_write_sync (collection_source, cancellable, error);
	}

	return success;
}
