/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 * 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/>.
 *
 *
 * Authors:
 *		Jeffrey Stedfast <fejj@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#include "evolution-config.h"

#include <locale.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include <e-util/e-util.h>

#include <libemail-engine/libemail-engine.h>

#include <em-format/e-mail-parser.h>
#include <em-format/e-mail-part-utils.h>
#include <em-format/e-mail-formatter-quote.h>

#include <shell/e-shell.h>

#include <composer/e-msg-composer.h>
#include <composer/e-composer-actions.h>
#include <composer/e-composer-post-header.h>

#include "e-mail-printer.h"
#include "e-mail-ui-session.h"
#include "e-mail-templates.h"
#include "e-mail-templates-store.h"
#include "em-utils.h"
#include "em-composer-utils.h"
#include "em-folder-selector.h"
#include "em-folder-tree.h"
#include "em-event.h"
#include "mail-send-recv.h"

#ifdef G_OS_WIN32
#ifdef gmtime_r
#undef gmtime_r
#endif
#ifdef localtime_r
#undef localtime_r
#endif

/* The gmtime() and localtime() in Microsoft's C library are MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
#endif

typedef struct _AsyncContext AsyncContext;
typedef struct _ForwardData ForwardData;

struct _AsyncContext {
	CamelMimeMessage *message;
	EMailSession *session;
	EMsgComposer *composer;
	EActivity *activity;
	gchar *folder_uri;
	gchar *message_uid;
	gulong num_loading_handler_id;
	gulong cancelled_handler_id;
};

struct _ForwardData {
	EShell *shell;
	CamelFolder *folder;
	GPtrArray *uids;
	EMailForwardStyle style;
};

static void
async_context_free (AsyncContext *async_context)
{
	if (async_context->cancelled_handler_id) {
		GCancellable *cancellable;

		cancellable = e_activity_get_cancellable (async_context->activity);
		/* Cannot use g_cancellable_disconnect(), because when this is called
		   from inside the cancelled handler, then the GCancellable deadlocks. */
		g_signal_handler_disconnect (cancellable, async_context->cancelled_handler_id);
		async_context->cancelled_handler_id = 0;
	}

	if (async_context->num_loading_handler_id) {
		EAttachmentView *view;
		EAttachmentStore *store;

		view = e_msg_composer_get_attachment_view (async_context->composer);
		store = e_attachment_view_get_store (view);

		e_signal_disconnect_notify_handler (store, &async_context->num_loading_handler_id);
	}

	g_clear_object (&async_context->message);
	g_clear_object (&async_context->session);
	g_clear_object (&async_context->composer);
	g_clear_object (&async_context->activity);

	g_free (async_context->folder_uri);
	g_free (async_context->message_uid);

	g_slice_free (AsyncContext, async_context);
}

static void
forward_data_free (ForwardData *data)
{
	if (data->shell != NULL)
		g_object_unref (data->shell);

	if (data->folder != NULL)
		g_object_unref (data->folder);

	if (data->uids != NULL)
		g_ptr_array_unref (data->uids);

	g_slice_free (ForwardData, data);
}

static gboolean
ask_confirm_for_unwanted_html_mail (EMsgComposer *composer,
                                    EDestination **recipients)
{
	gboolean res;
	GString *str;
	gint i;

	str = g_string_new ("");
	for (i = 0; recipients[i] != NULL; ++i) {
		if (!e_destination_get_html_mail_pref (recipients[i])) {
			const gchar *name;

			name = e_destination_get_textrep (recipients[i], FALSE);

			g_string_append_printf (str, "     %s\n", name);
		}
	}

	if (str->len)
		res = e_util_prompt_user (
			GTK_WINDOW (composer),
			"org.gnome.evolution.mail", "prompt-on-unwanted-html",
			"mail:ask-send-html", str->str, NULL);
	else
		res = TRUE;

	g_string_free (str, TRUE);

	return res;
}

static gboolean
ask_confirm_for_empty_subject (EMsgComposer *composer)
{
	return e_util_prompt_user (
		GTK_WINDOW (composer),
		"org.gnome.evolution.mail",
		"prompt-on-empty-subject",
		"mail:ask-send-no-subject", NULL);
}

static gboolean
ask_confirm_for_only_bcc (EMsgComposer *composer,
                          gboolean hidden_list_case)
{
	/* If the user is mailing a hidden contact list, it is possible for
	 * them to create a message with only Bcc recipients without really
	 * realizing it.  To try to avoid being totally confusing, I've changed
	 * this dialog to provide slightly different text in that case, to
	 * better explain what the hell is going on. */

	return e_util_prompt_user (
		GTK_WINDOW (composer),
		"org.gnome.evolution.mail",
		"prompt-on-only-bcc",
		hidden_list_case ?
		"mail:ask-send-only-bcc-contact" :
		"mail:ask-send-only-bcc", NULL);
}

static gboolean
is_group_definition (const gchar *str)
{
	const gchar *colon;

	if (!str || !*str)
		return FALSE;

	colon = strchr (str, ':');
	return colon > str && strchr (str, ';') > colon;
}

static gboolean
composer_presend_check_recipients (EMsgComposer *composer,
                                   EMailSession *session)
{
	EDestination **recipients;
	EDestination **recipients_bcc;
	CamelInternetAddress *cia;
	EComposerHeaderTable *table;
	EComposerHeader *post_to_header;
	GString *invalid_addrs = NULL;
	GSettings *settings;
	gboolean check_passed = FALSE;
	gint hidden = 0;
	gint shown = 0;
	gint num = 0;
	gint num_to_cc = 0;
	gint num_bcc = 0;
	gint num_post = 0;
	gint ii;

	/* We should do all of the validity checks based on the composer,
	 * and not on the created message, as extra interaction may occur
	 * when we get the message (e.g. passphrase to sign a message). */

	table = e_msg_composer_get_header_table (composer);

	recipients = e_composer_header_table_get_destinations_to (table);
	if (recipients) {
		for (ii = 0; recipients[ii] != NULL; ii++) {
			const gchar *addr;

			addr = e_destination_get_address (recipients[ii]);
			if (addr == NULL || *addr == '\0')
				continue;

			num_to_cc++;
		}

		e_destination_freev (recipients);
	}

	recipients = e_composer_header_table_get_destinations_cc (table);
	if (recipients) {
		for (ii = 0; recipients[ii] != NULL; ii++) {
			const gchar *addr;

			addr = e_destination_get_address (recipients[ii]);
			if (addr == NULL || *addr == '\0')
				continue;

			num_to_cc++;
		}

		e_destination_freev (recipients);
	}

	recipients = e_composer_header_table_get_destinations (table);

	cia = camel_internet_address_new ();

	/* See which ones are visible, present, etc. */
	for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) {
		const gchar *addr;
		gint len, j;

		addr = e_destination_get_address (recipients[ii]);
		if (addr == NULL || *addr == '\0')
			continue;

		camel_address_decode (CAMEL_ADDRESS (cia), addr);
		len = camel_address_length (CAMEL_ADDRESS (cia));

		if (len > 0) {
			if (!e_destination_is_evolution_list (recipients[ii])) {
				for (j = 0; j < len; j++) {
					const gchar *name = NULL, *eml = NULL;

					if (!camel_internet_address_get (cia, j, &name, &eml) ||
					    !eml ||
					    strchr (eml, '@') <= eml) {
						if (!invalid_addrs)
							invalid_addrs = g_string_new ("");
						else
							g_string_append (invalid_addrs, ", ");

						if (name)
							g_string_append (invalid_addrs, name);
						if (eml) {
							g_string_append (invalid_addrs, name ? " <" : "");
							g_string_append (invalid_addrs, eml);
							g_string_append (invalid_addrs, name ? ">" : "");
						}
					}
				}
			}

			camel_address_remove (CAMEL_ADDRESS (cia), -1);
			num++;
			if (e_destination_is_evolution_list (recipients[ii])
			    && !e_destination_list_show_addresses (recipients[ii])) {
				hidden++;
			} else {
				shown++;
			}
		} else if (is_group_definition (addr)) {
			/* like an address, it will not claim on only-bcc */
			shown++;
			num++;
		} else if (!invalid_addrs) {
			invalid_addrs = g_string_new (addr);
		} else {
			g_string_append (invalid_addrs, ", ");
			g_string_append (invalid_addrs, addr);
		}
	}

	recipients_bcc = e_composer_header_table_get_destinations_bcc (table);
	if (recipients_bcc) {
		for (ii = 0; recipients_bcc[ii] != NULL; ii++) {
			const gchar *addr;

			addr = e_destination_get_address (recipients_bcc[ii]);
			if (addr == NULL || *addr == '\0')
				continue;

			camel_address_decode (CAMEL_ADDRESS (cia), addr);
			if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) {
				camel_address_remove (CAMEL_ADDRESS (cia), -1);
				num_bcc++;
			}
		}

		e_destination_freev (recipients_bcc);
	}

	g_object_unref (cia);

	post_to_header = e_composer_header_table_get_header (
		table, E_COMPOSER_HEADER_POST_TO);
	if (e_composer_header_get_visible (post_to_header)) {
		GList *postlist;

		postlist = e_composer_header_table_get_post_to (table);
		num_post = g_list_length (postlist);
		g_list_foreach (postlist, (GFunc) g_free, NULL);
		g_list_free (postlist);
	}

	/* I'm sensing a lack of love, er, I mean recipients. */
	if (num == 0 && num_post == 0) {
		EHTMLEditor *editor;

		editor = e_msg_composer_get_editor (composer);
		e_alert_submit (E_ALERT_SINK (editor), "mail:send-no-recipients", NULL);

		goto finished;
	}

	if (invalid_addrs) {
		if (!e_util_prompt_user (
			GTK_WINDOW (composer),
			"org.gnome.evolution.mail",
			"prompt-on-invalid-recip",
			strstr (invalid_addrs->str, ", ") ?
				"mail:ask-send-invalid-recip-multi" :
				"mail:ask-send-invalid-recip-one",
			invalid_addrs->str, NULL)) {
			g_string_free (invalid_addrs, TRUE);
			goto finished;
		}

		g_string_free (invalid_addrs, TRUE);
	}

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	if (num_to_cc > 1 && num_to_cc >= g_settings_get_int (settings, "composer-many-to-cc-recips-num")) {
		gchar *head;
		gchar *msg;

		g_clear_object (&settings);

		head = g_strdup_printf (ngettext (
			/* Translators: The %d is replaced with the actual count of recipients, which is always more than one. */
			"Are you sure you want to send a message with %d To and CC recipients?",
			"Are you sure you want to send a message with %d To and CC recipients?",
			num_to_cc), num_to_cc);

		msg = g_strdup_printf (ngettext (
			/* Translators: The %d is replaced with the actual count of recipients, which is always more than one. */
			"You are trying to send a message to %d recipients in To and CC fields."
			" This would result in all recipients seeing the email addresses of each"
			" other. In some cases this behaviour is undesired, especially if they"
			" do not know each other or if privacy is a concern. Consider adding"
			" recipients to the BCC field instead.",
			"You are trying to send a message to %d recipients in To and CC fields."
			" This would result in all recipients seeing the email addresses of each"
			" other. In some cases this behaviour is undesired, especially if they"
			" do not know each other or if privacy is a concern. Consider adding"
			" recipients to the BCC field instead.",
			num_to_cc), num_to_cc);

		if (!e_util_prompt_user (
			GTK_WINDOW (composer),
			"org.gnome.evolution.mail",
			"prompt-on-many-to-cc-recips",
			"mail:ask-many-to-cc-recips",
			head, msg, NULL)) {
			GtkAction *action;

			g_free (head);
			g_free (msg);

			action = E_COMPOSER_ACTION_VIEW_BCC (composer);
			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);

			goto finished;
		}

		g_free (head);
		g_free (msg);
	}
	g_clear_object (&settings);

	if (num > 0 && (num == num_bcc || shown == 0)) {
		/* this means that the only recipients are Bcc's */
		if (!ask_confirm_for_only_bcc (composer, shown == 0))
			goto finished;
	}

	check_passed = TRUE;

finished:
	if (recipients != NULL)
		e_destination_freev (recipients);

	return check_passed;
}

static gboolean
composer_presend_check_identity (EMsgComposer *composer,
                                 EMailSession *session)
{
	EComposerHeaderTable *table;
	ESource *source = NULL;
	gchar *uid;
	gboolean success = TRUE;

	table = e_msg_composer_get_header_table (composer);

	uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
	if (uid)
		source = e_composer_header_table_ref_source (table, uid);
	g_free (uid);

	if (source) {
		EClientCache *client_cache;
		ESourceRegistry *registry;

		client_cache = e_composer_header_table_ref_client_cache (table);
		registry = e_client_cache_ref_registry (client_cache);

		success = e_source_registry_check_enabled (registry, source);
		if (!success) {
			e_alert_submit (
				E_ALERT_SINK (e_msg_composer_get_editor (composer)),
				"mail:send-no-account-enabled", NULL);
		}

		g_object_unref (client_cache);
		g_object_unref (registry);
	} else {
		success = FALSE;
		e_alert_submit (
			E_ALERT_SINK (e_msg_composer_get_editor (composer)),
			"mail:send-no-account", NULL);
	}


	g_clear_object (&source);

	return success;
}

static gboolean
composer_presend_check_plugins (EMsgComposer *composer,
                                EMailSession *session)
{
	EMEvent *eme;
	EMEventTargetComposer *target;
	gpointer data;

	/** @Event: composer.presendchecks
	 * @Title: Composer PreSend Checks
	 * @Target: EMEventTargetMessage
	 *
	 * composer.presendchecks is emitted during pre-checks for the
	 * message just before sending.  Since the e-plugin framework
	 * doesn't provide a way to return a value from the plugin,
	 * use 'presend_check_status' to set whether the check passed.
	 */
	eme = em_event_peek ();
	target = em_event_target_new_composer (eme, composer, 0);

	e_event_emit (
		(EEvent *) eme, "composer.presendchecks",
		(EEventTarget *) target);

	/* A non-NULL value for this key means the check failed. */
	data = g_object_get_data (G_OBJECT (composer), "presend_check_status");

	/* Clear the value in case we have to run these checks again. */
	g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL);

	return (data == NULL);
}

static gboolean
composer_presend_check_subject (EMsgComposer *composer,
                                EMailSession *session)
{
	EComposerHeaderTable *table;
	const gchar *subject;
	gboolean check_passed = TRUE;

	table = e_msg_composer_get_header_table (composer);
	subject = e_composer_header_table_get_subject (table);

	if (subject == NULL || subject[0] == '\0') {
		if (!ask_confirm_for_empty_subject (composer))
			check_passed = FALSE;
	}

	return check_passed;
}

static gboolean
composer_presend_check_unwanted_html (EMsgComposer *composer,
                                      EMailSession *session)
{
	EDestination **recipients;
	EHTMLEditor *editor;
	EContentEditor *cnt_editor;
	EComposerHeaderTable *table;
	GSettings *settings;
	gboolean check_passed = TRUE;
	gboolean html_mode;
	gboolean send_html;
	gboolean confirm_html;
	gint ii;

	settings = e_util_ref_settings ("org.gnome.evolution.mail");

	editor = e_msg_composer_get_editor (composer);
	cnt_editor = e_html_editor_get_content_editor (editor);
	html_mode = e_content_editor_get_html_mode (cnt_editor);

	table = e_msg_composer_get_header_table (composer);
	recipients = e_composer_header_table_get_destinations (table);

	send_html = g_settings_get_boolean (settings, "composer-send-html");
	confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");

	/* Only show this warning if our default is to send html.  If it
	 * isn't, we've manually switched into html mode in the composer
	 * and (presumably) had a good reason for doing this. */
	if (html_mode && send_html && confirm_html && recipients != NULL) {
		gboolean html_problem = FALSE;

		for (ii = 0; recipients[ii] != NULL; ii++) {
			if (!e_destination_get_html_mail_pref (recipients[ii])) {
				html_problem = TRUE;
				break;
			}
		}

		if (html_problem) {
			if (!ask_confirm_for_unwanted_html_mail (
				composer, recipients))
				check_passed = FALSE;
		}
	}

	if (recipients != NULL)
		e_destination_freev (recipients);

	g_object_unref (settings);

	return check_passed;
}

static void
composer_send_completed (GObject *source_object,
                         GAsyncResult *result,
                         gpointer user_data)
{
	EActivity *activity;
	gboolean service_unavailable;
	gboolean set_changed = FALSE;
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = (AsyncContext *) user_data;

	activity = async_context->activity;

	e_mail_session_send_to_finish (
		E_MAIL_SESSION (source_object), result, &local_error);

	if (e_activity_handle_cancellation (activity, local_error)) {
		set_changed = TRUE;
		goto exit;
	}

	/* Check for error codes which may indicate we're offline
	 * or name resolution failed or connection attempt failed. */
	service_unavailable =
		g_error_matches (
			local_error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_UNAVAILABLE) ||
		/* name resolution failed */
		g_error_matches (
			local_error, G_RESOLVER_ERROR,
			G_RESOLVER_ERROR_NOT_FOUND) ||
		g_error_matches (
			local_error, G_RESOLVER_ERROR,
			G_RESOLVER_ERROR_TEMPORARY_FAILURE) ||
		/* something internal to Camel failed */
		g_error_matches (
			local_error, CAMEL_SERVICE_ERROR,
			CAMEL_SERVICE_ERROR_URL_INVALID);
	if (service_unavailable) {
		/* Inform the user. */
		e_alert_run_dialog_for_args (
			GTK_WINDOW (async_context->composer),
			"mail-composer:saving-to-outbox", NULL);
		if (async_context->message)
			g_signal_emit_by_name (
				async_context->composer, "save-to-outbox",
				async_context->message, activity);
		else
			e_msg_composer_save_to_outbox (async_context->composer);
		goto exit;
	}

	/* Post-processing errors are shown in the shell window. */
	if (g_error_matches (
		local_error, E_MAIL_ERROR,
		E_MAIL_ERROR_POST_PROCESSING)) {
		EAlert *alert;
		EShell *shell;

		shell = e_msg_composer_get_shell (async_context->composer);

		alert = e_alert_new (
			"mail-composer:send-post-processing-error",
			local_error->message, NULL);
		e_shell_submit_alert (shell, alert);
		g_object_unref (alert);

	/* All other errors are shown in the composer window. */
	} else if (local_error != NULL) {
		gint response;

		/* Clear the activity bar before
		 * presenting the error dialog. */
		g_clear_object (&async_context->activity);
		async_context->activity = e_html_editor_new_activity (e_msg_composer_get_editor (async_context->composer));
		activity = async_context->activity;

		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (async_context->composer),
			"mail-composer:send-error",
			local_error->message, NULL);
		if (response == GTK_RESPONSE_OK)  /* Try Again */
			e_msg_composer_send (async_context->composer);
		if (response == GTK_RESPONSE_ACCEPT) { /* Save to Outbox */
			if (async_context->message)
				g_signal_emit_by_name (
					async_context->composer, "save-to-outbox",
					async_context->message, activity);
			else
				e_msg_composer_save_to_outbox (async_context->composer);
		}
		set_changed = TRUE;
		goto exit;
	}

	e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (activity), (GWeakNotify)
		gtk_widget_destroy, async_context->composer);

