/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>  
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */

#include <config.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

#include <glib.h>

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

#include <libedataserver/e-data-server-util.h>
#include <libedataserver/e-flag.h>

#include <camel/camel-url.h>
#include <camel/camel-operation.h>

#include "misc/e-gui-utils.h"
#include "e-util/e-error.h"
#include "e-util/e-icon-factory.h"
#include "widgets/misc/e-alert-activity.h"

#include "mail-config.h"
#include "mail-session.h"
#include "mail-mt.h"

#include "e-mail-shell-module.h"

/*#define MALLOC_CHECK*/
#define LOG_OPS
#define LOG_LOCKS
#define d(x)

static void set_stop(int sensitive);
static void mail_operation_status(struct _CamelOperation *op, const char *what, int pc, void *data);

#ifdef LOG_LOCKS
#define MAIL_MT_LOCK(x) (log_locks?fprintf(log, "%" G_GINT64_MODIFIER "x: lock " # x "\n", e_util_pthread_id(pthread_self())):0, pthread_mutex_lock(&x))
#define MAIL_MT_UNLOCK(x) (log_locks?fprintf(log, "%" G_GINT64_MODIFIER "x: unlock " # x "\n", e_util_pthread_id(pthread_self())): 0, pthread_mutex_unlock(&x))
#else
#define MAIL_MT_LOCK(x) pthread_mutex_lock(&x)
#define MAIL_MT_UNLOCK(x) pthread_mutex_unlock(&x)
#endif

/* background operation status stuff */
struct _MailMsgPrivate {
	int activity_state;	/* sigh sigh sigh, we need to keep track of the state external to the
				   pointer itself for locking/race conditions */
	EActivity *activity;
	GtkWidget *error;
	gboolean cancelable;
};

/* mail_msg stuff */
#ifdef LOG_OPS
static FILE *log;
static int log_ops, log_locks, log_init;
#endif

static unsigned int mail_msg_seq; /* sequence number of each message */
static GHashTable *mail_msg_active_table; /* table of active messages, must hold mail_msg_lock to access */
static pthread_mutex_t mail_msg_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t mail_msg_cond = PTHREAD_COND_INITIALIZER;

MailAsyncEvent *mail_async_event;

gpointer
mail_msg_new (MailMsgInfo *info)
{
	MailMsg *msg;

	MAIL_MT_LOCK(mail_msg_lock);

#if defined(LOG_OPS) || defined(LOG_LOCKS)
	if (!log_init) {
		time_t now = time(NULL);

		log_init = TRUE;
		log_ops = getenv("EVOLUTION_MAIL_LOG_OPS") != NULL;
		log_locks = getenv("EVOLUTION_MAIL_LOG_LOCKS") != NULL;
		if (log_ops || log_locks) {
			log = fopen("evolution-mail-ops.log", "w+");
			if (log) {
				setvbuf(log, NULL, _IOLBF, 0);
				fprintf(log, "Started evolution-mail: %s\n", ctime(&now));
				g_warning("Logging mail operations to evolution-mail-ops.log");

				if (log_ops)
					fprintf(log, "Logging async operations\n");

				if (log_locks) {
					fprintf(log, "%" G_GINT64_MODIFIER "x: lock mail_msg_lock\n", e_util_pthread_id(pthread_self()));
				}
			} else {
				g_warning ("Could not open log file: %s", strerror(errno));
				log_ops = log_locks = FALSE;
			}
		}
	}
#endif
	msg = g_slice_alloc0 (info->size);
	msg->info = info;
	msg->ref_count = 1;
	msg->seq = mail_msg_seq++;
	msg->cancel = camel_operation_new(mail_operation_status, GINT_TO_POINTER(msg->seq));
	camel_exception_init(&msg->ex);
	msg->priv = g_slice_new0 (MailMsgPrivate);
	msg->priv->cancelable = TRUE;

	g_hash_table_insert(mail_msg_active_table, GINT_TO_POINTER(msg->seq), msg);

	d(printf("New message %p\n", msg));

#ifdef LOG_OPS
	if (log_ops)
		fprintf(log, "%p: New\n", msg);
#endif
	MAIL_MT_UNLOCK(mail_msg_lock);

	return msg;
}