exit:
	g_clear_error (&local_error);

	if (set_changed) {
		EHTMLEditor *editor;
		EContentEditor *cnt_editor;

		editor = e_msg_composer_get_editor (async_context->composer);
		cnt_editor = e_html_editor_get_content_editor (editor);

		e_content_editor_set_changed (cnt_editor, TRUE);

		gtk_window_present (GTK_WINDOW (async_context->composer));
	}

	async_context_free (async_context);
}

static void
em_utils_composer_real_send (EMsgComposer *composer,
			     CamelMimeMessage *message,
			     EActivity *activity,
			     EMailSession *session)
{
	AsyncContext *async_context;
	GCancellable *cancellable;
	GSettings *settings;

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	if (g_settings_get_boolean (settings, "composer-use-outbox")) {
		/* Using e_msg_composer_save_to_outbox() means building
		   the message again; better to use the built message here. */
		g_signal_emit_by_name (
			composer, "save-to-outbox",
			message, activity);

		g_object_unref (settings);
		return;
	}

	g_object_unref (settings);

	if (!camel_session_get_online (CAMEL_SESSION (session))) {
		e_alert_run_dialog_for_args (
			GTK_WINDOW (composer),
			"mail-composer:saving-to-outbox", NULL);

		/* Using e_msg_composer_save_to_outbox() means building
		   the message again; better to use the built message here. */
		g_signal_emit_by_name (
			composer, "save-to-outbox",
			message, activity);

		return;
	}

	async_context = g_slice_new0 (AsyncContext);
	async_context->message = g_object_ref (message);
	async_context->composer = g_object_ref (composer);
	async_context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);

	e_mail_session_send_to (
		session, message,
		G_PRIORITY_DEFAULT,
		cancellable, NULL, NULL,
		composer_send_completed,
		async_context);
}

static void
composer_num_loading_notify_cb (EAttachmentStore *store,
				GParamSpec *param,
				AsyncContext *async_context)
{
	if (e_attachment_store_get_num_loading (store) > 0)
		return;

	em_utils_composer_real_send (
		async_context->composer,
		async_context->message,
		async_context->activity,
		async_context->session);

	async_context_free (async_context);
}

static void
composer_wait_for_attachment_load_cancelled_cb (GCancellable *cancellable,
						AsyncContext *async_context)
{
	async_context_free (async_context);
}

static void
em_utils_composer_send_cb (EMsgComposer *composer,
                           CamelMimeMessage *message,
                           EActivity *activity,
                           EMailSession *session)
{
	AsyncContext *async_context;
	GCancellable *cancellable;
	EAttachmentView *view;
	EAttachmentStore *store;

	view = e_msg_composer_get_attachment_view (composer);
	store = e_attachment_view_get_store (view);

	if (e_attachment_store_get_num_loading (store) <= 0) {
		em_utils_composer_real_send (composer, message, activity, session);
		return;
	}

	async_context = g_slice_new0 (AsyncContext);
	async_context->session = g_object_ref (session);
	async_context->message = g_object_ref (message);
	async_context->composer = g_object_ref (composer);
	async_context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);
	/* This message is never removed from the camel operation, otherwise the GtkInfoBar
	   hides itself and the user sees no feedback. */
	camel_operation_push_message (cancellable, "%s", _("Waiting for attachments to load…"));

	async_context->num_loading_handler_id = e_signal_connect_notify (store, "notify::num-loading",
		G_CALLBACK (composer_num_loading_notify_cb), async_context);
	/* Cannot use g_cancellable_connect() here, see async_context_free() */
	async_context->cancelled_handler_id = g_signal_connect (cancellable, "cancelled",
		G_CALLBACK (composer_wait_for_attachment_load_cancelled_cb), async_context);
}

static void
composer_set_no_change (EMsgComposer *composer)
{
	EHTMLEditor *editor;
	EContentEditor *cnt_editor;

	g_return_if_fail (composer != NULL);

	editor = e_msg_composer_get_editor (composer);
	cnt_editor = e_html_editor_get_content_editor (editor);

	e_content_editor_set_changed (cnt_editor, FALSE);
}

/* delete original messages from Outbox folder */
static void
manage_x_evolution_replace_outbox (EMsgComposer *composer,
                                   EMailSession *session,
                                   CamelMimeMessage *message,
                                   GCancellable *cancellable)
{
	const gchar *message_uid;
	const gchar *header;
	CamelFolder *outbox;

	g_return_if_fail (composer != NULL);
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	header = "X-Evolution-Replace-Outbox-UID";
	message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header);
	e_msg_composer_remove_header (composer, header);

	if (!message_uid)
		return;

	outbox = e_mail_session_get_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX);
	g_return_if_fail (outbox != NULL);

	camel_folder_set_message_flags (
		outbox, message_uid,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);

	/* ignore errors here */
	camel_folder_synchronize_message_sync (
		outbox, message_uid, cancellable, NULL);
}

static void
composer_save_to_drafts_complete (GObject *source_object,
                                  GAsyncResult *result,
                                  gpointer user_data)
{
	EActivity *activity;
	AsyncContext *async_context;
	EHTMLEditor *editor;
	EContentEditor *cnt_editor;
	GError *local_error = NULL;

	async_context = (AsyncContext *) user_data;

	editor = e_msg_composer_get_editor (async_context->composer);
	cnt_editor = e_html_editor_get_content_editor (editor);

	/* We don't really care if this failed.  If something other than
	 * cancellation happened, emit a runtime warning so the error is
	 * not completely lost. */
	e_mail_session_handle_draft_headers_finish (
		E_MAIL_SESSION (source_object), result, &local_error);

	activity = async_context->activity;

	if (e_activity_handle_cancellation (activity, local_error)) {
		e_content_editor_set_changed (cnt_editor, TRUE);
		g_error_free (local_error);

	} else if (local_error != NULL) {
		e_content_editor_set_changed (cnt_editor, TRUE);
		g_warning ("%s", local_error->message);
		g_error_free (local_error);
	} else
		e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

	/* Encode the draft message we just saved into the EMsgComposer
	 * as X-Evolution-Draft headers.  The message will be marked for
	 * deletion if the user saves a newer draft message or sends the
	 * composed message. */
	e_msg_composer_set_draft_headers (
		async_context->composer,
		async_context->folder_uri,
		async_context->message_uid);

	e_content_editor_set_changed (cnt_editor, FALSE);

	async_context_free (async_context);
}

static void
composer_save_to_drafts_append_mail (AsyncContext *async_context,
                                     CamelFolder *drafts_folder);

static void
composer_save_to_drafts_cleanup (GObject *source_object,
                                 GAsyncResult *result,
                                 gpointer user_data)
{
	CamelSession *session;
	EActivity *activity;
	EAlertSink *alert_sink;
	GCancellable *cancellable;
	EHTMLEditor *editor;
	EContentEditor *cnt_editor;
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = (AsyncContext *) user_data;

	editor = e_msg_composer_get_editor (async_context->composer);
	cnt_editor = e_html_editor_get_content_editor (editor);

	activity = async_context->activity;
	alert_sink = e_activity_get_alert_sink (activity);
	cancellable = e_activity_get_cancellable (activity);

	e_mail_folder_append_message_finish (
		CAMEL_FOLDER (source_object), result,
		&async_context->message_uid, &local_error);

	if (e_activity_handle_cancellation (activity, local_error)) {
		g_warn_if_fail (async_context->message_uid == NULL);
		e_content_editor_set_changed (cnt_editor, TRUE);
		async_context_free (async_context);
		g_error_free (local_error);
		return;

	} else if (local_error != NULL) {
		g_warn_if_fail (async_context->message_uid == NULL);

		if (e_msg_composer_is_exiting (async_context->composer)) {
			gint response;

			/* If we can't retrieve the Drafts folder for the
			 * selected account, ask the user if he wants to
			 * save to the local Drafts folder instead. */
			response = e_alert_run_dialog_for_args (
				GTK_WINDOW (async_context->composer),
				"mail:ask-default-drafts", local_error->message, NULL);
			if (response != GTK_RESPONSE_YES) {
				e_content_editor_set_changed (cnt_editor, TRUE);
				async_context_free (async_context);
			} else {
				composer_save_to_drafts_append_mail (async_context, NULL);
			}

			g_error_free (local_error);
			return;
		}

		e_alert_submit (
			alert_sink,
			"mail-composer:save-to-drafts-error",
			local_error->message, NULL);
		e_content_editor_set_changed (cnt_editor, TRUE);
		async_context_free (async_context);
		g_error_free (local_error);
		return;
	}

	session = e_msg_composer_ref_session (async_context->composer);

	/* Mark the previously saved draft message for deletion.
	 * Note: This is just a nice-to-have; ignore failures. */
	e_mail_session_handle_draft_headers (
		E_MAIL_SESSION (session),
		async_context->message,
		G_PRIORITY_DEFAULT, cancellable,
		composer_save_to_drafts_complete,
		async_context);

	g_object_unref (session);
}

static void
composer_save_to_drafts_append_mail (AsyncContext *async_context,
                                     CamelFolder *drafts_folder)
{
	CamelFolder *local_drafts_folder;
	GCancellable *cancellable;
	CamelMessageInfo *info;

	local_drafts_folder =
		e_mail_session_get_local_folder (
		async_context->session, E_MAIL_LOCAL_FOLDER_DRAFTS);

	if (drafts_folder == NULL)
		drafts_folder = g_object_ref (local_drafts_folder);

	cancellable = e_activity_get_cancellable (async_context->activity);

	info = camel_message_info_new (NULL);

	camel_message_info_set_flags (info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN |
		(camel_mime_message_has_attachment (async_context->message) ? CAMEL_MESSAGE_ATTACHMENTS : 0), ~0);

	camel_medium_remove_header (
		CAMEL_MEDIUM (async_context->message),
		"X-Evolution-Replace-Outbox-UID");

	e_mail_folder_append_message (
		drafts_folder, async_context->message,
		info, G_PRIORITY_DEFAULT, cancellable,
		composer_save_to_drafts_cleanup,
		async_context);

	g_clear_object (&info);

	g_object_unref (drafts_folder);
}

static void
composer_save_to_drafts_got_folder (GObject *source_object,
                                    GAsyncResult *result,
                                    gpointer user_data)
{
	EActivity *activity;
	CamelFolder *drafts_folder;
	EHTMLEditor *editor;
	EContentEditor *cnt_editor;
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = (AsyncContext *) user_data;

	activity = async_context->activity;

	editor = e_msg_composer_get_editor (async_context->composer);
	cnt_editor = e_html_editor_get_content_editor (editor);

	drafts_folder = e_mail_session_uri_to_folder_finish (
		E_MAIL_SESSION (source_object), result, &local_error);

	/* Sanity check. */
	g_return_if_fail (
		((drafts_folder != NULL) && (local_error == NULL)) ||
		((drafts_folder == NULL) && (local_error != NULL)));

	if (e_activity_handle_cancellation (activity, local_error)) {
		e_content_editor_set_changed (cnt_editor, TRUE);
		async_context_free (async_context);
		g_error_free (local_error);
		return;

	} else if (local_error != NULL) {
		gint response;

		/* If we can't retrieve the Drafts folder for the
		 * selected account, ask the user if he wants to
		 * save to the local Drafts folder instead. */
		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (async_context->composer),
			"mail:ask-default-drafts", local_error->message, NULL);

		g_error_free (local_error);

		if (response != GTK_RESPONSE_YES) {
			e_content_editor_set_changed (cnt_editor, TRUE);
			async_context_free (async_context);
			return;
		}
	}

	composer_save_to_drafts_append_mail (async_context, drafts_folder);
}

static void
em_utils_composer_save_to_drafts_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
                                     EActivity *activity,
                                     EMailSession *session)
{
	AsyncContext *async_context;
	EComposerHeaderTable *table;
	ESource *source;
	const gchar *local_drafts_folder_uri;
	gchar *identity_uid;
	gchar *drafts_folder_uri = NULL;

	async_context = g_slice_new0 (AsyncContext);
	async_context->message = g_object_ref (message);
	async_context->session = g_object_ref (session);
	async_context->composer = g_object_ref (composer);
	async_context->activity = g_object_ref (activity);

	table = e_msg_composer_get_header_table (composer);

	identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
	source = e_composer_header_table_ref_source (table, identity_uid);

	/* Get the selected identity's preferred Drafts folder. */
	if (source != NULL) {
		ESourceMailComposition *extension;
		const gchar *extension_name;
		gchar *uri;

		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
		extension = e_source_get_extension (source, extension_name);
		uri = e_source_mail_composition_dup_drafts_folder (extension);

		drafts_folder_uri = uri;

		g_object_unref (source);
	}

	local_drafts_folder_uri =
		e_mail_session_get_local_folder_uri (
		session, E_MAIL_LOCAL_FOLDER_DRAFTS);

	if (drafts_folder_uri == NULL) {
		async_context->folder_uri = g_strdup (local_drafts_folder_uri);
		composer_save_to_drafts_append_mail (async_context, NULL);
	} else {
		GCancellable *cancellable;

		cancellable = e_activity_get_cancellable (activity);
		async_context->folder_uri = g_strdup (drafts_folder_uri);

		e_mail_session_uri_to_folder (
			session, drafts_folder_uri, 0,
			G_PRIORITY_DEFAULT, cancellable,
			composer_save_to_drafts_got_folder,
			async_context);

		g_free (drafts_folder_uri);
	}

	g_free (identity_uid);
}

static void
emcu_manage_flush_outbox (EMailSession *session)
{
	GSettings *settings;

	g_return_if_fail (E_IS_MAIL_SESSION (session));

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	if (g_settings_get_boolean (settings, "composer-use-outbox")) {
		gint delay_flush = g_settings_get_int (settings, "composer-delay-outbox-flush");

		if (delay_flush == 0) {
			e_mail_session_flush_outbox (session);
		} else if (delay_flush > 0) {
			e_mail_session_schedule_outbox_flush (session, delay_flush);
		}
	}
	g_object_unref (settings);
}

static void
composer_save_to_outbox_completed (GObject *source_object,
                                   GAsyncResult *result,
                                   gpointer user_data)
{
	EMailSession *session;
	EActivity *activity;
	EAlertSink *alert_sink;
	GCancellable *cancellable;
	AsyncContext *async_context;
	GError *local_error = NULL;

	session = E_MAIL_SESSION (source_object);
	async_context = (AsyncContext *) user_data;

	activity = async_context->activity;
	alert_sink = e_activity_get_alert_sink (activity);
	cancellable = e_activity_get_cancellable (activity);

	e_mail_session_append_to_local_folder_finish (
		session, result, NULL, &local_error);

	if (e_activity_handle_cancellation (activity, local_error)) {
		g_error_free (local_error);
		goto exit;

	} else if (local_error != NULL) {
		e_alert_submit (
			alert_sink,
			"mail-composer:append-to-outbox-error",
			local_error->message, NULL);
		g_error_free (local_error);
		goto exit;
	}

	/* special processing for Outbox folder */
	manage_x_evolution_replace_outbox (
		async_context->composer,
		session,
		async_context->message,
		cancellable);

	e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (activity), (GWeakNotify)
		gtk_widget_destroy, async_context->composer);

	emcu_manage_flush_outbox (session);

exit:
	async_context_free (async_context);
}

static void
em_utils_composer_save_to_outbox_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
                                     EActivity *activity,
                                     EMailSession *session)
{
	AsyncContext *async_context;
	CamelMessageInfo *info;
	GCancellable *cancellable;

	async_context = g_slice_new0 (AsyncContext);
	async_context->message = g_object_ref (message);
	async_context->composer = g_object_ref (composer);
	async_context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);

	info = camel_message_info_new (NULL);
	camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);

	e_mail_session_append_to_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX,
		message, info, G_PRIORITY_DEFAULT, cancellable,
		composer_save_to_outbox_completed,
		async_context);

	g_clear_object (&info);
}

typedef struct _PrintAsyncContext {
	GMainLoop *main_loop;
	GError *error;
} PrintAsyncContext;

static void
em_composer_utils_print_done_cb (GObject *source_object,
				 GAsyncResult *result,
				 gpointer user_data)
{
	PrintAsyncContext *async_context = user_data;

	g_return_if_fail (E_IS_MAIL_PRINTER (source_object));
	g_return_if_fail (async_context != NULL);
	g_return_if_fail (async_context->main_loop != NULL);

	e_mail_printer_print_finish (E_MAIL_PRINTER (source_object), result, &(async_context->error));

	g_main_loop_quit (async_context->main_loop);
}

static void
em_utils_composer_print_cb (EMsgComposer *composer,
                            GtkPrintOperationAction action,
                            CamelMimeMessage *message,
                            EActivity *activity,
                            EMailSession *session)
{
	EMailParser *parser;
	EMailPartList *parts, *reserved_parts;
	EMailPrinter *printer;
	EMailBackend *mail_backend;
	const gchar *message_id;
	GCancellable *cancellable;
	CamelObjectBag *parts_registry;
	gchar *mail_uri;
	PrintAsyncContext async_context;

	mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (e_msg_composer_get_shell (composer), "mail"));
	g_return_if_fail (mail_backend != NULL);

	cancellable = e_activity_get_cancellable (activity);
	parser = e_mail_parser_new (CAMEL_SESSION (session));

	message_id = camel_mime_message_get_message_id (message);
	parts = e_mail_parser_parse_sync (parser, NULL, message_id, message, cancellable);
	if (!parts) {
		g_clear_object (&parser);
		return;
	}

	parts_registry = e_mail_part_list_get_registry ();

	mail_uri = e_mail_part_build_uri (NULL, message_id, NULL, NULL);
	reserved_parts = camel_object_bag_reserve (parts_registry, mail_uri);
	g_clear_object (&reserved_parts);

	camel_object_bag_add (parts_registry, mail_uri, parts);

	printer = e_mail_printer_new (parts, e_mail_backend_get_remote_content (mail_backend));

	async_context.error = NULL;
	async_context.main_loop = g_main_loop_new (NULL, FALSE);

	/* Cannot use EAsyncClosure here, it blocks the main context, which is not good here. */
	e_mail_printer_print (printer, action, NULL, cancellable, em_composer_utils_print_done_cb, &async_context);

	g_main_loop_run (async_context.main_loop);

	camel_object_bag_remove (parts_registry, parts);
	g_main_loop_unref (async_context.main_loop);
	g_object_unref (printer);
	g_object_unref (parts);
	g_free (mail_uri);

	if (e_activity_handle_cancellation (activity, async_context.error)) {
		g_error_free (async_context.error);
	} else if (async_context.error != NULL) {
		e_alert_submit (
			e_activity_get_alert_sink (activity),
			"mail-composer:no-build-message",
			async_context.error->message, NULL);
		g_error_free (async_context.error);
	}
}

static gint
compare_sources_with_uids_order_cb (gconstpointer a,
                                    gconstpointer b,
                                    gpointer user_data)
{
	ESource *asource = (ESource *) a;
	ESource *bsource = (ESource *) b;
	GHashTable *uids_order = user_data;
	gint aindex, bindex;

	aindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (asource)));
	bindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (bsource)));

	if (aindex <= 0)
		aindex = g_hash_table_size (uids_order);
	if (bindex <= 0)
		bindex = g_hash_table_size (uids_order);

	return aindex - bindex;
}

static void
sort_sources_by_ui (GList **psources,
                    gpointer user_data)
{
	EShell *shell = user_data;
	EShellBackend *shell_backend;
	EMailSession *mail_session;
	EMailAccountStore *account_store;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GHashTable *uids_order;
	gint index = 0;

	g_return_if_fail (psources != NULL);
	g_return_if_fail (E_IS_SHELL (shell));

	/* nothing to sort */
	if (!*psources || !g_list_next (*psources))
		return;

	shell_backend = e_shell_get_backend_by_name (shell, "mail");
	g_return_if_fail (shell_backend != NULL);

	mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
	g_return_if_fail (mail_session != NULL);

	account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session));
	g_return_if_fail (account_store != NULL);

	model = GTK_TREE_MODEL (account_store);
	if (!gtk_tree_model_get_iter_first (model, &iter))
		return;

	uids_order = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

	do {
		CamelService *service = NULL;

		gtk_tree_model_get (model, &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &service, -1);

		if (service) {
			index++;
			g_hash_table_insert (uids_order, g_strdup (camel_service_get_uid (service)), GINT_TO_POINTER (index));
			g_object_unref (service);
		}
	} while (gtk_tree_model_iter_next (model, &iter));

	*psources = g_list_sort_with_data (*psources, compare_sources_with_uids_order_cb, uids_order);

	g_hash_table_destroy (uids_order);
}

/* (trasfer full) */
ESource *
em_composer_utils_guess_identity_source (EShell *shell,
					 CamelMimeMessage *message,
					 CamelFolder *folder,
					 const gchar *message_uid,
					 gchar **out_identity_name,
					 gchar **out_identity_address)
{
	ESource *source;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);

	/* Check send account override for the passed-in folder */
	source = em_utils_check_send_account_override (shell, message, folder, out_identity_name, out_identity_address);

	/* If not set and it's a search folder, then check the original folder */
	if (!source && message_uid && CAMEL_IS_VEE_FOLDER (folder)) {
		CamelMessageInfo *mi = camel_folder_get_message_info (folder, message_uid);
		if (mi) {
			CamelFolder *location;

			location = camel_vee_folder_get_location (CAMEL_VEE_FOLDER (folder), (CamelVeeMessageInfo *) mi, NULL);
			if (location)
				source = em_utils_check_send_account_override (shell, message, location, out_identity_name, out_identity_address);
			g_clear_object (&mi);
		}
	}

	/* If no send account override, then guess */
	if (!source) {
		source = em_utils_guess_mail_identity_with_recipients_and_sort (e_shell_get_registry (shell),
			message, folder, message_uid, out_identity_name, out_identity_address, sort_sources_by_ui, shell);
	}

	return source;
}

/* Composing messages... */

static CamelMimeMessage *em_utils_get_composer_recipients_as_message (EMsgComposer *composer);

static void
set_up_new_composer (EMsgComposer *composer,
		     const gchar *subject,
		     CamelFolder *folder,
		     CamelMimeMessage *message,
		     const gchar *message_uid,
		     gboolean is_new_message)
{
	EClientCache *client_cache;
	ESourceRegistry *registry;
	EComposerHeaderTable *table;
	ESource *source = NULL;
	gchar *identity = NULL, *identity_name = NULL, *identity_address = NULL;

	table = e_msg_composer_get_header_table (composer);

	client_cache = e_composer_header_table_ref_client_cache (table);
	registry = e_client_cache_ref_registry (client_cache);

	if (folder != NULL) {
		gchar *folder_uri;
		GList *list;

		if (message) {
			g_object_ref (message);
		} else if (message_uid && !is_new_message) {
			/* Do this only if it's not a new message, in case the default account
			   has set "always CC/Bcc" address, which might match one of the configured
			   accounts, which in turn forces to use the default account due to this
			   read back from the composer. */
			message = em_utils_get_composer_recipients_as_message (composer);
		}

		if (message) {
			EShell *shell;

			shell = e_msg_composer_get_shell (composer);

			source = em_composer_utils_guess_identity_source (shell, message, folder, message_uid, &identity_name, &identity_address);
		}

		/* In case of search folder, try to guess the store from
		   the internal folders of it. If they are all from the same
		   store, then use that store. */
		if (!source && CAMEL_IS_VEE_FOLDER (folder)) {
			GHashTable *stores, *done_folders;
			GSList *todo;

			stores = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
			done_folders = g_hash_table_new (g_direct_hash, g_direct_equal);

			todo = g_slist_prepend (NULL, g_object_ref (folder));

			while (todo) {
				CamelVeeFolder *vfolder = todo->data;

				todo = g_slist_remove (todo, vfolder);
				if (!g_hash_table_contains (done_folders, vfolder)) {
					GList *folders, *llink;

					g_hash_table_insert (done_folders, vfolder, NULL);

					folders = camel_vee_folder_ref_folders (vfolder);
					for (llink = folders; llink; llink = g_list_next (llink)) {
						CamelFolder *subfolder = llink->data;

						if (!g_hash_table_contains (done_folders, subfolder)) {
							if (CAMEL_IS_VEE_FOLDER (subfolder)) {
								todo = g_slist_prepend (todo, g_object_ref (subfolder));
							} else {
								CamelStore *store = camel_folder_get_parent_store (subfolder);

								g_hash_table_insert (done_folders, subfolder, NULL);

								if (store) {
									g_hash_table_insert (stores, g_object_ref (store), NULL);

									if (g_hash_table_size (stores) > 1) {
										g_slist_free_full (todo, g_object_unref);
										todo = NULL;
										break;
									}
								}
							}
						}
					}

					g_list_free_full (folders, g_object_unref);
				}

				g_object_unref (vfolder);
			}

			if (g_hash_table_size (stores) == 1) {
				GHashTableIter iter;
				gpointer store;

				g_hash_table_iter_init (&iter, stores);
				if (g_hash_table_iter_next (&iter, &store, NULL) && store)
					source = em_utils_ref_mail_identity_for_store (registry, store);
			}

			g_slist_free_full (todo, g_object_unref);
			g_hash_table_destroy (done_folders);
			g_hash_table_destroy (stores);
		}

		g_clear_object (&message);

		if (!source) {
			CamelStore *store;

			store = camel_folder_get_parent_store (folder);
			source = em_utils_ref_mail_identity_for_store (registry, store);
		}

		folder_uri = e_mail_folder_uri_from_folder (folder);

		list = g_list_prepend (NULL, folder_uri);
		e_composer_header_table_set_post_to_list (table, list);
		g_list_free (list);

		g_free (folder_uri);
	}

	if (source != NULL) {
		identity = e_source_dup_uid (source);
		g_object_unref (source);
	}

	if (subject)
		e_composer_header_table_set_subject (table, subject);
	e_composer_header_table_set_identity_uid (table, identity, identity_name, identity_address);

	em_utils_apply_send_account_override_to_composer (composer, folder);

	g_free (identity);
	g_free (identity_name);
	g_free (identity_address);

	g_object_unref (client_cache);
	g_object_unref (registry);
}

/**
 * em_utils_compose_new_message:
 * @composer: an #EMsgComposer
 * @folder: (nullable): a #CamelFolder, or %NULL
 *
 * Sets up a new @composer window.
 *
 * See: em_utils_compose_new_message_with_selection()
 *
 * Since: 3.22
 **/
void
em_utils_compose_new_message (EMsgComposer *composer,
                              CamelFolder *folder)
{
	em_utils_compose_new_message_with_selection (composer, folder, NULL);
}

/**
 * em_utils_compose_new_message_with_selection:
 * @composer: an #EMsgComposer
 * @folder: (nullable): a #CamelFolder, or %NULL
 * @message_uid: (nullable): a UID of the selected message, or %NULL
 *
 * Sets up a new @composer window, similar to em_utils_compose_new_message(),
 * but also tries to identify From account more precisely, when the @folder
 * is a search folder.
 *
 * Since: 3.28
 **/
void
em_utils_compose_new_message_with_selection (EMsgComposer *composer,
					     CamelFolder *folder,
					     const gchar *message_uid)
{
	g_return_if_fail (E_IS_MSG_COMPOSER (composer));

	if (folder)
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

	set_up_new_composer (composer, "", folder, NULL, message_uid, TRUE);
	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));
}

static CamelMimeMessage *
em_utils_get_composer_recipients_as_message (EMsgComposer *composer)
{
	CamelMimeMessage *message;
	EComposerHeaderTable *table;
	EComposerHeader *header;
	EDestination **destv;
	CamelInternetAddress *to_addr, *cc_addr, *bcc_addr, *dest_addr;
	const gchar *text_addr;
	gint ii;

	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);

	table = e_msg_composer_get_header_table (composer);
	header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_TO);

	if (!e_composer_header_get_visible (header))
		return NULL;

	message = camel_mime_message_new ();

	to_addr = camel_internet_address_new ();
	cc_addr = camel_internet_address_new ();
	bcc_addr = camel_internet_address_new ();

	/* To */
	dest_addr = to_addr;
	destv = e_composer_header_table_get_destinations_to (table);
	for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
		text_addr = e_destination_get_address (destv[ii]);
		if (text_addr && *text_addr) {
			if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
				camel_internet_address_add (dest_addr, "", text_addr);
		}
	}
	e_destination_freev (destv);

	/* CC */
	dest_addr = cc_addr;
	destv = e_composer_header_table_get_destinations_cc (table);
	for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
		text_addr = e_destination_get_address (destv[ii]);
		if (text_addr && *text_addr) {
			if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
				camel_internet_address_add (dest_addr, "", text_addr);
		}
	}
	e_destination_freev (destv);

	/* Bcc */
	dest_addr = bcc_addr;
	destv = e_composer_header_table_get_destinations_bcc (table);
	for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
		text_addr = e_destination_get_address (destv[ii]);
		if (text_addr && *text_addr) {
			if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
				camel_internet_address_add (dest_addr, "", text_addr);
		}
	}
	e_destination_freev (destv);

	if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0)
		camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_TO, to_addr);

	if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0)
		camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_CC, cc_addr);

	if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0)
		camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_BCC, bcc_addr);

	g_object_unref (to_addr);
	g_object_unref (cc_addr);
	g_object_unref (bcc_addr);

	return message;
}

typedef struct _CreateComposerData {
	CamelFolder *folder;
	const gchar *message_uid; /* In the Camel string pool */
	gchar *mailto;
} CreateComposerData;

static void
create_composer_data_free (gpointer ptr)
{
	CreateComposerData *ccd = ptr;

	if (ccd) {
		g_clear_object (&ccd->folder);
		camel_pstring_free (ccd->message_uid);
		g_free (ccd->mailto);
		g_slice_free (CreateComposerData, ccd);
	}
}

static void
msg_composer_created_with_mailto_cb (GObject *source_object,
				     GAsyncResult *result,
				     gpointer user_data)
{
	CreateComposerData *ccd = user_data;
	EMsgComposer *composer;
	EComposerHeaderTable *table;
	EClientCache *client_cache;
	ESourceRegistry *registry;
	GError *error = NULL;

	g_return_if_fail (ccd != NULL);

	composer = e_msg_composer_new_finish (result, &error);
	if (error) {
		g_warning ("%s: Failed to create msg composer: %s", G_STRFUNC, error->message);
		g_clear_error (&error);
		create_composer_data_free (ccd);

		return;
	}

	if (ccd->mailto)
		e_msg_composer_setup_from_url (composer, ccd->mailto);

	set_up_new_composer (composer, NULL, ccd->folder, NULL, ccd->message_uid, TRUE);

	table = e_msg_composer_get_header_table (composer);

	client_cache = e_composer_header_table_ref_client_cache (table);
	registry = e_client_cache_ref_registry (client_cache);

	composer_set_no_change (composer);

	/* If a CamelFolder was given, we need to backtrack and find
	 * the corresponding ESource with a Mail Identity extension. */

	if (ccd->folder) {
		ESource *source;
		CamelStore *store;

		store = camel_folder_get_parent_store (ccd->folder);
		source = em_utils_ref_mail_identity_for_store (registry, store);

		if (source != NULL) {
			const gchar *uid = e_source_get_uid (source);
			e_composer_header_table_set_identity_uid (table, uid, NULL, NULL);
			g_object_unref (source);
		}
	}

	g_object_unref (client_cache);
	g_object_unref (registry);

	gtk_window_present (GTK_WINDOW (composer));

	create_composer_data_free (ccd);
}

/**
 * em_utils_compose_new_message_with_mailto:
 * @shell: an #EShell
 * @mailto: a mailto URL
 * @folder: a #CamelFolder, or %NULL
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window. If @mailto is non-NULL, the composer fields will be filled in
 * according to the values in the mailto URL.
 *
 * See: em_utils_compose_new_message_with_mailto_and_selection()
 **/
void
em_utils_compose_new_message_with_mailto (EShell *shell,
                                          const gchar *mailto,
                                          CamelFolder *folder)
{
	em_utils_compose_new_message_with_mailto_and_selection (shell, mailto, folder, NULL);
}

/**
 * em_utils_compose_new_message_with_mailto_and_selection:
 * @shell: an #EShell
 * @mailto: a mailto URL
 * @folder: a #CamelFolder, or %NULL
 * @message_uid: (nullable): a UID of the selected message, or %NULL
 *
 * similarly to em_utils_compose_new_message_with_mailto(), opens a new composer
 * window as a child window of @parent's toplevel window. If @mailto is non-NULL,
 * the composer fields will be filled in according to the values in the mailto URL.
 * It also tries to identify From account more precisely, when the @folder
 * is a search folder.
 *
 * Since: 3.28
 **/
void
em_utils_compose_new_message_with_mailto_and_selection (EShell *shell,
							const gchar *mailto,
							CamelFolder *folder,
							const gchar *message_uid)
{
	CreateComposerData *ccd;

	g_return_if_fail (E_IS_SHELL (shell));

	if (folder)
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

	ccd = g_slice_new0 (CreateComposerData);
	ccd->folder = folder ? g_object_ref (folder) : NULL;
	ccd->message_uid = camel_pstring_strdup (message_uid);
	ccd->mailto = g_strdup (mailto);

	e_msg_composer_new (shell, msg_composer_created_with_mailto_cb, ccd);
}

static ESource *
emcu_ref_identity_source_from_composer (EMsgComposer *composer)
{
	EComposerHeaderTable *table;
	ESource *source = NULL;
	gchar *identity_uid;

	if (!composer)
		return NULL;

	table = e_msg_composer_get_header_table (composer);
	identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
	if (identity_uid)
		source = e_composer_header_table_ref_source (table, identity_uid);
	g_free (identity_uid);

	return source;
}