static void
end_event_callback (CamelObject *o, EActivity *activity, void *error)
{
	if (error == NULL) {
		e_activity_complete (activity);
		g_object_unref (activity);
	} else {
		if (activity != NULL)
			g_object_unref (activity);
		activity = e_alert_activity_new_warning (error);
		e_shell_module_add_activity (mail_shell_module, activity);
		g_object_unref (activity);
	}
}


#ifdef MALLOC_CHECK
#include <mcheck.h>

static void
checkmem(void *p)
{
	if (p) {
		int status = mprobe(p);

		switch (status) {
		case MCHECK_HEAD:
			printf("Memory underrun at %p\n", p);
			abort();
		case MCHECK_TAIL:
			printf("Memory overrun at %p\n", p);
			abort();
		case MCHECK_FREE:
			printf("Double free %p\n", p);
			abort();
		}
	}
}
#endif

static void
mail_msg_free (MailMsg *mail_msg)
{
	if (mail_msg->priv->activity != NULL)
		g_object_unref (mail_msg->priv->activity);

	if (mail_msg->cancel != NULL) {
		camel_operation_mute (mail_msg->cancel);
		camel_operation_unref (mail_msg->cancel);
	}

	camel_exception_clear (&mail_msg->ex);
	g_slice_free (MailMsgPrivate, mail_msg->priv);
	g_slice_free1 (mail_msg->info->size, mail_msg);
}

gpointer
mail_msg_ref (gpointer msg)
{
	MailMsg *mail_msg = msg;

	g_return_val_if_fail (mail_msg != NULL, msg);
	g_return_val_if_fail (mail_msg->ref_count > 0, msg);

	g_atomic_int_add (&mail_msg->ref_count, 1);
	return msg;
}

void
mail_msg_unref (gpointer msg)
{
	MailMsg *mail_msg = msg;
	EActivity *activity = NULL;
	GtkWidget *error = NULL;

	g_return_if_fail (mail_msg != NULL);
	g_return_if_fail (mail_msg->ref_count > 0);

	if (g_atomic_int_exchange_and_add (&mail_msg->ref_count, -1) > 1)
		return;

#ifdef MALLOC_CHECK
	checkmem(mail_msg);
	checkmem(mail_msg->cancel);
	checkmem(mail_msg->priv);
#endif
	d(printf("Free message %p\n", msg));

	if (mail_msg->info->free)
		mail_msg->info->free(mail_msg);

	MAIL_MT_LOCK(mail_msg_lock);

#ifdef LOG_OPS
	if (log_ops) {
		const gchar *description;

		description = camel_exception_get_description (&mail_msg->ex);
		if (description == NULL)
			description = "None";
		fprintf(log, "%p: Free  (exception `%s')\n", msg, description);
	}
#endif
	g_hash_table_remove (
		mail_msg_active_table, GINT_TO_POINTER (mail_msg->seq));
	pthread_cond_broadcast (&mail_msg_cond);

	/* We need to make sure we dont lose a reference here YUCK YUCK */
	/* This is tightly integrated with the code in do_op_status,
	   as it closely relates to the CamelOperation setup in msg_new() above */
	if (mail_msg->priv->activity_state == 1) {
		/* tell the other to free it itself */
		mail_msg->priv->activity_state = 3;
		MAIL_MT_UNLOCK(mail_msg_lock);
		return;
	} else {
		activity = mail_msg->priv->activity;
		if (activity != NULL)
			g_object_ref (activity);
		error = mail_msg->priv->error;
	}

	MAIL_MT_UNLOCK(mail_msg_lock);

	mail_msg_free (mail_msg);

	if (activity != NULL)
		mail_async_event_emit (
			mail_async_event, MAIL_ASYNC_GUI,
			(MailAsyncFunc) end_event_callback,
			NULL, activity, error);
}

/* hash table of ops->dialogue of active errors */
static GHashTable *active_errors = NULL;

static void error_destroy(GtkObject *o, void *data)
{
	g_hash_table_remove(active_errors, data);
}

static void error_response(GtkObject *o, int button, void *data)
{
	gtk_widget_destroy((GtkWidget *)o);
}