static void
emcu_change_locale (const gchar *lc_messages,
		    const gchar *lc_time,
		    gchar **out_lc_messages,
		    gchar **out_lc_time)
{
	gboolean success;
	gchar *previous;

	if (lc_messages) {
		#if defined(LC_MESSAGES)
		previous = g_strdup (setlocale (LC_MESSAGES, NULL));
		success = setlocale (LC_MESSAGES, lc_messages) != NULL;
		#else
		previous = g_strdup (setlocale (LC_ALL, NULL));
		success = setlocale (LC_ALL, lc_messages) != NULL;
		#endif

		if (out_lc_messages)
			*out_lc_messages = success ? g_strdup (previous) : NULL;

		g_free (previous);
	}

	if (lc_time) {
		#if defined(LC_TIME)
		previous = g_strdup (setlocale (LC_TIME, NULL));
		success = setlocale (LC_TIME, lc_time) != NULL;
		#elif defined(LC_MESSAGES)
		previous = g_strdup (setlocale (LC_ALL, NULL));
		success = setlocale (LC_ALL, lc_time) != NULL;
		#else
		previous = NULL;
		success = FALSE;
		#endif

		if (out_lc_time)
			*out_lc_time = success ? g_strdup (previous) : NULL;

		g_free (previous);
	}
}

static void
emcu_prepare_attribution_locale (ESource *identity_source,
				 gchar **out_lc_messages,
				 gchar **out_lc_time)
{
	gchar *lang = NULL;

	g_return_if_fail (out_lc_messages != NULL);
	g_return_if_fail (out_lc_time != NULL);

	if (identity_source && e_source_has_extension (identity_source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
		ESourceMailComposition *extension;

		extension = e_source_get_extension (identity_source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
		lang = e_source_mail_composition_dup_language (extension);
	}

	if (!lang || !*lang) {
		GSettings *settings;

		g_free (lang);

		settings = e_util_ref_settings ("org.gnome.evolution.mail");
		lang = g_settings_get_string (settings, "composer-attribution-language");
		g_object_unref (settings);

		if (!lang || !*lang)
			g_clear_pointer (&lang, g_free);
	}

	if (!lang) {
		/* Set the locale always, even when using the user interface
		   language, because gettext() can return wrong text (in the previously
		   used language) */
		#if defined(LC_MESSAGES)
		lang = g_strdup (setlocale (LC_MESSAGES, NULL));
		#else
		lang = g_strdup (setlocale (LC_ALL, NULL));
		#endif
	}

	if (lang) {
		if (!strchr (lang, '.')) {
			gchar *tmp;

			tmp = g_strconcat (lang, ".utf8", NULL);
			g_free (lang);
			lang = tmp;
		}

		emcu_change_locale (lang, lang, out_lc_messages, out_lc_time);

		g_free (lang);
	}
}

/* Takes ownership (frees) the 'restore_locale' */
static void
emcu_restore_locale_after_attribution (gchar *restore_lc_messages,
				       gchar *restore_lc_time)
{
	emcu_change_locale (restore_lc_messages, restore_lc_time, NULL, NULL);

	g_free (restore_lc_messages);
	g_free (restore_lc_time);
}

/* Editing messages... */

typedef enum {
	QUOTING_ATTRIBUTION,
	QUOTING_FORWARD,
	QUOTING_ORIGINAL
} QuotingTextEnum;

static struct {
	const gchar * conf_key;
	const gchar * message;
} conf_messages[] = {
	[QUOTING_ATTRIBUTION] =
		{ "composer-message-attribution",
		/* Note to translators: this is the attribution string used
		 * when quoting messages.  Each ${Variable} gets replaced
		 * with a value.  To see a full list of available variables,
		 * see mail/em-composer-utils.c:attribvars array. */
		  N_("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at "
		     "${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:")
		},

	[QUOTING_FORWARD] =
		{ "composer-message-forward",
		  N_("-------- Forwarded Message --------")
		},

	[QUOTING_ORIGINAL] =
		{ "composer-message-original",
		  N_("-----Original Message-----")
		}
};

static gchar *
quoting_text (QuotingTextEnum type,
	      EMsgComposer *composer)
{
	GSettings *settings;
	gchar *restore_lc_messages = NULL, *restore_lc_time = NULL;
	gchar *text;

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	text = g_settings_get_string (settings, conf_messages[type].conf_key);
	g_object_unref (settings);

	if (text && *text)
		return text;

	g_free (text);

	if (composer) {
		ESource *identity_source;

		identity_source = emcu_ref_identity_source_from_composer (composer);

		emcu_prepare_attribution_locale (identity_source, &restore_lc_messages, &restore_lc_time);

		g_clear_object (&identity_source);
	}

	text = g_strdup (_(conf_messages[type].message));

	emcu_restore_locale_after_attribution (restore_lc_messages, restore_lc_time);

	return text;
}

/**
 * em_composer_utils_get_forward_marker:
 * @composer: an #EMsgComposer from which to get the identity information
 *
 * Returns: (transfer full): a text marker which is used for inline forwarded messages.
 *   Free returned pointer with g_free(), when no longer needed.
 *
 * Since: 3.24
 **/
gchar *
em_composer_utils_get_forward_marker (EMsgComposer *composer)
{
	return quoting_text (QUOTING_FORWARD, composer);
}

/**
 * em_composer_utils_get_original_marker:
 * @composer: an #EMsgComposer from which to get the identity information
 *
 * Returns: (transfer full): a text marker which is used for inline message replies.
 *   Free returned pointer with g_free(), when no longer needed.
 *
 * Since: 3.24
 **/
gchar *
em_composer_utils_get_original_marker (EMsgComposer *composer)
{
	return quoting_text (QUOTING_ORIGINAL, composer);
}

static gboolean
emcu_message_references_existing_account (CamelMimeMessage *message,
					  EMsgComposer *composer)
{
	ESource *source;
	gchar *identity_uid;
	gboolean res = FALSE;

	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);

	identity_uid = (gchar *) camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Identity");
	if (!identity_uid) {
		/* for backward compatibility */
		identity_uid = (gchar *) camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Account");
	}

	if (!identity_uid)
		return FALSE;

	identity_uid = g_strstrip (g_strdup (identity_uid));
	source = e_composer_header_table_ref_source (e_msg_composer_get_header_table (composer), identity_uid);

	res = source != NULL;

	g_clear_object (&source);
	g_free (identity_uid);

	return res;
}

typedef struct _OutboxData {
	CamelSession *session;
	CamelMessageInfo *info;
} OutboxData;

static void
outbox_data_free (gpointer ptr)
{
	OutboxData *od = ptr;

	if (od) {
		if (od->info) {
			g_object_set_data (G_OBJECT (od->info), MAIL_USER_KEY_EDITING, NULL);

			if (od->session && !(camel_message_info_get_flags (od->info) & CAMEL_MESSAGE_DELETED)) {
				emcu_manage_flush_outbox (E_MAIL_SESSION (od->session));
			}
		}

		g_clear_object (&od->session);
		g_clear_object (&od->info);
		g_free (od);
	}
}

/**
 * em_utils_edit_message:
 * @composer: an #EMsgComposer
 * @folder: a #CamelFolder
 * @message: a #CamelMimeMessage
 * @message_uid: UID of @message, or %NULL
 *
 * Sets up the @composer with the headers/mime-parts/etc of the @message.
 *
 * Since: 3.22
 **/
void
em_utils_edit_message (EMsgComposer *composer,
                       CamelFolder *folder,
                       CamelMimeMessage *message,
                       const gchar *message_uid,
                       gboolean keep_signature)
{
	ESourceRegistry *registry;
	ESource *source;
	gboolean folder_is_sent;
	gboolean folder_is_drafts;
	gboolean folder_is_outbox;
	gboolean folder_is_templates;
	gchar *override_identity_uid = NULL, *override_alias_name = NULL, *override_alias_address = NULL;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
	if (folder)
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

	registry = e_shell_get_registry (e_msg_composer_get_shell (composer));

	if (folder) {
		folder_is_sent = em_utils_folder_is_sent (registry, folder);
		folder_is_drafts = em_utils_folder_is_drafts (registry, folder);
		folder_is_outbox = em_utils_folder_is_outbox (registry, folder);
		folder_is_templates = em_utils_folder_is_templates (registry, folder);
	} else {
		folder_is_sent = FALSE;
		folder_is_drafts = FALSE;
		folder_is_outbox = FALSE;
		folder_is_templates = FALSE;
	}
	if (folder) {
		if ((!folder_is_sent && !folder_is_drafts && !folder_is_outbox && !folder_is_templates) ||
		    (!folder_is_outbox && !folder_is_templates && !emcu_message_references_existing_account (message, composer))) {
			CamelStore *store;

			store = camel_folder_get_parent_store (folder);
			source = em_utils_ref_mail_identity_for_store (registry, store);

			if (source) {
				g_free (override_identity_uid);
				override_identity_uid = e_source_dup_uid (source);
				g_object_unref (source);
			}
		}

		source = em_utils_check_send_account_override (e_msg_composer_get_shell (composer), message, folder, &override_alias_name, &override_alias_address);
		if (source) {
			g_free (override_identity_uid);
			override_identity_uid = e_source_dup_uid (source);
			g_object_unref (source);
		}
	}

	/* Remember the source message headers when re-editing in Drafts or Outbox,
	   thus Reply, Forward and such mark the source message properly. Do this
	   before the setup_with_message(), because it modifies the message headers. */
	if (folder_is_drafts || folder_is_outbox) {
		CamelMedium *medium;
		const gchar *hdr_folder;
		const gchar *hdr_message;
		const gchar *hdr_flags;

		medium = CAMEL_MEDIUM (message);

		hdr_folder = camel_medium_get_header (medium, "X-Evolution-Source-Folder");
		hdr_message = camel_medium_get_header (medium, "X-Evolution-Source-Message");
		hdr_flags = camel_medium_get_header (medium, "X-Evolution-Source-Flags");

		if (hdr_folder && hdr_message && hdr_flags) {
			e_msg_composer_set_header (composer, "X-Evolution-Source-Folder", hdr_folder);
			e_msg_composer_set_header (composer, "X-Evolution-Source-Message", hdr_message);
			e_msg_composer_set_header (composer, "X-Evolution-Source-Flags", hdr_flags);
		}
	}

	e_msg_composer_setup_with_message (composer, message, keep_signature, override_identity_uid, override_alias_name, override_alias_address, NULL);

	g_free (override_identity_uid);
	g_free (override_alias_name);
	g_free (override_alias_address);

	/* Override PostTo header only if the folder is a regular folder */
	if (folder && !folder_is_sent && !folder_is_drafts && !folder_is_outbox && !folder_is_templates) {
		EComposerHeaderTable *table;
		gchar *folder_uri;
		GList *list;

		table = e_msg_composer_get_header_table (composer);

		folder_uri = e_mail_folder_uri_from_folder (folder);

		list = g_list_prepend (NULL, folder_uri);
		e_composer_header_table_set_post_to_list (table, list);
		g_list_free (list);

		g_free (folder_uri);
	}

	e_msg_composer_remove_header (
		composer, "X-Evolution-Replace-Outbox-UID");

	if (message_uid != NULL && folder_is_drafts && folder) {
		gchar *folder_uri;

		folder_uri = e_mail_folder_uri_from_folder (folder);

		e_msg_composer_set_draft_headers (
			composer, folder_uri, message_uid);

		g_free (folder_uri);

	} else if (message_uid != NULL && folder_is_outbox) {
		CamelMessageInfo *info;

		e_msg_composer_set_header (
			composer, "X-Evolution-Replace-Outbox-UID",
			message_uid);

		info = camel_folder_get_message_info (folder, message_uid);
		if (info) {
			OutboxData *od;

			/* This makes the message not to send it while it's being edited */
			g_object_set_data (G_OBJECT (info), MAIL_USER_KEY_EDITING, GINT_TO_POINTER (1));

			od = g_new0 (OutboxData, 1);
			od->session = e_msg_composer_ref_session (composer);
			od->info = info; /* takes ownership of it */

			g_object_set_data_full (G_OBJECT (composer), MAIL_USER_KEY_EDITING, od, outbox_data_free);
		}
	}

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));
}

static void
emu_update_composers_security (EMsgComposer *composer,
                               guint32 validity_found)
{
	GtkAction *action;
	GSettings *settings;
	gboolean sign_reply;

	g_return_if_fail (composer != NULL);

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	sign_reply = (validity_found & E_MAIL_PART_VALIDITY_SIGNED) != 0 &&
		     g_settings_get_boolean (settings, "composer-sign-reply-if-signed");
	g_object_unref (settings);

	/* Pre-set only for encrypted messages, not for signed */
	if (sign_reply) {
		action = NULL;

		if (validity_found & E_MAIL_PART_VALIDITY_SMIME) {
			if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_SIGN (composer))) &&
			    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_ENCRYPT (composer))))
				action = E_COMPOSER_ACTION_SMIME_SIGN (composer);
		} else {
			if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_SIGN (composer))) &&
			    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_ENCRYPT (composer))))
				action = E_COMPOSER_ACTION_PGP_SIGN (composer);
		}

		if (action)
			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
	}

	if (validity_found & E_MAIL_PART_VALIDITY_ENCRYPTED) {
		action = NULL;

		if (validity_found & E_MAIL_PART_VALIDITY_SMIME) {
			if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_SIGN (composer))) &&
			    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_ENCRYPT (composer))))
				action = E_COMPOSER_ACTION_SMIME_ENCRYPT (composer);
		} else {
			if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_SIGN (composer))) &&
			    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_ENCRYPT (composer))))
				action = E_COMPOSER_ACTION_PGP_ENCRYPT (composer);
		}

		if (action)
			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
	}
}

void
em_utils_get_real_folder_uri_and_message_uid (CamelFolder *folder,
                                              const gchar *uid,
                                              gchar **folder_uri,
                                              gchar **message_uid)
{
	g_return_if_fail (folder != NULL);
	g_return_if_fail (uid != NULL);
	g_return_if_fail (folder_uri != NULL);
	g_return_if_fail (message_uid != NULL);

	em_utils_get_real_folder_and_message_uid (folder, uid, NULL, folder_uri, message_uid);
}

static void
emu_add_composer_references_from_message (EMsgComposer *composer,
					  CamelMimeMessage *message)
{
	const gchar *message_id_header;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	message_id_header = camel_mime_message_get_message_id (message);
	if (message_id_header && *message_id_header) {
		GString *references = g_string_new ("");
		gint ii = 0;
		const gchar *value;
		gchar *unfolded;

		while (value = e_msg_composer_get_header (composer, "References", ii), value) {
			ii++;

			if (references->len)
				g_string_append_c (references, ' ');
			g_string_append (references, value);
		}

		if (references->len)
			g_string_append_c (references, ' ');

		if (*message_id_header != '<')
			g_string_append_c (references, '<');

		g_string_append (references, message_id_header);

		if (*message_id_header != '<')
			g_string_append_c (references, '>');

		unfolded = camel_header_unfold (references->str);

		e_msg_composer_set_header (composer, "References", unfolded);

		g_string_free (references, TRUE);
		g_free (unfolded);
	}
}

static void
real_update_forwarded_flag (gpointer uid,
                            gpointer folder)
{
	if (uid && folder)
		camel_folder_set_message_flags (
			folder, uid, CAMEL_MESSAGE_FORWARDED,
			CAMEL_MESSAGE_FORWARDED);
}

static void
update_forwarded_flags_cb (EMsgComposer *composer,
			   CamelMimeMessage *message,
			   EActivity *activity,
                           ForwardData *data)
{
	if (data && data->uids && data->folder)
		g_ptr_array_foreach (
			data->uids, real_update_forwarded_flag, data->folder);
}

static void
emu_set_source_headers (EMsgComposer *composer,
			CamelFolder *folder,
			const gchar *message_uid,
			guint32 flags)
{
	gchar *source_folder_uri = NULL;
	gchar *source_message_uid = NULL;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));

	if (!folder || !message_uid)
		return;
	g_return_if_fail (CAMEL_IS_FOLDER (folder));

	em_utils_get_real_folder_uri_and_message_uid (folder, message_uid,
		&source_folder_uri, &source_message_uid);

	if (!source_message_uid)
		source_message_uid = g_strdup (message_uid);

	if (source_folder_uri && source_message_uid)
		e_msg_composer_set_source_headers (composer, source_folder_uri, source_message_uid, flags);

	g_free (source_folder_uri);
	g_free (source_message_uid);
}

static void
setup_forward_attached_callbacks (EMsgComposer *composer,
                                  CamelFolder *folder,
                                  GPtrArray *uids)
{
	ForwardData *data;

	if (!composer || !folder || !uids || !uids->len)
		return;

	if (uids->len == 1) {
		emu_set_source_headers (composer, folder, uids->pdata[0], CAMEL_MESSAGE_FORWARDED);
		return;
	}

	data = g_slice_new0 (ForwardData);
	data->folder = g_object_ref (folder);
	data->uids = g_ptr_array_ref (uids);

	g_signal_connect (
		composer, "send",
		G_CALLBACK (update_forwarded_flags_cb), data);
	g_signal_connect (
		composer, "save-to-drafts",
		G_CALLBACK (update_forwarded_flags_cb), data);

	g_object_set_data_full (
		G_OBJECT (composer), "forward-data", data,
		(GDestroyNotify) forward_data_free);
}