void
mail_msg_check_error (gpointer msg)
{
	MailMsg *m = msg;
	char *what;
	GtkDialog *gd;

#ifdef MALLOC_CHECK
	checkmem(m);
	checkmem(m->cancel);
	checkmem(m->priv);
#endif

	/* don't report any errors if we are not in interactive mode */
	if (!mail_session_get_interactive ())
		return;

	if (!camel_exception_is_set(&m->ex)
	    || m->ex.id == CAMEL_EXCEPTION_USER_CANCEL
	    || m->ex.id == CAMEL_EXCEPTION_FOLDER_INVALID_UID)
		return;

	if (active_errors == NULL)
		active_errors = g_hash_table_new(NULL, NULL);

	/* check to see if we have dialogue already running for this operation */
	/* we key on the operation pointer, which is at least accurate enough
	   for the operation type, although it could be on a different object. */
	if (g_hash_table_lookup(active_errors, m->info)) {
		g_message("Error occurred while existing dialogue active:\n%s", camel_exception_get_description(&m->ex));
		return;
	}

	if (m->info->desc
	    && (what = m->info->desc (m))) {
		gd = (GtkDialog *)e_error_new(NULL, "mail:async-error", what, camel_exception_get_description(&m->ex), NULL);
		g_free(what);
	} else
		gd = (GtkDialog *)e_error_new(NULL, "mail:async-error-nodescribe", camel_exception_get_description(&m->ex), NULL);

	g_hash_table_insert(active_errors, m->info, gd);
	g_signal_connect(gd, "response", G_CALLBACK(error_response), m->info);
	g_signal_connect(gd, "destroy", G_CALLBACK(error_destroy), m->info);
	if (m->priv->cancelable)
		m->priv->error = (GtkWidget *) gd;
	else
		gtk_widget_show((GtkWidget *)gd);
}

void mail_msg_cancel(unsigned int msgid)
{
	MailMsg *m;

	MAIL_MT_LOCK(mail_msg_lock);
	m = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid));

	if (m && m->cancel)
		camel_operation_cancel(m->cancel);

	MAIL_MT_UNLOCK(mail_msg_lock);
}


/* waits for a message to be finished processing (freed)
   the messageid is from MailMsg->seq */
void mail_msg_wait(unsigned int msgid)
{
	MailMsg *m;

	if (mail_in_main_thread ()) {
		MAIL_MT_LOCK(mail_msg_lock);
		m = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid));
		while (m) {
			MAIL_MT_UNLOCK(mail_msg_lock);
			gtk_main_iteration();
			MAIL_MT_LOCK(mail_msg_lock);
			m = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid));
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	} else {
		MAIL_MT_LOCK(mail_msg_lock);
		m = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid));
		while (m) {
			pthread_cond_wait(&mail_msg_cond, &mail_msg_lock);
			m = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid));
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	}
}

int mail_msg_active(unsigned int msgid)
{
	int active;

	MAIL_MT_LOCK(mail_msg_lock);
	if (msgid == (unsigned int)-1)
		active = g_hash_table_size(mail_msg_active_table) > 0;
	else
		active = g_hash_table_lookup(mail_msg_active_table, GINT_TO_POINTER(msgid)) != NULL;
	MAIL_MT_UNLOCK(mail_msg_lock);

	return active;
}