static void
forward_non_attached (EMsgComposer *composer,
                      CamelFolder *folder,
                      const gchar *uid,
                      CamelMimeMessage *message,
                      EMailForwardStyle style)
{
	CamelSession *session;
	gchar *text, *forward, *subject;
	guint32 validity_found = 0;
	guint32 flags;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));

	session = e_msg_composer_ref_session (composer);

	flags = E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS |
		E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
	if (style == E_MAIL_FORWARD_STYLE_QUOTED)
		flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE;
	if (!e_content_editor_get_html_mode (e_html_editor_get_content_editor (e_msg_composer_get_editor (composer))))
		flags |= E_MAIL_FORMATTER_QUOTE_FLAG_NO_FORMATTING;

	/* Setup composer's From account before calling quoting_text(),
	   because quoting_text() relies on that account. */
	subject = mail_tool_generate_forward_subject (message);
	set_up_new_composer (composer, subject, folder, message, uid, FALSE);
	g_free (subject);

	forward = quoting_text (QUOTING_FORWARD, composer);
	text = em_utils_message_to_html (session, message, forward, flags, NULL, NULL, NULL, &validity_found);

	if (text != NULL) {
		CamelDataWrapper *content;

		content = camel_medium_get_content (CAMEL_MEDIUM (message));

		if (CAMEL_IS_MULTIPART (content))
			e_msg_composer_add_message_attachments (
				composer, message, FALSE);

		e_msg_composer_set_body_text (composer, text, TRUE);

		emu_add_composer_references_from_message (composer, message);

		emu_set_source_headers (composer, folder, uid, CAMEL_MESSAGE_FORWARDED);

		emu_update_composers_security (composer, validity_found);
		composer_set_no_change (composer);
		gtk_widget_show (GTK_WIDGET (composer));

		g_free (text);
	}

	g_clear_object (&session);
	g_free (forward);
}

/**
 * em_utils_forward_message:
 * @composer: an #EMsgComposer
 * @message: a #CamelMimeMessage to forward
 * @style: the forward style to use
 * @folder: (nullable):  a #CamelFolder, or %NULL
 * @uid: (nullable): the UID of %message, or %NULL
 *
 * Forwards @message in the given @style.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_ATTACHED, the new message is
 * created as follows.  If there is more than a single message in @uids,
 * a multipart/digest will be constructed and attached to a new composer
 * window preset with the appropriate header defaults for forwarding the
 * first message in the list.  If only one message is to be forwarded,
 * it is forwarded as a simple message/rfc822 attachment.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_INLINE, each message is forwarded
 * in its own composer window in 'inline' form.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_QUOTED, each message is forwarded
 * in its own composer window in 'quoted' form (each line starting with
 * a "> ").
 **/
void
em_utils_forward_message (EMsgComposer *composer,
                          CamelMimeMessage *message,
                          EMailForwardStyle style,
                          CamelFolder *folder,
                          const gchar *uid)
{
	CamelMimePart *part;
	GPtrArray *uids = NULL;
	gchar *subject;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	e_msg_composer_set_is_reply_or_forward (composer, TRUE);

	switch (style) {
		case E_MAIL_FORWARD_STYLE_ATTACHED:
		default:
			part = mail_tool_make_message_attachment (message);
			subject = mail_tool_generate_forward_subject (message);

			if (folder && uid) {
				uids = g_ptr_array_new ();
				g_ptr_array_add (uids, (gpointer) uid);
			}

			em_utils_forward_attachment (composer, part, subject, uids ? folder : NULL, uids);

			g_object_unref (part);
			g_free (subject);
			break;

		case E_MAIL_FORWARD_STYLE_INLINE:
		case E_MAIL_FORWARD_STYLE_QUOTED:
			forward_non_attached (composer, folder, uid, message, style);
			break;
	}

	if (uids)
		g_ptr_array_unref (uids);
}

void
em_utils_forward_attachment (EMsgComposer *composer,
                             CamelMimePart *part,
                             const gchar *subject,
                             CamelFolder *folder,
                             GPtrArray *uids)
{
	CamelDataWrapper *content;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_PART (part));

	if (folder != NULL)
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

	e_msg_composer_set_is_reply_or_forward (composer, TRUE);

	set_up_new_composer (composer, subject, folder, NULL, NULL, FALSE);

	e_msg_composer_attach (composer, part);

	content = camel_medium_get_content (CAMEL_MEDIUM (part));
	if (CAMEL_IS_MIME_MESSAGE (content)) {
		emu_add_composer_references_from_message (composer, CAMEL_MIME_MESSAGE (content));
	} else if (CAMEL_IS_MULTIPART (content)) {
		gchar *mime_type;

		mime_type = camel_data_wrapper_get_mime_type (content);
		if (mime_type && g_ascii_strcasecmp (mime_type, "multipart/digest") == 0) {
			/* This is the way evolution forwards multiple messages as attachment */
			CamelMultipart *multipart;
			guint ii, nparts;

			multipart = CAMEL_MULTIPART (content);
			nparts = camel_multipart_get_number (multipart);

			for (ii = 0; ii < nparts; ii++) {
				CamelMimePart *mpart;

				g_free (mime_type);

				mpart = camel_multipart_get_part (multipart, ii);
				mime_type = camel_data_wrapper_get_mime_type (CAMEL_DATA_WRAPPER (mpart));

				if (mime_type && g_ascii_strcasecmp (mime_type, "message/rfc822") == 0) {
					content = camel_medium_get_content (CAMEL_MEDIUM (mpart));

					if (CAMEL_IS_MIME_MESSAGE (content))
						emu_add_composer_references_from_message (composer, CAMEL_MIME_MESSAGE (content));
				}
			}
		}

		g_free (mime_type);
	}

	if (uids != NULL)
		setup_forward_attached_callbacks (composer, folder, uids);

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));
}

/**
 * em_utils_redirect_message:
 * @composer: an #EMsgComposer
 * @message: message to redirect
 *
 * Sets up the @composer to redirect @message (Note: only headers will be
 * editable). Adds Resent-From/Resent-To/etc headers.
 *
 * Since: 3.22
 **/
void
em_utils_redirect_message (EMsgComposer *composer,
                           CamelMimeMessage *message)
{
	ESource *source;
	EShell *shell;
	CamelMedium *medium;
	gchar *identity_uid = NULL, *alias_name = NULL, *alias_address = NULL;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	shell = e_msg_composer_get_shell (composer);
	medium = CAMEL_MEDIUM (message);

	/* QMail will refuse to send a message if it finds one of
	 * it's Delivered-To headers in the message, so remove all
	 * Delivered-To headers. Fixes bug #23635. */
	while (camel_medium_get_header (medium, "Delivered-To"))
		camel_medium_remove_header (medium, "Delivered-To");

	while (camel_medium_get_header (medium, "Bcc"))
		camel_medium_remove_header (medium, "Bcc");

	while (camel_medium_get_header (medium, "Resent-Bcc"))
		camel_medium_remove_header (medium, "Resent-Bcc");

	source = em_composer_utils_guess_identity_source (shell, message, NULL, NULL, &alias_name, &alias_address);

	if (source != NULL) {
		identity_uid = e_source_dup_uid (source);
		g_object_unref (source);
	}

	e_msg_composer_setup_redirect (composer, message, identity_uid, alias_name, alias_address, NULL);

	g_free (identity_uid);
	g_free (alias_name);
	g_free (alias_address);

	gtk_widget_show (GTK_WIDGET (composer));

	composer_set_no_change (composer);
}

/* Replying to messages... */

EDestination **
em_utils_camel_address_to_destination (CamelInternetAddress *iaddr)
{
	EDestination *dest, **destv;
	gint n, i, j;

	if (iaddr == NULL)
		return NULL;

	if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0)
		return NULL;

	destv = g_malloc (sizeof (EDestination *) * (n + 1));
	for (i = 0, j = 0; i < n; i++) {
		const gchar *name, *addr;

		if (camel_internet_address_get (iaddr, i, &name, &addr)) {
			dest = e_destination_new ();
			e_destination_set_name (dest, name);
			e_destination_set_email (dest, addr);

			destv[j++] = dest;
		}
	}

	if (j == 0) {
		g_free (destv);
		return NULL;
	}

	destv[j] = NULL;

	return destv;
}

static gchar *
emcu_construct_reply_subject (const gchar *source_subject)
{
	gchar *res;

	if (source_subject) {
		GSettings *settings;
		gboolean skip_len = -1;

		if (em_utils_is_re_in_subject (source_subject, &skip_len, NULL, NULL) && skip_len > 0)
			source_subject = source_subject + skip_len;

		settings = e_util_ref_settings ("org.gnome.evolution.mail");
		if (g_settings_get_boolean (settings, "composer-use-localized-fwd-re")) {
			/* Translators: This is a reply attribution in the message reply subject. The %s is replaced with the subject of the original message. Both 'Re'-s in the 'reply-attribution' translation context should translate into the same string, the same as the ':' separator. */
			res = g_strdup_printf (C_("reply-attribution", "Re: %s"), source_subject);
		} else {
			/* Do not localize this string */
			res = g_strdup_printf ("Re: %s", source_subject);
		}
		g_clear_object (&settings);
	} else {
		res = g_strdup ("");
	}

	return res;
}

static void
reply_setup_composer_recipients (EMsgComposer *composer,
				 CamelInternetAddress *to,
				 CamelInternetAddress *cc,
				 CamelFolder *folder,
				 const gchar *message_uid,
				 CamelNNTPAddress *postto)
{
	EComposerHeaderTable *table;
	EDestination **tov, **ccv;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	if (to != NULL)
		g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));

	if (cc != NULL)
		g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));

	/* Construct the tov/ccv */
	tov = em_utils_camel_address_to_destination (to);
	ccv = em_utils_camel_address_to_destination (cc);

	table = e_msg_composer_get_header_table (composer);
	e_composer_header_table_set_destinations_to (table, tov);

	/* Add destinations instead of setting, so we don't remove
	 * automatic CC addresses that have already been added. */
	e_composer_header_table_add_destinations_cc (table, ccv);

	e_destination_freev (tov);
	e_destination_freev (ccv);

	/* Add post-to, if necessary */
	if (postto && camel_address_length ((CamelAddress *) postto)) {
		CamelFolder *use_folder = folder, *temp_folder = NULL;
		gchar *store_url = NULL;
		gchar *post;

		if (use_folder && CAMEL_IS_VEE_FOLDER (use_folder) && message_uid) {
			em_utils_get_real_folder_and_message_uid (use_folder, message_uid, &temp_folder, NULL, NULL);

			if (temp_folder)
				use_folder = temp_folder;
		}

		if (use_folder) {
			CamelStore *parent_store;
			CamelService *service;
			CamelURL *url;

			parent_store = camel_folder_get_parent_store (use_folder);

			service = CAMEL_SERVICE (parent_store);
			url = camel_service_new_camel_url (service);

			store_url = camel_url_to_string (
				url, CAMEL_URL_HIDE_ALL);
			if (store_url[strlen (store_url) - 1] == '/')
				store_url[strlen (store_url) - 1] = '\0';

			camel_url_free (url);
		}

		post = camel_address_encode ((CamelAddress *) postto);
		e_composer_header_table_set_post_to_base (
			table, store_url ? store_url : "", post);
		g_free (post);
		g_free (store_url);
		g_clear_object (&temp_folder);
	}
}

static void
reply_setup_composer (EMsgComposer *composer,
		      CamelMimeMessage *message,
		      const gchar *identity_uid,
		      const gchar *identity_name,
		      const gchar *identity_address,
		      CamelInternetAddress *to,
		      CamelInternetAddress *cc,
		      CamelFolder *folder,
		      const gchar *message_uid,
		      CamelNNTPAddress *postto)
{
	gchar *message_id, *references;
	EComposerHeaderTable *table;
	CamelMedium *medium;
	gchar *subject;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	e_msg_composer_set_is_reply_or_forward (composer, TRUE);

	if (to != NULL)
		g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));

	if (cc != NULL)
		g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));

	reply_setup_composer_recipients (composer, to, cc, folder, message_uid, postto);

	/* Set the subject of the new message. */
	subject = emcu_construct_reply_subject (camel_mime_message_get_subject (message));

	table = e_msg_composer_get_header_table (composer);
	e_composer_header_table_set_subject (table, subject);
	e_composer_header_table_set_identity_uid (table, identity_uid, identity_name, identity_address);

	g_free (subject);

	/* Add In-Reply-To and References. */

	medium = CAMEL_MEDIUM (message);
	message_id = camel_header_unfold (camel_medium_get_header (medium, "Message-ID"));
	references = camel_header_unfold (camel_medium_get_header (medium, "References"));

	if (message_id != NULL) {
		gchar *reply_refs;

		e_msg_composer_add_header (
			composer, "In-Reply-To", message_id);

		if (references)
			reply_refs = g_strdup_printf (
				"%s %s", references, message_id);
		else
			reply_refs = g_strdup (message_id);

		e_msg_composer_add_header (
			composer, "References", reply_refs);
		g_free (reply_refs);

	} else if (references != NULL) {
		e_msg_composer_add_header (
			composer, "References", references);
	}

	g_free (message_id);
	g_free (references);
}

static gboolean
get_reply_list (CamelMimeMessage *message,
                CamelInternetAddress *to)
{
	const gchar *header, *p;
	gchar *addr;

	/* Examples:
	 *
	 * List-Post: <mailto:list@host.com>
	 * List-Post: <mailto:moderator@host.com?subject=list%20posting>
	 * List-Post: NO (posting not allowed on this list)
	 */
	if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post")))
		return FALSE;

	while (*header == ' ' || *header == '\t')
		header++;

	/* check for NO */
	if (!g_ascii_strncasecmp (header, "NO", 2))
		return FALSE;

	/* Search for the first mailto angle-bracket enclosed URL.
	 * (See rfc2369, Section 2, paragraph 3 for details) */
	if (!(header = camel_strstrcase (header, "<mailto:")))
		return FALSE;

	header += 8;

	p = header;
	while (*p && !strchr ("?>", *p))
		p++;

	addr = g_strndup (header, p - header);
	camel_internet_address_add (to, NULL, addr);
	g_free (addr);

	return TRUE;
}

gboolean
em_utils_is_munged_list_message (CamelMimeMessage *message)
{
	CamelInternetAddress *reply_to, *list;
	gboolean result = FALSE;

	reply_to = camel_mime_message_get_reply_to (message);
	if (reply_to) {
		list = camel_internet_address_new ();

		if (get_reply_list (message, list) &&
		    camel_address_length (CAMEL_ADDRESS (list)) ==
		    camel_address_length (CAMEL_ADDRESS (reply_to))) {
			gint i;
			const gchar *r_name, *r_addr;
			const gchar *l_name, *l_addr;

			for (i = 0; i < camel_address_length (CAMEL_ADDRESS (list)); i++) {
				if (!camel_internet_address_get (reply_to, i, &r_name, &r_addr))
					break;
				if (!camel_internet_address_get (list, i, &l_name, &l_addr))
					break;
				if (strcmp (l_addr, r_addr))
					break;
			}
			if (i == camel_address_length (CAMEL_ADDRESS (list)))
				result = TRUE;
		}
		g_object_unref (list);
	}
	return result;
}

static CamelInternetAddress *
get_reply_to (CamelMimeMessage *message)
{
	CamelInternetAddress *reply_to;

	reply_to = camel_mime_message_get_reply_to (message);
	if (reply_to) {
		GSettings *settings;
		gboolean ignore_list_reply_to;

		settings = e_util_ref_settings ("org.gnome.evolution.mail");
		ignore_list_reply_to = g_settings_get_boolean (
			settings, "composer-ignore-list-reply-to");
		g_object_unref (settings);

		if (ignore_list_reply_to && em_utils_is_munged_list_message (message))
			reply_to = NULL;
	}
	if (!reply_to)
		reply_to = camel_mime_message_get_from (message);

	return reply_to;
}