void mail_msg_wait_all(void)
{
	if (mail_in_main_thread ()) {
		MAIL_MT_LOCK(mail_msg_lock);
		while (g_hash_table_size(mail_msg_active_table) > 0) {
			MAIL_MT_UNLOCK(mail_msg_lock);
			gtk_main_iteration();
			MAIL_MT_LOCK(mail_msg_lock);
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	} else {
		MAIL_MT_LOCK(mail_msg_lock);
		while (g_hash_table_size(mail_msg_active_table) > 0) {
			pthread_cond_wait(&mail_msg_cond, &mail_msg_lock);
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	}
}

/* **************************************** */

static GHookList cancel_hook_list;

GHook *
mail_cancel_hook_add (GHookFunc func, gpointer data)
{
	GHook *hook;

	MAIL_MT_LOCK (mail_msg_lock);

	if (!cancel_hook_list.is_setup)
		g_hook_list_init (&cancel_hook_list, sizeof (GHook));

	hook = g_hook_alloc (&cancel_hook_list);
	hook->func = func;
	hook->data = data;

	g_hook_append (&cancel_hook_list, hook);

	MAIL_MT_UNLOCK (mail_msg_lock);

	return hook;
}

void
mail_cancel_hook_remove (GHook *hook)
{
	MAIL_MT_LOCK (mail_msg_lock);

	g_return_if_fail (cancel_hook_list.is_setup);
	g_hook_destroy_link (&cancel_hook_list, hook);

	MAIL_MT_UNLOCK (mail_msg_lock);
}

void
mail_cancel_all (void)
{
	camel_operation_cancel (NULL);

	MAIL_MT_LOCK (mail_msg_lock);

	if (cancel_hook_list.is_setup)
		g_hook_list_invoke (&cancel_hook_list, FALSE);

	MAIL_MT_UNLOCK (mail_msg_lock);
}

void
mail_msg_set_cancelable (gpointer msg, gboolean status)
{
	MailMsg *mail_msg = msg;

	mail_msg->priv->cancelable = status;
}

static guint idle_source_id = 0;
G_LOCK_DEFINE_STATIC (idle_source_id);
static GAsyncQueue *main_loop_queue = NULL;
static GAsyncQueue *msg_reply_queue = NULL;
static GThread *main_thread = NULL;

static gboolean
mail_msg_idle_cb (void)
{
	MailMsg *msg;

	g_return_val_if_fail (main_loop_queue != NULL, FALSE);
	g_return_val_if_fail (msg_reply_queue != NULL, FALSE);

	G_LOCK (idle_source_id);
	idle_source_id = 0;
	G_UNLOCK (idle_source_id);

	/* check the main loop queue */
	while ((msg = g_async_queue_try_pop (main_loop_queue)) != NULL) {
		if (msg->info->exec != NULL)
			msg->info->exec (msg);
		if (msg->info->done != NULL)
			msg->info->done (msg);
		mail_msg_unref (msg);
	}

	/* check the reply queue */
	while ((msg = g_async_queue_try_pop (msg_reply_queue)) != NULL) {
		if (msg->info->done != NULL)
			msg->info->done (msg);
		mail_msg_check_error (msg);
		mail_msg_unref (msg);
	}

	return FALSE;
}

static void
mail_msg_proxy (MailMsg *msg)
{
	if (msg->info->desc != NULL && msg->cancel) {
		gchar *text = msg->info->desc (msg);
		camel_operation_register (msg->cancel);
		camel_operation_start (msg->cancel, "%s", text);
		g_free (text);
	}

	if (msg->info->exec != NULL) {
		mail_enable_stop ();
		msg->info->exec (msg);
		mail_disable_stop ();
	}

	if (msg->info->desc != NULL && msg->cancel) {
		camel_operation_end (msg->cancel);
		camel_operation_unregister (msg->cancel);
		MAIL_MT_LOCK (mail_msg_lock);
		camel_operation_unref (msg->cancel);
		msg->cancel = NULL;
		MAIL_MT_UNLOCK (mail_msg_lock);
	}

	g_async_queue_push (msg_reply_queue, msg);

	G_LOCK (idle_source_id);
	if (idle_source_id == 0)
		idle_source_id = g_idle_add (
			(GSourceFunc) mail_msg_idle_cb, NULL);
	G_UNLOCK (idle_source_id);
}

void
mail_msg_cleanup (void)
{
	mail_msg_wait_all();

	G_LOCK (idle_source_id);
	if (idle_source_id != 0) {
		GSource *source;

		/* Cancel the idle source. */
		source = g_main_context_find_source_by_id (
			g_main_context_default (), idle_source_id);
		g_source_destroy (source);
		idle_source_id = 0;
	}
	G_UNLOCK (idle_source_id);

	g_async_queue_unref (main_loop_queue);
	main_loop_queue = NULL;

	g_async_queue_unref (msg_reply_queue);
	msg_reply_queue = NULL;
}

void
mail_msg_init (void)
{
	main_loop_queue = g_async_queue_new ();
	msg_reply_queue = g_async_queue_new ();

	mail_msg_active_table = g_hash_table_new (NULL, NULL);
	main_thread = g_thread_self ();

	mail_async_event = mail_async_event_new ();
}

static gint
mail_msg_compare (const MailMsg *msg1, const MailMsg *msg2)
{
	gint priority1 = msg1->priority;
	gint priority2 = msg2->priority;

	if (priority1 == priority2)
		return 0;

	return (priority1 < priority2) ? 1 : -1;
}

static gpointer
create_thread_pool (gpointer data)
{
	GThreadPool *thread_pool;
	gint max_threads = GPOINTER_TO_INT (data);

	/* once created, run forever */
	thread_pool = g_thread_pool_new (
		(GFunc) mail_msg_proxy, NULL, max_threads, FALSE, NULL);
	g_thread_pool_set_sort_function (
		thread_pool, (GCompareDataFunc) mail_msg_compare, NULL);

	return thread_pool;
}

void
mail_msg_main_loop_push (gpointer msg)
{
	g_async_queue_push_sorted (main_loop_queue, msg,
		(GCompareDataFunc) mail_msg_compare, NULL);

	G_LOCK (idle_source_id);
	if (idle_source_id == 0)
		idle_source_id = g_idle_add (
			(GSourceFunc) mail_msg_idle_cb, NULL);
	G_UNLOCK (idle_source_id);
}

void
mail_msg_unordered_push (gpointer msg)
{
	static GOnce once = G_ONCE_INIT;

	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (10));

	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}

void
mail_msg_fast_ordered_push (gpointer msg)
{
	static GOnce once = G_ONCE_INIT;

	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));

	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}

void
mail_msg_slow_ordered_push (gpointer msg)
{
	static GOnce once = G_ONCE_INIT;

	g_once (&once, (GThreadFunc) create_thread_pool, GINT_TO_POINTER (1));

	g_thread_pool_push ((GThreadPool *) once.retval, msg, NULL);
}

gboolean
mail_in_main_thread (void)
{
	return (g_thread_self () == main_thread);
}

/* ********************************************************************** */

/* locks */
static pthread_mutex_t status_lock = PTHREAD_MUTEX_INITIALIZER;

/* ********************************************************************** */

struct _proxy_msg {
	MailMsg base;

	MailAsyncEvent *ea;
	mail_async_event_t type;

	pthread_t thread;
	int have_thread;

	MailAsyncFunc func;
	void *o;
	void *event_data;
	void *data;
};

static void
do_async_event(struct _proxy_msg *m)
{
	m->thread = pthread_self();
	m->have_thread = TRUE;
	m->func(m->o, m->event_data, m->data);
	m->have_thread = FALSE;

	g_mutex_lock(m->ea->lock);
	m->ea->tasks = g_slist_remove(m->ea->tasks, m);
	g_mutex_unlock(m->ea->lock);
}

static int
idle_async_event(void *mm)
{
	do_async_event(mm);
	mail_msg_unref(mm);

	return FALSE;
}

static MailMsgInfo async_event_info = {
	sizeof (struct _proxy_msg),
	(MailMsgDescFunc) NULL,
	(MailMsgExecFunc) do_async_event,
	(MailMsgDoneFunc) NULL,
	(MailMsgFreeFunc) NULL
};

MailAsyncEvent *mail_async_event_new(void)
{
	MailAsyncEvent *ea;

	ea = g_malloc0(sizeof(*ea));
	ea->lock = g_mutex_new();

	return ea;
}

int mail_async_event_emit(MailAsyncEvent *ea, mail_async_event_t type, MailAsyncFunc func, void *o, void *event_data, void *data)
{
	struct _proxy_msg *m;
	int id;

	/* we dont have a reply port for this, we dont care when/if it gets executed, just queue it */
	m = mail_msg_new(&async_event_info);
	m->func = func;
	m->o = o;
	m->event_data = event_data;
	m->data = data;
	m->ea = ea;
	m->type = type;
	m->have_thread = FALSE;

	id = m->base.seq;
	g_mutex_lock(ea->lock);
	ea->tasks = g_slist_prepend(ea->tasks, m);
	g_mutex_unlock(ea->lock);

	/* We use an idle function instead of our own message port only because the
	   gui message ports's notification buffer might overflow and deadlock us */
	if (type == MAIL_ASYNC_GUI) {
		if (mail_in_main_thread ())
			g_idle_add(idle_async_event, m);
		else
			mail_msg_main_loop_push(m);
	} else
		mail_msg_fast_ordered_push (m);

	return id;
}