static void
get_reply_sender (CamelMimeMessage *message,
                  CamelInternetAddress *to,
                  CamelNNTPAddress *postto)
{
	CamelInternetAddress *reply_to;
	CamelMedium *medium;
	const gchar *posthdr = NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	reply_to = get_reply_to (message);

	if (reply_to != NULL) {
		const gchar *name;
		const gchar *addr;
		gint ii = 0;

		while (camel_internet_address_get (reply_to, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}
}

void
em_utils_get_reply_sender (CamelMimeMessage *message,
                           CamelInternetAddress *to,
                           CamelNNTPAddress *postto)
{
	get_reply_sender (message, to, postto);
}

static void
get_reply_from (CamelMimeMessage *message,
                CamelInternetAddress *to,
                CamelNNTPAddress *postto)
{
	CamelInternetAddress *from;
	CamelMedium *medium;
	const gchar *name, *addr;
	const gchar *posthdr = NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	from = camel_mime_message_get_from (message);

	if (from != NULL) {
		gint ii = 0;

		while (camel_internet_address_get (from, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}
}

static void
get_reply_recipient (CamelMimeMessage *message,
                     CamelInternetAddress *to,
                     CamelNNTPAddress *postto,
                     CamelInternetAddress *address)
{
	CamelMedium *medium;
	const gchar *posthdr = NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		 posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	if (address != NULL) {
		const gchar *name;
		const gchar *addr;
		gint ii = 0;

		while (camel_internet_address_get (address, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}

}

static void
concat_unique_addrs (CamelInternetAddress *dest,
                     CamelInternetAddress *src,
                     GHashTable *rcpt_hash)
{
	const gchar *name, *addr;
	gint i;

	for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) {
		if (!g_hash_table_contains (rcpt_hash, addr)) {
			camel_internet_address_add (dest, name, addr);
			g_hash_table_insert (rcpt_hash, g_strdup (addr), NULL);
		}
	}
}

static void
add_source_to_recipient_hash (ESourceRegistry *registry,
			      GHashTable *rcpt_hash,
			      const gchar *address,
			      ESource *source,
			      gboolean source_is_default)
{
	ESource *cached_source;
	gboolean insert_source;

	g_return_if_fail (rcpt_hash != NULL);
	g_return_if_fail (E_IS_SOURCE (source));

	if (!address || !*address)
		return;

	cached_source = g_hash_table_lookup (rcpt_hash, address);

	insert_source = source_is_default || !cached_source;

	if (insert_source)
		g_hash_table_insert (rcpt_hash, g_strdup (address), g_object_ref (source));
}

static void
unref_nonull_object (gpointer ptr)
{
	if (ptr)
		g_object_unref (ptr);
}

static GHashTable *
generate_recipient_hash (ESourceRegistry *registry)
{
	GHashTable *rcpt_hash;
	ESource *default_source;
	GList *list, *link;
	const gchar *extension_name;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);

	rcpt_hash = g_hash_table_new_full (
		camel_strcase_hash,
		camel_strcase_equal,
		g_free, unref_nonull_object);

	default_source = e_source_registry_ref_default_mail_identity (registry);

	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
	list = e_source_registry_list_sources (registry, extension_name);

	for (link = list; link != NULL; link = g_list_next (link)) {
		ESource *source = E_SOURCE (link->data);
		ESourceMailIdentity *extension;
		GHashTable *aliases;
		const gchar *address;
		gboolean source_is_default;

		/* No default mail identity implies there are no mail
		 * identities at all and so we should never get here. */
		g_warn_if_fail (default_source != NULL);

		if (!e_source_registry_check_enabled (registry, source))
			continue;

		source_is_default = e_source_equal (source, default_source);

		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
		extension = e_source_get_extension (source, extension_name);

		address = e_source_mail_identity_get_address (extension);

		add_source_to_recipient_hash (registry, rcpt_hash, address, source, source_is_default);

		aliases = e_source_mail_identity_get_aliases_as_hash_table (extension);
		if (aliases) {
			GHashTableIter iter;
			gpointer key;

			g_hash_table_iter_init (&iter, aliases);
			while (g_hash_table_iter_next (&iter, &key, NULL)) {
				address = key;

				add_source_to_recipient_hash (registry, rcpt_hash, address, source, source_is_default);
			}

			g_hash_table_destroy (aliases);
		}
	}

	g_list_free_full (list, (GDestroyNotify) g_object_unref);

	if (default_source != NULL)
		g_object_unref (default_source);

	return rcpt_hash;
}

void
em_utils_get_reply_all (ESourceRegistry *registry,
                        CamelMimeMessage *message,
                        CamelInternetAddress *to,
                        CamelInternetAddress *cc,
                        CamelNNTPAddress *postto)
{
	CamelInternetAddress *reply_to;
	CamelInternetAddress *to_addrs;
	CamelInternetAddress *cc_addrs;
	CamelMedium *medium;
	const gchar *name, *addr;
	const gchar *posthdr = NULL;
	GHashTable *rcpt_hash;

	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL)
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);

	rcpt_hash = generate_recipient_hash (registry);

	reply_to = get_reply_to (message);
	to_addrs = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_TO);
	cc_addrs = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_CC);

	if (reply_to != NULL) {
		gint ii = 0;

		while (camel_internet_address_get (reply_to, ii++, &name, &addr)) {
			/* Ignore references to the Reply-To address
			 * in the To and Cc lists. */
			if (addr && !g_hash_table_contains (rcpt_hash, addr)) {
				/* In the case we are doing a Reply-To-All,
				 * we do not want to include the user's email
				 * address because replying to oneself is
				 * kinda silly. */
				camel_internet_address_add (to, name, addr);
				g_hash_table_insert (rcpt_hash, g_strdup (addr), NULL);
			}
		}
	}

	concat_unique_addrs (to, to_addrs, rcpt_hash);
	concat_unique_addrs (cc, cc_addrs, rcpt_hash);

	/* Set as the 'To' the first 'Reply-To' address, if such exists, when no address
	   had been picked (like when all addresses are configured mail accounts). */
	if (reply_to &&
	    camel_address_length ((CamelAddress *) to) == 0 &&
	    camel_internet_address_get (reply_to, 0, &name, &addr)) {
		camel_internet_address_add (to, name, addr);
	}

	/* Promote the first Cc: address to To: if To: is empty. */
	if (camel_address_length ((CamelAddress *) to) == 0 &&
	    camel_address_length ((CamelAddress *) cc) > 0) {
		if (camel_internet_address_get (cc, 0, &name, &addr))
			camel_internet_address_add (to, name, addr);
		camel_address_remove ((CamelAddress *) cc, 0);
	}

	/* If To: is still empty, may we removed duplicates (i.e. ourself),
	 * so add the original To if it was set. */
	if (camel_address_length ((CamelAddress *) to) == 0
	    && (camel_internet_address_get (to_addrs, 0, &name, &addr)
		|| camel_internet_address_get (cc_addrs, 0, &name, &addr))) {
		camel_internet_address_add (to, name, addr);
	}

	g_hash_table_destroy (rcpt_hash);
}

enum {
	ATTRIB_UNKNOWN,
	ATTRIB_CUSTOM,
	ATTRIB_TIMEZONE,
	ATTRIB_STRFTIME,
	ATTRIB_TM_SEC,
	ATTRIB_TM_MIN,
	ATTRIB_TM_24HOUR,
	ATTRIB_TM_12HOUR,
	ATTRIB_TM_MDAY,
	ATTRIB_TM_MON,
	ATTRIB_TM_YEAR,
	ATTRIB_TM_2YEAR,
	ATTRIB_TM_WDAY, /* not actually used */
	ATTRIB_TM_YDAY
};

typedef void		(*AttribFormatter)	(GString *str,
						 const gchar *attr,
						 CamelMimeMessage *message);

static void
format_sender (GString *str,
               const gchar *attr,
               CamelMimeMessage *message)
{
	CamelInternetAddress *sender;
	const gchar *name, *addr = NULL;
	gchar *tmp = NULL;

	sender = camel_mime_message_get_from (message);
	if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) {
		name = NULL;

		if (camel_internet_address_get (sender, 0, &name, &addr)) {
			if (name && !*name) {
				name = NULL;
			} else if (name && *name == '\"') {
				gint len = strlen (name);

				if (len == 1) {
					name = NULL;
				} else if (len > 1 && name[len - 1] == '\"') {
					if (len == 2) {
						name = NULL;
					} else {
						tmp = g_strndup (name + 1, len - 2);
						name = tmp;
					}
				}
			}
		}
	} else {
		name = _("an unknown sender");
	}

	if (name && !strcmp (attr, "{SenderName}")) {
		g_string_append (str, name);
	} else if (addr && !strcmp (attr, "{SenderEMail}")) {
		g_string_append (str, addr);
	} else if (name && *name) {
		g_string_append (str, name);
	} else if (addr) {
		g_string_append (str, addr);
	}

	g_free (tmp);
}

static struct {
	const gchar *name;
	gint type;
	struct {
		const gchar *format;         /* strftime or printf format */
		AttribFormatter formatter;  /* custom formatter */
	} v;
} attribvars[] = {
	{ "{Sender}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{SenderName}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{SenderEMail}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{AbbrevWeekdayName}", ATTRIB_STRFTIME, { "%a", NULL } },
	{ "{WeekdayName}", ATTRIB_STRFTIME, { "%A", NULL } },
	{ "{AbbrevMonthName}", ATTRIB_STRFTIME, { "%b", NULL } },
	{ "{MonthName}", ATTRIB_STRFTIME, { "%B", NULL } },
	{ "{AmPmUpper}", ATTRIB_STRFTIME, { "%p", NULL } },
	{ "{AmPmLower}", ATTRIB_STRFTIME, { "%P", NULL } },
	{ "{Day}", ATTRIB_TM_MDAY, { "%02d", NULL } },  /* %d  01-31 */
	{ "{ Day}", ATTRIB_TM_MDAY, { "% 2d", NULL } },  /* %e   1-31 */
	{ "{24Hour}", ATTRIB_TM_24HOUR, { "%02d", NULL } },  /* %H  00-23 */
	{ "{12Hour}", ATTRIB_TM_12HOUR, { "%02d", NULL } },  /* %I  00-12 */
	{ "{DayOfYear}", ATTRIB_TM_YDAY, { "%d", NULL } },  /* %j  1-366 */
	{ "{Month}", ATTRIB_TM_MON, { "%02d", NULL } },  /* %m  01-12 */
	{ "{Minute}", ATTRIB_TM_MIN, { "%02d", NULL } },  /* %M  00-59 */
	{ "{Seconds}", ATTRIB_TM_SEC, { "%02d", NULL } },  /* %S  00-61 */
	{ "{2DigitYear}", ATTRIB_TM_2YEAR, { "%02d", NULL } },  /* %y */
	{ "{Year}", ATTRIB_TM_YEAR, { "%04d", NULL } },  /* %Y */
	{ "{TimeZone}", ATTRIB_TIMEZONE, { "%+05d", NULL } }
};

gchar *
em_composer_utils_get_reply_credits (ESource *identity_source,
				     CamelMimeMessage *message)
{
	register const gchar *inptr;
	const gchar *start;
	gint tzone, len, i;
	gchar buf[64];
	GString *str;
	struct tm tm;
	time_t date;
	gint type;
	gchar *format, *restore_lc_messages = NULL, *restore_lc_time = NULL;

	emcu_prepare_attribution_locale (identity_source, &restore_lc_messages, &restore_lc_time);

	format = quoting_text (QUOTING_ATTRIBUTION, NULL);
	str = g_string_new ("");

	date = camel_mime_message_get_date (message, &tzone);

	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
		/* The message has no Date: header, look at Received: */
		date = camel_mime_message_get_date_received (message, &tzone);
	}
	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
		/* That didn't work either, use current time */
		time (&date);
		tzone = 0;
	} else if (tzone == 0) {
		GSettings *settings;

		settings = e_util_ref_settings ("org.gnome.evolution.mail");

		if (g_settings_get_boolean (settings, "composer-reply-credits-utc-to-localtime")) {
			struct tm local;
			gint offset = 0;

			e_localtime_with_offset (date, &local, &offset);

			tzone = offset / 3600;
			tzone = (tzone * 100) + ((offset / 60) % 60);
		}

		g_clear_object (&settings);
	}

	/* Convert to UTC */
	date += (tzone / 100) * 60 * 60;
	date += (tzone % 100) * 60;

	gmtime_r (&date, &tm);

	inptr = format;
	while (*inptr != '\0') {
		start = inptr;
		while (*inptr && strncmp (inptr, "${", 2) != 0)
			inptr++;

		g_string_append_len (str, start, inptr - start);

		if (*inptr == '\0')
			break;

		start = ++inptr;
		while (*inptr && *inptr != '}')
			inptr++;

		if (*inptr != '}') {
			/* broken translation */
			g_string_append_len (str, "${", 2);
			inptr = start + 1;
			continue;
		}

		inptr++;
		len = inptr - start;
		type = ATTRIB_UNKNOWN;
		for (i = 0; i < G_N_ELEMENTS (attribvars); i++) {
			if (!strncmp (attribvars[i].name, start, len)) {
				type = attribvars[i].type;
				break;
			}
		}

		switch (type) {
		case ATTRIB_CUSTOM:
			attribvars[i].v.formatter (
				str, attribvars[i].name, message);
			break;
		case ATTRIB_TIMEZONE:
			g_string_append_printf (
				str, attribvars[i].v.format, tzone);
			break;
		case ATTRIB_STRFTIME:
			e_utf8_strftime_match_lc_messages (
				buf, sizeof (buf), attribvars[i].v.format, &tm);
			g_string_append (str, buf);
			break;
		case ATTRIB_TM_SEC:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_sec);
			break;
		case ATTRIB_TM_MIN:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_min);
			break;
		case ATTRIB_TM_24HOUR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_hour);
			break;
		case ATTRIB_TM_12HOUR:
			g_string_append_printf (
				str, attribvars[i].v.format,
				(tm.tm_hour + 1) % 13);
			break;
		case ATTRIB_TM_MDAY:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_mday);
			break;
		case ATTRIB_TM_MON:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_mon + 1);
			break;
		case ATTRIB_TM_YEAR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_year + 1900);
			break;
		case ATTRIB_TM_2YEAR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_year % 100);
			break;
		case ATTRIB_TM_WDAY:
			/* not actually used */
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_wday);
			break;
		case ATTRIB_TM_YDAY:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_yday + 1);
			break;
		default:
			/* Misspelled variable?  Drop the
			 * format argument and continue. */
			break;
		}
	}

	emcu_restore_locale_after_attribution (restore_lc_messages, restore_lc_time);

	g_free (format);

	return g_string_free (str, FALSE);
}

static void
composer_set_body (EMsgComposer *composer,
                   CamelMimeMessage *message,
                   EMailReplyStyle style,
                   EMailPartList *parts_list)
{
	gchar *text, *credits, *original;
	ESource *identity_source;
	CamelMimePart *part;
	CamelSession *session;
	GSettings *settings;
	guint32 validity_found = 0, keep_sig_flag = 0;

	settings = e_util_ref_settings ("org.gnome.evolution.mail");
	if (g_settings_get_boolean (settings, "composer-reply-keep-signature"))
		keep_sig_flag = E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
	g_clear_object (&settings);

	session = e_msg_composer_ref_session (composer);

	switch (style) {
	case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
		/* do nothing */
		break;
	case E_MAIL_REPLY_STYLE_ATTACH:
		/* attach the original message as an attachment */
		part = mail_tool_make_message_attachment (message);
		e_msg_composer_attach (composer, part);
		g_object_unref (part);
		break;
	case E_MAIL_REPLY_STYLE_OUTLOOK:
		original = quoting_text (QUOTING_ORIGINAL, composer);
		text = em_utils_message_to_html (
			session, message, original, E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS | keep_sig_flag,
			parts_list, NULL, NULL, &validity_found);
		e_msg_composer_set_body_text (composer, text, TRUE);
		g_free (text);
		g_free (original);
		emu_update_composers_security (composer, validity_found);
		break;

	case E_MAIL_REPLY_STYLE_QUOTED:
	default:
		identity_source = emcu_ref_identity_source_from_composer (composer);

		/* do what any sane user would want when replying... */
		credits = em_composer_utils_get_reply_credits (identity_source, message);

		g_clear_object (&identity_source);

		text = em_utils_message_to_html (
			session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE | keep_sig_flag,
			parts_list, NULL, NULL, &validity_found);
		g_free (credits);
		e_msg_composer_set_body_text (composer, text, TRUE);
		g_free (text);
		emu_update_composers_security (composer, validity_found);
		break;
	}

	g_object_unref (session);
}

static gboolean
emcu_folder_is_inbox (CamelFolder *folder)
{
	CamelSession *session;
	CamelStore *store;
	gboolean is_inbox = FALSE;

	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);

	store = camel_folder_get_parent_store (folder);
	if (!store)
		return FALSE;

	session = camel_service_ref_session (CAMEL_SERVICE (store));
	if (!session)
		return FALSE;

	if (E_IS_MAIL_SESSION (session)) {
		MailFolderCache *folder_cache;
		CamelFolderInfoFlags flags = 0;

		folder_cache = e_mail_session_get_folder_cache (E_MAIL_SESSION (session));
		if (folder_cache && mail_folder_cache_get_folder_info_flags (
			folder_cache, store, camel_folder_get_full_name (folder), &flags)) {
			is_inbox = (flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX;
		}
	}

	g_object_unref (session);

	return is_inbox;
}

typedef struct _AltReplyContext {
	EShell *shell;
	EAlertSink *alert_sink;
	CamelMimeMessage *source_message;
	CamelFolder *folder;
	gchar *message_uid;
	CamelMimeMessage *new_message; /* When processed with a template */
	EMailPartList *source;
	EMailReplyType type;
	EMailReplyStyle style;
	guint32 flags;
	gboolean template_preserve_subject;
} AltReplyContext;

static void
alt_reply_context_free (gpointer ptr)
{
	AltReplyContext *context = ptr;

	if (context) {
		g_clear_object (&context->shell);
		g_clear_object (&context->alert_sink);
		g_clear_object (&context->source_message);
		g_clear_object (&context->folder);
		g_clear_object (&context->source);
		g_clear_object (&context->new_message);
		g_free (context->message_uid);
		g_slice_free (AltReplyContext, context);
	}
}

static guint32
get_composer_mark_read_on_reply_flag (void)
{
	GSettings *settings;
	guint32 res = 0;

	settings = e_util_ref_settings ("org.gnome.evolution.mail");

	if (g_settings_get_boolean (settings, "composer-mark-read-on-reply"))
		res = CAMEL_MESSAGE_SEEN;

	g_object_unref (settings);

	return res;
}

static void
alt_reply_composer_created_cb (GObject *source_object,
			       GAsyncResult *result,
			       gpointer user_data)
{
	AltReplyContext *context = user_data;
	EMsgComposer *composer;
	GError *error = NULL;

	g_return_if_fail (context != NULL);

	composer = e_msg_composer_new_finish (result, &error);

	if (composer) {
		EContentEditor *cnt_editor;

		cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));

		if (context->new_message) {
			CamelInternetAddress *to = NULL, *cc = NULL;
			CamelNNTPAddress *postto = NULL;
			gboolean need_reply_all = FALSE;

			if ((context->flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN | E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
				e_content_editor_set_html_mode (cnt_editor, (context->flags & E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0);
			}

			em_utils_edit_message (composer, context->folder, context->new_message, context->message_uid, TRUE);

			if (context->type == E_MAIL_REPLY_TO_SENDER) {
				/* Reply to sender */
				to = camel_internet_address_new ();
				if (context->folder)
					postto = camel_nntp_address_new ();
				get_reply_sender (context->source_message, to, postto);
			} else if (context->type == E_MAIL_REPLY_TO_LIST) {
				/* Reply to list */
				to = camel_internet_address_new ();

				if (!get_reply_list (context->source_message, to)) {
					need_reply_all = TRUE;
					g_clear_object (&to);
				}
			} else if (context->type != E_MAIL_REPLY_TO_ALL) {
				g_warn_if_reached ();
			}

			if (context->type == E_MAIL_REPLY_TO_ALL || need_reply_all) {
				/* Reply to all */
				to = camel_internet_address_new ();
				cc = camel_internet_address_new ();

				if (context->folder)
					postto = camel_nntp_address_new ();

				em_utils_get_reply_all (e_shell_get_registry (context->shell), context->source_message, to, cc, postto);
			}

			reply_setup_composer_recipients (composer, to, cc, context->folder, context->message_uid, postto);

			composer_set_no_change (composer);

			g_clear_object (&to);
			g_clear_object (&cc);
			g_clear_object (&postto);

			if (context->folder && context->message_uid) {
				emu_set_source_headers (composer, context->folder, context->message_uid,
					CAMEL_MESSAGE_ANSWERED | get_composer_mark_read_on_reply_flag ());
			}
		} else {
			em_utils_reply_to_message (composer, context->source_message,
				context->folder, context->message_uid, context->type, context->style,
				context->source, NULL, context->flags | E_MAIL_REPLY_FLAG_FORCE_SENDER_REPLY);
		}
	} else {
		e_alert_submit (context->alert_sink, "mail-composer:failed-create-composer",
			error ? error->message : _("Unknown error"), NULL);
	}

	alt_reply_context_free (context);
	g_clear_error (&error);
}