int mail_async_event_destroy(MailAsyncEvent *ea)
{
	int id;
	pthread_t thread = pthread_self();
	struct _proxy_msg *m;

	g_mutex_lock(ea->lock);
	while (ea->tasks) {
		m = ea->tasks->data;
		id = m->base.seq;
		if (m->have_thread && pthread_equal(m->thread, thread)) {
			g_warning("Destroying async event from inside an event, returning EDEADLK");
			g_mutex_unlock(ea->lock);
			errno = EDEADLK;
			return -1;
		}
		g_mutex_unlock(ea->lock);
		mail_msg_wait(id);
		g_mutex_lock(ea->lock);
	}
	g_mutex_unlock(ea->lock);

	g_mutex_free(ea->lock);
	g_free(ea);

	return 0;
}

/* ********************************************************************** */

struct _call_msg {
	MailMsg base;

	mail_call_t type;
	MailMainFunc func;
	void *ret;
	va_list ap;
	EFlag *done;
};

static void
do_call(struct _call_msg *m)
{
	void *p1, *p2, *p3, *p4, *p5;
	int i1;
	va_list ap;

	G_VA_COPY(ap, m->ap);

	switch(m->type) {
	case MAIL_CALL_p_p:
		p1 = va_arg(ap, void *);
		m->ret = m->func(p1);
		break;
	case MAIL_CALL_p_pp:
		p1 = va_arg(ap, void *);
		p2 = va_arg(ap, void *);
		m->ret = m->func(p1, p2);
		break;
	case MAIL_CALL_p_ppp:
		p1 = va_arg(ap, void *);
		p2 = va_arg(ap, void *);
		p3 = va_arg(ap, void *);
		m->ret = m->func(p1, p2, p3);
		break;
	case MAIL_CALL_p_pppp:
		p1 = va_arg(ap, void *);
		p2 = va_arg(ap, void *);
		p3 = va_arg(ap, void *);
		p4 = va_arg(ap, void *);
		m->ret = m->func(p1, p2, p3, p4);
		break;
	case MAIL_CALL_p_ppppp:
		p1 = va_arg(ap, void *);
		p2 = va_arg(ap, void *);
		p3 = va_arg(ap, void *);
		p4 = va_arg(ap, void *);
		p5 = va_arg(ap, void *);
		m->ret = m->func(p1, p2, p3, p4, p5);
		break;
	case MAIL_CALL_p_ppippp:
		p1 = va_arg(ap, void *);
		p2 = va_arg(ap, void *);
		i1 = va_arg(ap, int);
		p3 = va_arg(ap, void *);
		p4 = va_arg(ap, void *);
		p5 = va_arg(ap, void *);
		m->ret = m->func(p1, p2, i1, p3, p4, p5);
		break;
	}

	if (m->done != NULL)
		e_flag_set (m->done);
}

static MailMsgInfo mail_call_info = {
	sizeof (struct _call_msg),
	(MailMsgDescFunc) NULL,
	(MailMsgExecFunc) do_call,
	(MailMsgDoneFunc) NULL,
	(MailMsgFreeFunc) NULL
};

void *
mail_call_main (mail_call_t type, MailMainFunc func, ...)
{
	struct _call_msg *m;
	void *ret;
	va_list ap;

	va_start(ap, func);

	m = mail_msg_new (&mail_call_info);
	m->type = type;
	m->func = func;
	G_VA_COPY(m->ap, ap);

	if (mail_in_main_thread ())
		do_call (m);
	else {
		mail_msg_ref (m);
		m->done = e_flag_new ();
		mail_msg_main_loop_push (m);
		e_flag_wait (m->done);
		e_flag_free (m->done);
	}

	va_end(ap);

	ret = m->ret;
	mail_msg_unref (m);

	return ret;
}

/* ********************************************************************** */
/* locked via status_lock */
static int busy_state;

static void
do_set_busy(MailMsg *mm)
{
	set_stop(busy_state > 0);
}

static MailMsgInfo set_busy_info = {
	sizeof (MailMsg),
	(MailMsgDescFunc) NULL,
	(MailMsgExecFunc) do_set_busy,
	(MailMsgDoneFunc) NULL,
	(MailMsgFreeFunc) NULL
};

void mail_enable_stop(void)
{
	MailMsg *m;

	MAIL_MT_LOCK(status_lock);
	busy_state++;
	if (busy_state == 1) {
		m = mail_msg_new(&set_busy_info);
		mail_msg_main_loop_push(m);
	}
	MAIL_MT_UNLOCK(status_lock);
}