static void
alt_reply_template_applied_cb (GObject *source_object,
			       GAsyncResult *result,
			       gpointer user_data)
{
	AltReplyContext *context = user_data;
	GError *error = NULL;

	g_return_if_fail (context != NULL);

	context->new_message = e_mail_templates_apply_finish (source_object, result, &error);

	if (context->new_message) {
		if (context->template_preserve_subject) {
			gchar *subject;

			subject = emcu_construct_reply_subject (camel_mime_message_get_subject (context->source_message));
			camel_mime_message_set_subject (context->new_message, subject);
			g_free (subject);
		}

		e_msg_composer_new (context->shell, alt_reply_composer_created_cb, context);
	} else {
		e_alert_submit (context->alert_sink, "mail:no-retrieve-message",
			error ? error->message : _("Unknown error"), NULL);
		alt_reply_context_free (context);
	}

	g_clear_error (&error);
}

static void
emcu_three_state_toggled_cb (GtkToggleButton *widget,
			     gpointer user_data)
{
	glong *phandlerid = user_data;

	g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
	g_return_if_fail (phandlerid != NULL);

	g_signal_handler_block (widget, *phandlerid);

	if (gtk_toggle_button_get_inconsistent (widget) &&
	    gtk_toggle_button_get_active (widget)) {
		gtk_toggle_button_set_active (widget, FALSE);
		gtk_toggle_button_set_inconsistent (widget, FALSE);
	} else if (!gtk_toggle_button_get_active (widget)) {
		gtk_toggle_button_set_inconsistent (widget, TRUE);
		gtk_toggle_button_set_active (widget, FALSE);
	} else {
	}

	g_signal_handler_unblock (widget, *phandlerid);
}

static void
emcu_connect_three_state_changer (GtkToggleButton *toggle_button)
{
	glong *phandlerid;

	g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));

	phandlerid = g_new0 (glong, 1);

	*phandlerid = g_signal_connect_data (toggle_button, "toggled",
		G_CALLBACK (emcu_three_state_toggled_cb),
		phandlerid, (GClosureNotify) g_free, 0);
}

static void
emcu_three_state_set_value (GtkToggleButton *toggle_button,
			    EThreeState value)
{
	g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));

	if (value == E_THREE_STATE_OFF) {
		gtk_toggle_button_set_active (toggle_button, FALSE);
		gtk_toggle_button_set_inconsistent (toggle_button, FALSE);
	} else if (value == E_THREE_STATE_ON) {
		gtk_toggle_button_set_active (toggle_button, TRUE);
		gtk_toggle_button_set_inconsistent (toggle_button, FALSE);
	} else {
		gtk_toggle_button_set_active (toggle_button, FALSE);
		gtk_toggle_button_set_inconsistent (toggle_button, TRUE);
	}
}

static EThreeState
emcu_three_state_get_value (GtkToggleButton *toggle_button)
{
	g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), E_THREE_STATE_INCONSISTENT);

	if (gtk_toggle_button_get_inconsistent (toggle_button))
		return E_THREE_STATE_INCONSISTENT;
	else if (gtk_toggle_button_get_active (toggle_button))
		return E_THREE_STATE_ON;

	return E_THREE_STATE_OFF;
}

static GtkComboBox *
emcu_create_templates_combo (EShell *shell,
			     const gchar *folder_uri,
			     const gchar *message_uid)
{
	GtkComboBox *combo;
	GtkCellRenderer *renderer;
	EShellBackend *shell_backend;
	EMailSession *mail_session;
	EMailTemplatesStore *templates_store;
	GtkTreeStore *tree_store;
	GtkTreeIter found_iter;
	gboolean found_message = FALSE;

	shell_backend = e_shell_get_backend_by_name (shell, "mail");
	g_return_val_if_fail (E_IS_MAIL_BACKEND (shell_backend), NULL);

	mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
	templates_store = e_mail_templates_store_ref_default (e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session)));

	tree_store = e_mail_templates_store_build_model (templates_store, folder_uri, message_uid, &found_message, &found_iter);

	combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (tree_store)));

	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer),
		"ellipsize", PANGO_ELLIPSIZE_END,
		"max-width-chars", 60,
		NULL);

	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
		"text", E_MAIL_TEMPLATES_STORE_COLUMN_DISPLAY_NAME,
		NULL);

	g_clear_object (&templates_store);
	g_clear_object (&tree_store);

	if (found_message) {
		gtk_combo_box_set_active_iter (combo, &found_iter);
	} else {
		gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
	}

	return combo;
}

/**
 * em_utils_reply_alternative:
 * @parent: (nullable): a parent #GtkWindow for the question dialog
 * @shell: an #EShell instance used to create #EMsgComposer
 * @alert_sink: an #EAlertSink to put any errors to
 * @message: a #CamelMimeMessage
 * @folder: (nullable): a #CamelFolder, or %NULL
 * @message_uid: (nullable): the UID of @message, or %NULL
 * @style: the reply style to use
 * @source: (nullable): source to inherit view settings from
 *
 * This is similar to em_utils_reply_to_message(), except it asks user to
 * change some settings before sending. It calls em_utils_reply_to_message()
 * at the end for non-templated replies.
 *
 * Since: 3.30
 **/
void
em_utils_reply_alternative (GtkWindow *parent,
			    EShell *shell,
			    EAlertSink *alert_sink,
			    CamelMimeMessage *message,
			    CamelFolder *folder,
			    const gchar *message_uid,
			    EMailReplyStyle default_style,
			    EMailPartList *source)
{
	GtkWidget *dialog, *widget, *style_label;
	GtkBox *hbox, *vbox;
	GtkRadioButton *recip_sender, *recip_list, *recip_all;
	GtkLabel *sender_label, *list_label, *all_label;
	GtkRadioButton *style_default, *style_attach, *style_inline, *style_quote, *style_no_quote;
	GtkToggleButton *html_format;
	GtkToggleButton *bottom_posting;
	GtkToggleButton *top_signature;
	GtkCheckButton *apply_template;
	GtkComboBox *templates;
	GtkCheckButton *preserve_message_subject;
	PangoAttrList *attr_list;
	GSettings *settings;
	gchar *last_tmpl_folder_uri, *last_tmpl_message_uid, *address, *text;
	gboolean can_reply_list = FALSE;
	CamelInternetAddress *to, *cc;
	CamelNNTPAddress *postto = NULL;
	gint n_addresses;

	g_return_if_fail (E_IS_SHELL (shell));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	settings = e_util_ref_settings ("org.gnome.evolution.mail");

	dialog = gtk_dialog_new_with_buttons (_("Alternative Reply"), parent,
		GTK_DIALOG_DESTROY_WITH_PARENT,
		_("_Cancel"), GTK_RESPONSE_CANCEL,
		_("_Reply"), GTK_RESPONSE_OK,
		NULL);

	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);

	vbox = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog)));
	gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
	gtk_box_set_spacing (vbox, 2);

	#define add_with_indent(x) \
		gtk_widget_set_margin_left (GTK_WIDGET (x), 12); \
		gtk_box_pack_start (vbox, GTK_WIDGET (x), FALSE, FALSE, 0);

	widget = gtk_label_new (_("Recipients:"));
	g_object_set (G_OBJECT (widget),
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		NULL);
	gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);

	attr_list = pango_attr_list_new ();
	pango_attr_list_insert (attr_list, pango_attr_style_new (PANGO_STYLE_ITALIC));

	#define add_with_label(wgt, lbl) G_STMT_START { \
		GtkWidget *divider_label = gtk_label_new (":"); \
		gtk_label_set_attributes (GTK_LABEL (lbl), attr_list); \
		hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6)); \
		gtk_box_pack_start (hbox, GTK_WIDGET (wgt), FALSE, FALSE, 0); \
		gtk_box_pack_start (hbox, GTK_WIDGET (divider_label), FALSE, FALSE, 0); \
		gtk_box_pack_start (hbox, GTK_WIDGET (lbl), FALSE, FALSE, 0); \
		e_binding_bind_property ( \
			wgt, "sensitive", \
			divider_label, "visible", \
			G_BINDING_SYNC_CREATE); \
		e_binding_bind_property ( \
			wgt, "sensitive", \
			lbl, "visible", \
			G_BINDING_SYNC_CREATE); \
		add_with_indent (hbox); } G_STMT_END

	recip_sender = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		NULL, _("Reply to _Sender")));
	sender_label = GTK_LABEL (gtk_label_new (""));
	add_with_label (recip_sender, sender_label);

	recip_list = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (recip_sender), _("Reply to _List")));
	list_label = GTK_LABEL (gtk_label_new (""));
	add_with_label (recip_list, list_label);

	recip_all = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (recip_sender), _("Reply to _All")));
	all_label = GTK_LABEL (gtk_label_new (""));
	add_with_label (recip_all, all_label);

	#undef add_with_label

	pango_attr_list_unref (attr_list);

	/* One line gap between sections */
	widget = gtk_label_new (" ");
	gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);

	style_label = gtk_label_new (_("Reply style:"));
	g_object_set (G_OBJECT (style_label),
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		NULL);
	gtk_box_pack_start (vbox, style_label, FALSE, FALSE, 0);

	style_default = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		NULL, _("_Default")));
	add_with_indent (style_default);

	style_attach = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (style_default), _("Attach_ment")));
	add_with_indent (style_attach);

	style_inline = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (style_default), _("Inline (_Outlook style)")));
	add_with_indent (style_inline);

	style_quote = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (style_default), _("_Quote")));
	add_with_indent (style_quote);

	style_no_quote = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
		gtk_radio_button_get_group (style_default), _("Do _Not Quote")));
	add_with_indent (style_no_quote);

	/* One line gap between sections */
	widget = gtk_label_new (" ");
	gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);

	html_format = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("_Format message in HTML")));
	gtk_box_pack_start (vbox, GTK_WIDGET (html_format), FALSE, FALSE, 0);

	bottom_posting = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("Start _typing at the bottom")));
	gtk_box_pack_start (vbox, GTK_WIDGET (bottom_posting), FALSE, FALSE, 0);

	top_signature = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("_Keep signature above the original message")));
	gtk_box_pack_start (vbox, GTK_WIDGET (top_signature), FALSE, FALSE, 0);

	/* One line gap between sections */
	widget = gtk_label_new (" ");
	gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);

	apply_template = GTK_CHECK_BUTTON (gtk_check_button_new_with_mnemonic (_("Apply t_emplate")));
	gtk_box_pack_start (vbox, GTK_WIDGET (apply_template), FALSE, FALSE, 0);

	last_tmpl_folder_uri = g_settings_get_string (settings, "alt-reply-template-folder-uri");
	last_tmpl_message_uid = g_settings_get_string (settings, "alt-reply-template-message-uid");

	templates = emcu_create_templates_combo (shell, last_tmpl_folder_uri, last_tmpl_message_uid);
	add_with_indent (templates);

	g_free (last_tmpl_folder_uri);
	g_free (last_tmpl_message_uid);

	preserve_message_subject = GTK_CHECK_BUTTON (gtk_check_button_new_with_mnemonic (_("Preserve original message S_ubject")));
	add_with_indent (preserve_message_subject);

	#undef add_with_indent

	gtk_widget_show_all (GTK_WIDGET (vbox));

	#define populate_label_with_text(lbl, txt) \
		g_object_set (G_OBJECT (lbl), \
			"ellipsize", PANGO_ELLIPSIZE_END, \
			"max-width-chars", 50, \
			"label", txt ? txt : "", \
			"tooltip-text", txt ? txt : "", \
			NULL);

	/* Reply to sender */
	to = camel_internet_address_new ();
	if (folder)
		postto = camel_nntp_address_new ();
	get_reply_sender (message, to, postto);

	if (postto && camel_address_length (CAMEL_ADDRESS (postto)) > 0) {
		address = camel_address_format (CAMEL_ADDRESS (postto));
	} else {
		address = camel_address_format (CAMEL_ADDRESS (to));
	}

	populate_label_with_text (sender_label, address);

	g_clear_object (&postto);
	g_clear_object (&to);
	g_free (address);

	/* Reply to list */
	to = camel_internet_address_new ();

	if (!get_reply_list (message, to)) {
		gtk_widget_set_sensitive (GTK_WIDGET (recip_list), FALSE);
	} else {
		can_reply_list = TRUE;

		address = camel_address_format (CAMEL_ADDRESS (to));
		populate_label_with_text (list_label, address);
		g_free (address);
	}

	g_clear_object (&to);

	/* Reply to all */
	to = camel_internet_address_new ();
	cc = camel_internet_address_new ();

	if (folder)
		postto = camel_nntp_address_new ();

	em_utils_get_reply_all (e_shell_get_registry (shell), message, to, cc, postto);

	if (postto && camel_address_length (CAMEL_ADDRESS (postto)) > 0) {
		n_addresses = camel_address_length (CAMEL_ADDRESS (postto));
		address = camel_address_format (CAMEL_ADDRESS (postto));
	} else {
		camel_address_cat (CAMEL_ADDRESS (to), CAMEL_ADDRESS (cc));
		n_addresses = camel_address_length (CAMEL_ADDRESS (to));
		address = camel_address_format (CAMEL_ADDRESS (to));
	}

	text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "one recipient", "%d recipients", n_addresses), n_addresses);
	populate_label_with_text (all_label, text);
	gtk_widget_set_tooltip_text (GTK_WIDGET (all_label), address);

	g_clear_object (&to);
	g_clear_object (&cc);
	g_clear_object (&postto);
	g_free (address);
	g_free (text);

	#undef populate_label_with_text

	/* Prefer reply-to-list */
	if (g_settings_get_boolean (settings, "composer-group-reply-to-list")) {
		if (can_reply_list)
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_list), TRUE);
		else if (n_addresses > 1)
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_all), TRUE);
		else
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_sender), TRUE);

	/* Prefer reply-to-all */
	} else {
		if (n_addresses > 1)
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_all), TRUE);
		else if (can_reply_list)
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_list), TRUE);
		else
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_sender), TRUE);
	}

	switch (g_settings_get_enum (settings, "alt-reply-style")) {
	case E_MAIL_REPLY_STYLE_UNKNOWN:
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_default), TRUE);
		break;
	case E_MAIL_REPLY_STYLE_QUOTED:
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_quote), TRUE);
		break;
	case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_no_quote), TRUE);
		break;
	case E_MAIL_REPLY_STYLE_ATTACH:
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_attach), TRUE);
		break;
	case E_MAIL_REPLY_STYLE_OUTLOOK:
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_inline), TRUE);
		break;
	}

	emcu_three_state_set_value (html_format, g_settings_get_enum (settings, "alt-reply-html-format"));
	emcu_three_state_set_value (bottom_posting, g_settings_get_enum (settings, "alt-reply-start-bottom"));
	emcu_three_state_set_value (top_signature, g_settings_get_enum (settings, "alt-reply-top-signature"));
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), g_settings_get_boolean (settings, "alt-reply-template-apply"));
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (preserve_message_subject), g_settings_get_boolean (settings, "alt-reply-template-preserve-subject"));

	if (!gtk_widget_get_sensitive (GTK_WIDGET (templates)))
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), FALSE);

	emcu_connect_three_state_changer (html_format);
	emcu_connect_three_state_changer (bottom_posting);
	emcu_connect_three_state_changer (top_signature);

	e_binding_bind_property (
		apply_template, "active",
		templates, "sensitive",
		G_BINDING_SYNC_CREATE);

	e_binding_bind_property (
		apply_template, "active",
		preserve_message_subject, "sensitive",
		G_BINDING_SYNC_CREATE);

	/* Enable the 'Reply Style' section only if not using Template */
	e_binding_bind_property (
		apply_template, "active",
		style_label, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		style_default, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		style_attach, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		style_inline, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		style_quote, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		style_no_quote, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	/* Similarly with other options, which don't work when using Templates */
	e_binding_bind_property (
		apply_template, "active",
		bottom_posting, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	e_binding_bind_property (
		apply_template, "active",
		top_signature, "sensitive",
		G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
		GtkTreeIter iter;
		AltReplyContext *context;
		EThreeState three_state;
		CamelFolder *template_folder = NULL;
		gchar *template_message_uid = NULL;

		context = g_slice_new0 (AltReplyContext);
		context->shell = g_object_ref (shell);
		context->alert_sink = g_object_ref (alert_sink);
		context->source_message = g_object_ref (message);
		context->folder = folder ? g_object_ref (folder) : NULL;
		context->source = source ? g_object_ref (source) : NULL;
		context->message_uid = g_strdup (message_uid);
		context->style = E_MAIL_REPLY_STYLE_UNKNOWN;
		context->flags = E_MAIL_REPLY_FLAG_FORCE_STYLE;

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_quote)))
			context->style = E_MAIL_REPLY_STYLE_QUOTED;
		else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_no_quote)))
			context->style = E_MAIL_REPLY_STYLE_DO_NOT_QUOTE;
		else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_attach)))
			context->style = E_MAIL_REPLY_STYLE_ATTACH;
		else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_inline)))
			context->style = E_MAIL_REPLY_STYLE_OUTLOOK;
		else
			context->flags = context->flags & (~E_MAIL_REPLY_FLAG_FORCE_STYLE);

		three_state = emcu_three_state_get_value (html_format);
		g_settings_set_enum (settings, "alt-reply-html-format", three_state);

		if (three_state == E_THREE_STATE_ON)
			context->flags |= E_MAIL_REPLY_FLAG_FORMAT_HTML;
		else if (three_state == E_THREE_STATE_OFF)
			context->flags |= E_MAIL_REPLY_FLAG_FORMAT_PLAIN;

		three_state = emcu_three_state_get_value (bottom_posting);
		g_settings_set_enum (settings, "alt-reply-start-bottom", three_state);

		if (three_state == E_THREE_STATE_ON)
			context->flags |= E_MAIL_REPLY_FLAG_BOTTOM_POSTING;
		else if (three_state == E_THREE_STATE_OFF)
			context->flags |= E_MAIL_REPLY_FLAG_TOP_POSTING;

		three_state = emcu_three_state_get_value (top_signature);
		g_settings_set_enum (settings, "alt-reply-top-signature", three_state);

		if (three_state == E_THREE_STATE_ON)
			context->flags |= E_MAIL_REPLY_FLAG_TOP_SIGNATURE;
		else if (three_state == E_THREE_STATE_OFF)
			context->flags |= E_MAIL_REPLY_FLAG_BOTTOM_SIGNATURE;

		g_settings_set_enum (settings, "alt-reply-style", context->style);
		g_settings_set_boolean (settings, "alt-reply-template-apply", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_template)));
		g_settings_set_boolean (settings, "alt-reply-template-preserve-subject", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (preserve_message_subject)));

		if (gtk_combo_box_get_active_iter (templates, &iter)) {
			gtk_tree_model_get (gtk_combo_box_get_model (templates), &iter,
				E_MAIL_TEMPLATES_STORE_COLUMN_FOLDER, &template_folder,
				E_MAIL_TEMPLATES_STORE_COLUMN_MESSAGE_UID, &template_message_uid,
				-1);
		}

		if (template_folder) {
			gchar *folder_uri;

			folder_uri = e_mail_folder_uri_from_folder (template_folder);
			g_settings_set_string (settings, "alt-reply-template-folder-uri", folder_uri ? folder_uri : "");
			g_free (folder_uri);
		}

		g_settings_set_string (settings, "alt-reply-template-message-uid", template_message_uid ? template_message_uid : "");

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_sender)))
			context->type = E_MAIL_REPLY_TO_SENDER;
		else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_list)))
			context->type = E_MAIL_REPLY_TO_LIST;
		else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_all)))
			context->type = E_MAIL_REPLY_TO_ALL;
		else
			g_warn_if_reached ();

		if (context->style == E_MAIL_REPLY_STYLE_UNKNOWN)
			context->style = default_style;

		context->template_preserve_subject = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (preserve_message_subject));

		if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_template))) {
			e_mail_templates_apply (context->source_message, context->folder, message_uid, template_folder, template_message_uid,
				NULL, alt_reply_template_applied_cb, context);

		} else {
			e_msg_composer_new (context->shell, alt_reply_composer_created_cb, context);
		}

		g_clear_object (&template_folder);
		g_free (template_message_uid);
	}

	gtk_widget_destroy (dialog);
	g_clear_object (&settings);
}