void mail_disable_stop(void)
{
	MailMsg *m;

	MAIL_MT_LOCK(status_lock);
	busy_state--;
	if (busy_state == 0) {
		m = mail_msg_new(&set_busy_info);
		mail_msg_main_loop_push(m);
	}
	MAIL_MT_UNLOCK(status_lock);
}

/* ******************************************************************************** */

struct _op_status_msg {
	MailMsg base;

	struct _CamelOperation *op;
	char *what;
	int pc;
	void *data;
};

static void
op_status_exec (struct _op_status_msg *m)
{
	MailMsg *msg;
	MailMsgPrivate *data;
	char *out, *p, *o, c;
	int pc;

	g_return_if_fail (mail_in_main_thread ());

	MAIL_MT_LOCK (mail_msg_lock);

	msg = g_hash_table_lookup (mail_msg_active_table, m->data);

	if (msg == NULL) {
		MAIL_MT_UNLOCK (mail_msg_lock);
		return;
	}

	data = msg->priv;

	out = alloca (strlen (m->what) * 2 + 1);
	o = out;
	p = m->what;
	while ((c = *p++)) {
		if (c == '%')
			*o++ = '%';
		*o++ = c;
	}
	*o = 0;

	pc = m->pc;

	if (data->activity == NULL) {
		char *what;

		/* its being created/removed?  well leave it be */
		if (data->activity_state == 1 || data->activity_state == 3) {
			MAIL_MT_UNLOCK (mail_msg_lock);
			return;
		} else {
			data->activity_state = 1;

			MAIL_MT_UNLOCK (mail_msg_lock);
			if (msg->info->desc)
				what = msg->info->desc (msg);
			else if (m->what)
				what = g_strdup (m->what);
			/* uncommenting because message is not very useful for a user, see bug 271734*/
			else {
				what = g_strdup("");
			}

			data->activity = e_activity_new (what);
			e_activity_set_allow_cancel (data->activity, TRUE);
			e_activity_set_percent (data->activity, 0.0);
			e_shell_module_add_activity (mail_shell_module, data->activity);

			g_signal_connect_swapped (
				data->activity, "cancelled",
				G_CALLBACK (camel_operation_cancel),
				msg->cancel);

			g_free (what);
			MAIL_MT_LOCK (mail_msg_lock);
			if (data->activity_state == 3) {
				EActivity *activity;

				activity = g_object_ref (data->activity);

				MAIL_MT_UNLOCK (mail_msg_lock);
				mail_msg_free (msg);

				if (activity != 0)
					mail_async_event_emit (
						mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) end_event_callback,
								NULL, activity, NULL);
			} else {
				data->activity_state = 2;
				MAIL_MT_UNLOCK (mail_msg_lock);
			}
			return;
		}
	} else if (data->activity != NULL) {
		MAIL_MT_UNLOCK (mail_msg_lock);
		e_activity_set_percent (data->activity, pc / 100.0);
	} else {
		MAIL_MT_UNLOCK (mail_msg_lock);
	}
}

static void
op_status_free (struct _op_status_msg *m)
{
	g_free (m->what);
}

static MailMsgInfo op_status_info = {
	sizeof (struct _op_status_msg),
	(MailMsgDescFunc) NULL,
	(MailMsgExecFunc) op_status_exec,
	(MailMsgDoneFunc) NULL,
	(MailMsgFreeFunc) op_status_free
};

static void
mail_operation_status (struct _CamelOperation *op, const char *what, int pc, void *data)
{
	struct _op_status_msg *m;

	d(printf("got operation statys: %s %d%%\n", what, pc));

	m = mail_msg_new(&op_status_info);
	m->op = op;
	m->what = g_strdup(what);
	switch (pc) {
	case CAMEL_OPERATION_START:
		pc = 0;
		break;
	case CAMEL_OPERATION_END:
		pc = 100;
		break;
	}
	m->pc = pc;
	m->data = data;
	mail_msg_main_loop_push(m);
}

/* ******************** */

static void
set_stop (int sensitive)
{
	static int last = FALSE;

	if (last == sensitive)
		return;

	last = sensitive;
}