static void
em_utils_update_by_reply_flags (EContentEditor *cnt_editor,
				guint32 reply_flags) /* EMailReplyFlags */
{
	if ((reply_flags & (E_MAIL_REPLY_FLAG_TOP_POSTING | E_MAIL_REPLY_FLAG_BOTTOM_POSTING)) != 0) {
		e_content_editor_set_start_bottom (cnt_editor,
			(reply_flags & E_MAIL_REPLY_FLAG_TOP_POSTING) != 0 ?
			E_THREE_STATE_OFF : E_THREE_STATE_ON);
	}

	if ((reply_flags & (E_MAIL_REPLY_FLAG_TOP_SIGNATURE | E_MAIL_REPLY_FLAG_BOTTOM_SIGNATURE)) != 0) {
		e_content_editor_set_top_signature (cnt_editor,
			(reply_flags & E_MAIL_REPLY_FLAG_TOP_SIGNATURE) != 0 ?
			E_THREE_STATE_ON : E_THREE_STATE_OFF);
	}
}

/**
 * em_utils_reply_to_message:
 * @composer: an #EMsgComposer
 * @message: a #CamelMimeMessage
 * @folder: a #CamelFolder, or %NULL
 * @message_uid: the UID of @message, or %NULL
 * @type: the type of reply to create
 * @style: the reply style to use
 * @source: source to inherit view settings from
 * @address: used for E_MAIL_REPLY_TO_RECIPIENT @type
 * @reply_flags: bit-or of #EMailReplyFlags
 *
 * Creates a new composer ready to reply to @message.
 *
 * @folder and @message_uid may be supplied in order to update the message
 * flags once it has been replied to.
 **/
void
em_utils_reply_to_message (EMsgComposer *composer,
                           CamelMimeMessage *message,
                           CamelFolder *folder,
                           const gchar *message_uid,
                           EMailReplyType type,
                           EMailReplyStyle style,
                           EMailPartList *parts_list,
			   CamelInternetAddress *address,
			   EMailReplyFlags reply_flags)
{
	ESourceRegistry *registry;
	CamelInternetAddress *to, *cc;
	CamelNNTPAddress *postto = NULL;
	EShell *shell;
	ESourceMailCompositionReplyStyle prefer_reply_style = E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DEFAULT;
	ESource *source;
	EContentEditor *cnt_editor;
	gchar *identity_uid = NULL, *identity_name = NULL, *identity_address = NULL;
	guint32 flags;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));

	if ((reply_flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN | E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
		e_content_editor_set_html_mode (cnt_editor, (reply_flags & E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0);
	}

	em_utils_update_by_reply_flags (cnt_editor, reply_flags);

	to = camel_internet_address_new ();
	cc = camel_internet_address_new ();

	shell = e_msg_composer_get_shell (composer);
	registry = e_shell_get_registry (shell);

	if (type == E_MAIL_REPLY_TO_SENDER &&
	    !(reply_flags & E_MAIL_REPLY_FLAG_FORCE_SENDER_REPLY) &&
	    em_utils_sender_is_user (registry, message, TRUE)) {
		type = E_MAIL_REPLY_TO_ALL;
	}

	source = em_composer_utils_guess_identity_source (shell, message, folder, message_uid, &identity_name, &identity_address);

	if (source != NULL) {
		identity_uid = e_source_dup_uid (source);
		if (!(reply_flags & E_MAIL_REPLY_FLAG_FORCE_STYLE) &&
		    e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
			ESourceMailComposition *extension;

			extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
			prefer_reply_style = e_source_mail_composition_get_reply_style (extension);
		}

		g_object_unref (source);
	}

	flags = CAMEL_MESSAGE_ANSWERED | get_composer_mark_read_on_reply_flag ();

	if (!address && (type == E_MAIL_REPLY_TO_FROM || type == E_MAIL_REPLY_TO_SENDER) &&
	    folder && !emcu_folder_is_inbox (folder) && em_utils_folder_is_sent (registry, folder))
		type = E_MAIL_REPLY_TO_ALL;

	switch (type) {
	case E_MAIL_REPLY_TO_FROM:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_from (message, to, postto);
		break;
	case E_MAIL_REPLY_TO_RECIPIENT:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_recipient (message, to, postto, address);
		break;
	case E_MAIL_REPLY_TO_SENDER:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_sender (message, to, postto);
		break;
	case E_MAIL_REPLY_TO_LIST:
		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
		if (get_reply_list (message, to))
			break;
		/* falls through */
	case E_MAIL_REPLY_TO_ALL:
		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
		if (folder)
			postto = camel_nntp_address_new ();

		em_utils_get_reply_all (registry, message, to, cc, postto);
		break;
	}

	reply_setup_composer (composer, message, identity_uid, identity_name, identity_address, to, cc, folder, message_uid, postto);
	e_msg_composer_add_message_attachments (composer, message, TRUE);

	if (postto)
		g_object_unref (postto);
	g_object_unref (to);
	g_object_unref (cc);

	/* If there was no send-account override */
	if (!identity_uid) {
		EComposerHeaderTable *header_table;
		gchar *used_identity_uid;

		header_table = e_msg_composer_get_header_table (composer);
		used_identity_uid = e_composer_header_table_dup_identity_uid (header_table, NULL, NULL);

		if (used_identity_uid) {
			source = e_source_registry_ref_source (registry, used_identity_uid);
			if (source) {
				if (!(reply_flags & E_MAIL_REPLY_FLAG_FORCE_STYLE) &&
				    e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
					ESourceMailComposition *extension;

					extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
					prefer_reply_style = e_source_mail_composition_get_reply_style (extension);
				}

				g_object_unref (source);
			}
		}

		g_free (used_identity_uid);
	}

	switch (prefer_reply_style) {
		case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DEFAULT:
			/* Do nothing, keep the passed-in reply style. */
			break;
		case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_QUOTED:
			style = E_MAIL_REPLY_STYLE_QUOTED;
			break;
		case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DO_NOT_QUOTE:
			style = E_MAIL_REPLY_STYLE_DO_NOT_QUOTE;
			break;
		case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_ATTACH:
			style = E_MAIL_REPLY_STYLE_ATTACH;
			break;
		case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_OUTLOOK:
			style = E_MAIL_REPLY_STYLE_OUTLOOK;
			break;
	}

	composer_set_body (composer, message, style, parts_list);

	if (folder)
		emu_set_source_headers (composer, folder, message_uid, flags);

	/* because some reply types can change recipients after the composer is populated */
	em_utils_apply_send_account_override_to_composer (composer, folder);

	/* This is required to be done (also) at the end */
	em_utils_update_by_reply_flags (cnt_editor, reply_flags);

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));

	g_free (identity_uid);
	g_free (identity_name);
	g_free (identity_address);
}

static void
post_header_clicked_cb (EComposerPostHeader *header,
                        EMailSession *session)
{
	GtkTreeSelection *selection;
	EMFolderSelector *selector;
	EMFolderTreeModel *model;
	EMFolderTree *folder_tree;
	GtkWidget *dialog;
	GList *list;
	const gchar *caption;

	/* FIXME Limit the folder tree to the NNTP account? */
	model = em_folder_tree_model_get_default ();

	dialog = em_folder_selector_new (
		/* FIXME GTK_WINDOW (composer) */ NULL, model);

	gtk_window_set_title (GTK_WINDOW (dialog), _("Posting destination"));

	selector = EM_FOLDER_SELECTOR (dialog);
	em_folder_selector_set_can_create (selector, TRUE);

	caption = _("Choose folders to post the message to.");
	em_folder_selector_set_caption (selector, caption);

	folder_tree = em_folder_selector_get_folder_tree (selector);

	em_folder_tree_set_excluded (
		folder_tree,
		EMFT_EXCLUDE_NOSELECT |
		EMFT_EXCLUDE_VIRTUAL |
		EMFT_EXCLUDE_VTRASH);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	list = e_composer_post_header_get_folders (header);
	em_folder_tree_set_selected_list (folder_tree, list, FALSE);
	g_list_foreach (list, (GFunc) g_free, NULL);
	g_list_free (list);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) {
		/* Prevent the header's "custom" flag from being reset,
		 * which is what the default method will do next. */
		g_signal_stop_emission_by_name (header, "clicked");
		goto exit;
	}

	list = em_folder_tree_get_selected_uris (folder_tree);
	e_composer_post_header_set_folders (header, list);
	g_list_foreach (list, (GFunc) g_free, NULL);
	g_list_free (list);

exit:
	gtk_widget_destroy (dialog);
}

/**
 * em_configure_new_composer:
 * @composer: a newly created #EMsgComposer
 *
 * Integrates a newly created #EMsgComposer into the mail backend.  The
 * composer can't link directly to the mail backend without introducing
 * circular library dependencies, so this function finishes configuring
 * things the #EMsgComposer instance can't do itself.
 **/
void
em_configure_new_composer (EMsgComposer *composer,
                           EMailSession *session)
{
	EComposerHeaderTable *table;
	EComposerHeaderType header_type;
	EComposerHeader *header;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (E_IS_MAIL_SESSION (session));

	header_type = E_COMPOSER_HEADER_POST_TO;
	table = e_msg_composer_get_header_table (composer);
	header = e_composer_header_table_get_header (table, header_type);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_recipients), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_identity), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_plugins), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_subject), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_unwanted_html), session);

	g_signal_connect (
		composer, "send",
		G_CALLBACK (em_utils_composer_send_cb), session);

	g_signal_connect (
		composer, "save-to-drafts",
		G_CALLBACK (em_utils_composer_save_to_drafts_cb), session);

	g_signal_connect (
		composer, "save-to-outbox",
		G_CALLBACK (em_utils_composer_save_to_outbox_cb), session);

	g_signal_connect (
		composer, "print",
		G_CALLBACK (em_utils_composer_print_cb), session);

	/* Handle "Post To:" button clicks, which displays a folder tree
	 * widget.  The composer doesn't know about folder tree widgets,
	 * so it can't handle this itself.
	 *
	 * Note: This is a G_SIGNAL_RUN_LAST signal, which allows us to
	 *       stop the signal emission if the user cancels or closes
	 *       the folder selector dialog.  See the handler function. */
	g_signal_connect (
		header, "clicked",
		G_CALLBACK (post_header_clicked_cb), session);
}

/* free returned pointer with g_object_unref(), if not NULL */
ESource *
em_utils_check_send_account_override (EShell *shell,
                                      CamelMimeMessage *message,
                                      CamelFolder *folder,
				      gchar **out_alias_name,
				      gchar **out_alias_address)
{
	EMailBackend *mail_backend;
	EMailSendAccountOverride *account_override;
	CamelInternetAddress *to = NULL, *cc = NULL, *bcc = NULL;
	gchar *folder_uri = NULL, *account_uid, *alias_name = NULL, *alias_address = NULL;
	ESource *account_source = NULL;
	ESourceRegistry *source_registry;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);

	if (!message && !folder)
		return NULL;

	if (message) {
		to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
		cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
		bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
	}

	mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (shell, "mail"));
	g_return_val_if_fail (mail_backend != NULL, NULL);

	if (folder)
		folder_uri = e_mail_folder_uri_from_folder (folder);

	source_registry = e_shell_get_registry (shell);
	account_override = e_mail_backend_get_send_account_override (mail_backend);
	account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc, &alias_name, &alias_address);

	while (account_uid) {
		account_source = e_source_registry_ref_source (source_registry, account_uid);
		if (account_source)
			break;

		/* stored send account override settings contain a reference
		 * to a dropped account, thus cleanup it now */
		e_mail_send_account_override_remove_for_account_uid (account_override, account_uid, alias_name, alias_address);

		g_free (account_uid);
		g_free (alias_name);
		g_free (alias_address);

		alias_name = NULL;
		alias_address = NULL;

		account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc, &alias_name, &alias_address);
	}

	if (out_alias_name)
		*out_alias_name = alias_name;
	else
		g_free (alias_name);

	if (out_alias_address)
		*out_alias_address = alias_address;
	else
		g_free (alias_address);

	g_free (folder_uri);
	g_free (account_uid);

	return account_source;
}

void
em_utils_apply_send_account_override_to_composer (EMsgComposer *composer,
                                                  CamelFolder *folder)
{
	CamelMimeMessage *message;
	EComposerHeaderTable *header_table;
	EShell *shell;
	ESource *source;
	gchar *alias_name = NULL, *alias_address = NULL;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));

	shell = e_msg_composer_get_shell (composer);
	message = em_utils_get_composer_recipients_as_message (composer);
	source = em_utils_check_send_account_override (shell, message, folder, &alias_name, &alias_address);
	g_clear_object (&message);

	if (!source)
		return;

	header_table = e_msg_composer_get_header_table (composer);
	e_composer_header_table_set_identity_uid (header_table, e_source_get_uid (source), alias_name, alias_address);

	g_object_unref (source);
	g_free (alias_name);
	g_free (alias_address);
}

void
em_utils_add_installed_languages (GtkComboBoxText *combo)
{
	const ESupportedLocales *supported_locales;
	GHashTable *locales;
	GList *langs = NULL, *link;
	gboolean has_en_us = FALSE;
	gint ii, n_langs = 0;

	g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo));

	supported_locales = e_util_get_supported_locales ();
	locales = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

	for (ii = 0; supported_locales[ii].code; ii++) {
		const gchar *locale = supported_locales[ii].locale;

		if (locale) {
			gchar *name;

			name = e_util_get_language_name (locale);

			if (name && *name) {
				g_hash_table_insert (locales, name, (gpointer) locale);
			} else {
				g_free (name);
				g_hash_table_insert (locales, g_strdup (locale), (gpointer) locale);
			}

			has_en_us = has_en_us || g_strcmp0 (locale, "en_US") == 0;
		}
	}

	if (!has_en_us) {
		const gchar *locale = "C";
		gchar *name = e_util_get_language_name ("en_US");

		if (name && *name) {
			g_hash_table_insert (locales, name, (gpointer) locale);
		} else {
			g_free (name);
			g_hash_table_insert (locales, g_strdup ("en_US"), (gpointer) locale);
		}
	}

	langs = g_hash_table_get_keys (locales);
	langs = g_list_sort (langs, (GCompareFunc) g_utf8_collate);

	for (link = langs; link; link = g_list_next (link)) {
		const gchar *lang_name = link->data;

		if (lang_name) {
			const gchar *locale = g_hash_table_lookup (locales, lang_name);

			gtk_combo_box_text_append (combo, locale, lang_name);
			n_langs++;
		}
	}

	g_hash_table_destroy (locales);
	g_list_free (langs);

	if (n_langs > 10)
		gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (combo), 5);
}
