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

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

#include <glib.h>

#include <gtk/gtkentry.h>
#include <gtk/gtkmain.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkprogress.h>
#include <gtk/gtkprogressbar.h>
#include <gtk/gtktable.h>
#include <gtk/gtkwidget.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
#include <libgnomeui/gnome-dialog.h>
#include <libgnomeui/gnome-dialog-util.h>
#include <libgnomeui/gnome-dialog.h>
#include <libgnomeui/gnome-stock.h>
#include <gal/widgets/e-gui-utils.h>

#include "folder-browser-factory.h"
#include "e-util/e-msgport.h"
#include "camel/camel-operation.h"

#include "mail-mt.h"

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

static void set_view_data(const char *current_message, int busy);
static void set_stop(int sensitive);
static void mail_enable_stop(void);
static void mail_disable_stop(void);
static void mail_operation_status(struct _CamelOperation *op, const char *what, int pc, void *data);

#define MAIL_MT_LOCK(x) pthread_mutex_lock(&x)
#define MAIL_MT_UNLOCK(x) pthread_mutex_unlock(&x)

/* background operation status stuff */
struct _mail_msg_priv {
	GtkProgressBar *bar;
	GtkLabel *label;

	/* for pending requests, before timeout_id is activated (then bar will be ! NULL) */
	char *what;
	int pc;
	int timeout_id;
};

static GtkWindow *progress_dialogue;
static int progress_row;

/* mail_msg stuff */
static unsigned int mail_msg_seq; /* sequence number of each message */
static GHashTable *mail_msg_active; /* 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;

pthread_t mail_gui_thread;

void *mail_msg_new(mail_msg_op_t *ops, EMsgPort *reply_port, size_t size)
{
	struct _mail_msg *msg;

	MAIL_MT_LOCK(mail_msg_lock);

	msg = g_malloc0(size);
	msg->ops = ops;
	msg->seq = mail_msg_seq++;
	msg->msg.reply_port = reply_port;
	msg->cancel = camel_operation_new(mail_operation_status, (void *)msg->seq);
	camel_exception_init(&msg->ex);
	msg->priv = g_malloc0(sizeof(*msg->priv));

	g_hash_table_insert(mail_msg_active, (void *)msg->seq, msg);

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

	MAIL_MT_UNLOCK(mail_msg_lock);

	return msg;
}

/* either destroy the progress (in event_data), or the whole dialogue (in data) */
static void destroy_widgets(CamelObject *o, void *event_data, void *data)
{
	if (data)
		gtk_widget_destroy((GtkWidget *)data);
	if (event_data)
		gtk_widget_destroy((GtkWidget *)event_data);
}

#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

void mail_msg_free(void *msg)
{
	struct _mail_msg *m = msg;
	void *bar = NULL, *label = NULL;

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

	if (m->ops->destroy_msg)
		m->ops->destroy_msg(m);

	MAIL_MT_LOCK(mail_msg_lock);

	g_hash_table_remove(mail_msg_active, (void *)m->seq);
	pthread_cond_broadcast(&mail_msg_cond);

	/* this closes the bar, and/or the whole progress dialogue, once we're out of things to do */
	if (g_hash_table_size(mail_msg_active) == 0) {
		if (progress_dialogue != NULL) {
			bar = progress_dialogue;
			progress_dialogue = NULL;
			progress_row = 0;
		}
	} else if (m->priv->bar) {
		bar = m->priv->bar;
		label = m->priv->label;
	}

	if (m->priv->timeout_id > 0)
		gtk_timeout_remove(m->priv->timeout_id);

	MAIL_MT_UNLOCK(mail_msg_lock);

	camel_operation_unref(m->cancel);
	camel_exception_clear(&m->ex);
	g_free(m->priv->what);
	g_free(m->priv);
	g_free(m);

	if (bar || label)
		mail_proxy_event(destroy_widgets, NULL, bar, label);
}

void mail_msg_check_error(void *msg)
{
	struct _mail_msg *m = msg;
	char *what = NULL;
	char *text;
	GnomeDialog *gd;

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

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

	if (m->ops->describe_msg)
		what = m->ops->describe_msg(m, FALSE);

	if (what) {
		text = g_strdup_printf(_("Error while '%s':\n%s"), what, camel_exception_get_description(&m->ex));
		g_free (what);
	} else
		text = g_strdup_printf(_("Error while performing operation:\n%s"), camel_exception_get_description(&m->ex));

	gd = (GnomeDialog *)gnome_error_dialog(text);
	gnome_dialog_run_and_close(gd);
	g_free(text);
}

void mail_msg_cancel(unsigned int msgid)
{
	struct _mail_msg *m;

	MAIL_MT_LOCK(mail_msg_lock);
	m = g_hash_table_lookup(mail_msg_active, (void *)msgid);

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

	MAIL_MT_UNLOCK(mail_msg_lock);
}


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

	if (ismain) {
		MAIL_MT_LOCK(mail_msg_lock);
		m = g_hash_table_lookup(mail_msg_active, (void *)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, (void *)msgid);
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	} else {
		MAIL_MT_LOCK(mail_msg_lock);
		m = g_hash_table_lookup(mail_msg_active, (void *)msgid);
		while (m) {
			pthread_cond_wait(&mail_msg_cond, &mail_msg_lock);
			m = g_hash_table_lookup(mail_msg_active, (void *)msgid);
		}
		MAIL_MT_UNLOCK(mail_msg_lock);
	}
}

EMsgPort		*mail_gui_port;
static GIOChannel	*mail_gui_channel;
EMsgPort		*mail_gui_reply_port;
static GIOChannel	*mail_gui_reply_channel;

/* a couple of global threads available */
EThread *mail_thread_queued;	/* for operations that can (or should) be queued */
EThread *mail_thread_new;	/* for operations that should run in a new thread each time */

static gboolean
mail_msgport_replied(GIOChannel *source, GIOCondition cond, void *d)
{
	EMsgPort *port = (EMsgPort *)d;
	mail_msg_t *m;

	while (( m = (mail_msg_t *)e_msgport_get(port))) {

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

		if (m->ops->reply_msg)
			m->ops->reply_msg(m);
		mail_msg_check_error(m);
		if (m->ops->describe_msg)
			mail_status_end();
		mail_msg_free(m);
	}

	return TRUE;
}

static gboolean
mail_msgport_received(GIOChannel *source, GIOCondition cond, void *d)
{
	EMsgPort *port = (EMsgPort *)d;
	mail_msg_t *m;

	while (( m = (mail_msg_t *)e_msgport_get(port))) {
#ifdef MALLOC_CHECK
		checkmem(m);
		checkmem(m->cancel);
		checkmem(m->priv);
#endif
		if (m->ops->describe_msg) {
			char *text = m->ops->describe_msg(m, FALSE);
			mail_status_start(text);
			g_free(text);
		}
		if (m->ops->receive_msg)
			m->ops->receive_msg(m);
		if (m->msg.reply_port)
			e_msgport_reply((EMsg *)m);
		else {
			if (m->ops->reply_msg)
				m->ops->reply_msg(m);
			if (m->ops->describe_msg)
				mail_status_end();
			mail_msg_free(m);
		}
	}

	return TRUE;
}

static void
mail_msg_destroy(EThread *e, EMsg *msg, void *data)
{
	mail_msg_t *m = (mail_msg_t *)msg;

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

	if (m->ops->describe_msg)
		mail_status_end();
	mail_msg_free(m);
}

static void
mail_msg_received(EThread *e, EMsg *msg, void *data)
{
	mail_msg_t *m = (mail_msg_t *)msg;

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

	if (m->ops->describe_msg) {
		char *text = m->ops->describe_msg(m, FALSE);
		d(printf("message received at thread\n"));
		mail_status_start(text);
		g_free(text);
	}

	if (m->ops->receive_msg) {
		mail_enable_stop();
		m->ops->receive_msg(m);
		mail_disable_stop();
	}
}

static void mail_msg_cleanup(void)
{
	e_thread_destroy(mail_thread_queued);
	e_thread_destroy(mail_thread_new);

	e_msgport_destroy(mail_gui_port);
	e_msgport_destroy(mail_gui_reply_port);

	/* FIXME: channels too, etc */
}

void mail_msg_init(void)
{
	mail_gui_reply_port = e_msgport_new();
	mail_gui_reply_channel = g_io_channel_unix_new(e_msgport_fd(mail_gui_reply_port));
	g_io_add_watch(mail_gui_reply_channel, G_IO_IN, mail_msgport_replied, mail_gui_reply_port);

	mail_gui_port = e_msgport_new();
	mail_gui_channel = g_io_channel_unix_new(e_msgport_fd(mail_gui_port));
	g_io_add_watch(mail_gui_channel, G_IO_IN, mail_msgport_received, mail_gui_port);

	mail_thread_queued = e_thread_new(E_THREAD_QUEUE);
	e_thread_set_msg_destroy(mail_thread_queued, mail_msg_destroy, 0);
	e_thread_set_msg_received(mail_thread_queued, mail_msg_received, 0);
	e_thread_set_reply_port(mail_thread_queued, mail_gui_reply_port);

	mail_thread_new = e_thread_new(E_THREAD_NEW);
	e_thread_set_msg_destroy(mail_thread_new, mail_msg_destroy, 0);
	e_thread_set_msg_received(mail_thread_new, mail_msg_received, 0);
	e_thread_set_reply_port(mail_thread_new, mail_gui_reply_port);

	mail_msg_active = g_hash_table_new(NULL, NULL);
	mail_gui_thread = pthread_self();

	atexit(mail_msg_cleanup);
}

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

struct _set_msg {
	struct _mail_msg msg;
	char *text;
};

/* locks */
static pthread_mutex_t status_lock = PTHREAD_MUTEX_INITIALIZER;
#define STATUS_BUSY_PENDING (2)

/* blah blah */

#define STATUS_DELAY (5)

static int status_depth;
static int status_showing;
static int status_shown;
static char *status_message_next;
static int status_message_clear;
static int status_timeout_id;
/*static int status_busy;*/

struct _status_msg {
	struct _mail_msg msg;
	char *text;
	int busy;
};

static gboolean
status_timeout(void *data)
{
	char *msg;
	int busy = 0;

	d(printf("got status timeout\n"));

	MAIL_MT_LOCK(status_lock);
	if (status_message_next) {
		d(printf("setting message to '%s' busy %d\n", status_message_next, status_busy));
		msg = status_message_next;
		status_message_next = NULL;
		busy = status_depth > 0;
		status_message_clear = 0;
		MAIL_MT_UNLOCK(status_lock);

		/* copy msg so we can set it outside the lock */
		/* unset first is a hack to avoid the stack stuff that doesn't and can't work anyway */
		if (status_shown)
			set_view_data(NULL, FALSE);
		status_shown = TRUE;
		set_view_data(msg, busy);
		g_free(msg);
		return TRUE;
	}

	/* the delay-clear stuff doesn't work yet.  Dont care ... */

	status_showing = FALSE;
	status_message_clear++;
	if (status_message_clear >= STATUS_DELAY && status_depth==0) {
		d(printf("clearing message, busy = %d\n", status_depth));
	} else {
		d(printf("delaying clear\n"));
		MAIL_MT_UNLOCK(status_lock);
		return TRUE;
	}

	status_timeout_id = 0;

	MAIL_MT_UNLOCK(status_lock);

	if (status_shown)
		set_view_data(NULL, FALSE);
	status_shown = FALSE;

	return FALSE;
}

static void do_set_status(struct _mail_msg *mm)
{
	struct _status_msg *m = (struct _status_msg *)mm;

	MAIL_MT_LOCK(status_lock);

	if (status_timeout_id != 0)
		gtk_timeout_remove(status_timeout_id);

	status_timeout_id = gtk_timeout_add(500, status_timeout, 0);
	status_message_clear = 0;

	MAIL_MT_UNLOCK(status_lock);

	/* the 'clear' stuff doesn't really work yet, but oh well,
	   this stuff here needs a little changing for it to work */
	if (status_shown)
		set_view_data(NULL, status_depth != 0);
	status_shown = 0;

	if (m->text) {
		status_shown = 1;
		set_view_data(m->text, status_depth != 0);
	}
}

static void do_del_status(struct _mail_msg *mm)
{
	struct _status_msg *m = (struct _status_msg *)mm;

	g_free(m->text);
}

struct _mail_msg_op set_status_op = {
	NULL,
	do_set_status,
	NULL,
	do_del_status,
};

/* start a new operation */
void mail_status_start(const char *msg)
{
	struct _status_msg *m = NULL;

	MAIL_MT_LOCK(status_lock);
	status_depth++;
	MAIL_MT_UNLOCK(status_lock);

	if (msg == NULL || msg[0] == 0)
		msg = _("Working");

	m = mail_msg_new(&set_status_op, NULL, sizeof(*m));
	m->text = g_strdup(msg);
	m->busy = TRUE;

	e_msgport_put(mail_gui_port, &m->msg.msg);
}

/* end it */
void mail_status_end(void)
{
	struct _status_msg *m = NULL;

	m = mail_msg_new(&set_status_op, NULL, sizeof(*m));
	m->text = NULL;

	MAIL_MT_LOCK(status_lock);
	status_depth--;
	m->busy = status_depth = 0;
	MAIL_MT_UNLOCK(status_lock);

	e_msgport_put(mail_gui_port, &m->msg.msg);
}

/* message during it */
void mail_status(const char *msg)
{
	if (msg == NULL || msg[0] == 0)
		msg = _("Working");

	MAIL_MT_LOCK(status_lock);

	g_free(status_message_next);
	status_message_next = g_strdup(msg);

	MAIL_MT_UNLOCK(status_lock);
}

void mail_statusf(const char *fmt, ...)
{
	va_list ap;
	char *text;

	va_start(ap, fmt);
	text = g_strdup_vprintf(fmt, ap);
	va_end(ap);
	mail_status(text);
	g_free(text);
}

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

struct _pass_msg {
	struct _mail_msg msg;
	const char *prompt;
	int secret;
	char *result;
};

/* libgnomeui's idea of an api/gui is very weird ... hence this dumb hack */
static void focus_on_entry(GtkWidget *widget, void *user_data)
{
	if (GTK_IS_ENTRY(widget))
		gtk_widget_grab_focus(widget);
}

static void pass_got(char *string, void *data)
{
	struct _pass_msg *m = data;

	if (string)
		m->result = g_strdup (string);
}

static void
do_get_pass(struct _mail_msg *mm)
{
	struct _pass_msg *m = (struct _pass_msg *)mm;
	GtkWidget *dialogue;

	/* this api is just awful ... hence the hacks */
	dialogue = gnome_request_dialog(m->secret, m->prompt, NULL,
					0, pass_got, m, NULL);
	e_container_foreach_leaf((GtkContainer *)dialogue, focus_on_entry, NULL);

	/* hrm, we can't run this async since the gui_port from which we're called
	   will reply to our message for us */
	gnome_dialog_run_and_close((GnomeDialog *)dialogue);

	/*gtk_widget_show(dialogue);*/
}

static void
do_free_pass(struct _mail_msg *mm)
{
	/*struct _pass_msg *m = (struct _pass_msg *)mm;*/

	/* the string is passed out so we dont need to free it */
}

struct _mail_msg_op get_pass_op = {
	NULL,
	do_get_pass,
	NULL,
	do_free_pass,
};

/* returns the password, or NULL if cancelled */
char *
mail_get_password(const char *prompt, gboolean secret)
{
	char *ret;
	struct _pass_msg *m, *r;
	EMsgPort *pass_reply;

	pass_reply = e_msgport_new();

	m = mail_msg_new(&get_pass_op, pass_reply, sizeof(*m));

	m->prompt = prompt;
	m->secret = secret;

	if (pthread_self() == mail_gui_thread) {
		do_get_pass((struct _mail_msg *)m);
		r = m;
	} else {
		static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

		/* we want this single-threaded, this is the easiest way to do it without blocking ? */
		pthread_mutex_lock(&lock);
		e_msgport_put(mail_gui_port, (EMsg *)m);
		e_msgport_wait(pass_reply);
		r = (struct _pass_msg *)e_msgport_get(pass_reply);
		pthread_mutex_unlock(&lock);
	}

	g_assert(r == m);

	ret = m->result;
	
	mail_msg_free(m);
	e_msgport_destroy(pass_reply);

	return ret;
}

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

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

struct _user_message_msg {
	struct _mail_msg msg;
	const char *type;
	const char *prompt;
	gboolean allow_cancel;
	gboolean result;
};

static void
do_user_message (struct _mail_msg *mm)
{
	struct _user_message_msg *m = (struct _user_message_msg *)mm;
	GtkWidget *dialog;
	
	dialog = gnome_message_box_new (m->prompt, m->type,
					m->allow_cancel ? GNOME_STOCK_BUTTON_CANCEL : GNOME_STOCK_BUTTON_OK,
					m->allow_cancel ? GNOME_STOCK_BUTTON_OK: NULL,
					NULL);
	gnome_dialog_set_default (GNOME_DIALOG (dialog), 1);
	gtk_window_set_policy (GTK_WINDOW (dialog), TRUE, TRUE, TRUE);
	
	/* hrm, we can't run this async since the gui_port from which we're called
	   will reply to our message for us */
	m->result = gnome_dialog_run_and_close (GNOME_DIALOG (dialog)) != 0;
}

struct _mail_msg_op user_message_op = {
	NULL,
	do_user_message,
	NULL,
	NULL,
};

/* prompt the user with a yes/no question and return the response */
gboolean
mail_user_message (const char *type, const char *prompt, gboolean allow_cancel)
{
	struct _user_message_msg *m, *r;
	EMsgPort *user_message_reply;
	gboolean accept;
	
	user_message_reply = e_msgport_new ();
	
	m = mail_msg_new (&user_message_op, user_message_reply, sizeof (*m));
	
	m->type = type;
	m->prompt = prompt;
	m->allow_cancel = allow_cancel;
	
	if (pthread_self () == mail_gui_thread) {
		do_user_message ((struct _mail_msg *)m);
		r = m;
	} else {
		static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
		
		/* we want this single-threaded, this is the easiest way to do it without blocking ? */
		pthread_mutex_lock (&lock);
		e_msgport_put (mail_gui_port, (EMsg *)m);
		e_msgport_wait (user_message_reply);
		r = (struct _user_message_msg *)e_msgport_get (user_message_reply);
		pthread_mutex_unlock (&lock);
	}
	
	g_assert (r == m);
	
	accept = m->result;
	
	mail_msg_free (m);
	e_msgport_destroy (user_message_reply);
	
	return accept;
}

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

struct _proxy_msg {
	struct _mail_msg msg;
	CamelObjectEventHookFunc func;
	CamelObject *o;
	void *event_data;
	void *data;
};

static void
do_proxy_event(struct _mail_msg *mm)
{
	struct _proxy_msg *m = (struct _proxy_msg *)mm;

	m->func(m->o, m->event_data, m->data);
}

struct _mail_msg_op proxy_event_op = {
	NULL,
	do_proxy_event,
	NULL,
	NULL,
};

int mail_proxy_event(CamelObjectEventHookFunc func, CamelObject *o, void *event_data, void *data)
{
	struct _proxy_msg *m;
	int id;
	int ismain = pthread_self() == mail_gui_thread;

	if (ismain) {
		func(o, event_data, data);
		/* id of -1 is 'always finished' */
		return -1;
	} else {
		/* we dont have a reply port for this, we dont care when/if it gets executed, just queue it */
		m = mail_msg_new(&proxy_event_op, NULL, sizeof(*m));
		m->func = func;
		m->o = o;
		m->event_data = event_data;
		m->data = data;
		
		id = m->msg.seq;
		e_msgport_put(mail_gui_port, (EMsg *)m);
		return id;
	}
}

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

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

struct _mail_msg_op set_busy_op = {
	NULL,
	do_set_busy,
	NULL,
	NULL,
};

static void mail_enable_stop(void)
{
	struct _mail_msg *m;

	MAIL_MT_LOCK(status_lock);
	busy_state++;
	if (busy_state == 1) {
		m = mail_msg_new(&set_busy_op, NULL, sizeof(*m));
		e_msgport_put(mail_gui_port, (EMsg *)m);
	}
	MAIL_MT_UNLOCK(status_lock);
}

static void mail_disable_stop(void)
{
	struct _mail_msg *m;

	MAIL_MT_LOCK(status_lock);
	busy_state--;
	if (busy_state == 0) {
		m = mail_msg_new(&set_busy_op, NULL, sizeof(*m));
		e_msgport_put(mail_gui_port, (EMsg *)m);
	}
	MAIL_MT_UNLOCK(status_lock);
}

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

struct _op_status_msg {
	struct _mail_msg msg;

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

GtkTable *progress_table;

static int op_status_timeout(void *d)
{
	int id = (int)d;
	struct _mail_msg *msg;
	struct _mail_msg_priv *data;
	
	MAIL_MT_LOCK(mail_msg_lock);

	msg = g_hash_table_lookup(mail_msg_active, (void *)id);
	if (msg == NULL) {
		MAIL_MT_UNLOCK(mail_msg_lock);
		return FALSE;
	}

	data = msg->priv;

	if (progress_dialogue == NULL) {
		if (data->pc == 100) {
			MAIL_MT_UNLOCK(mail_msg_lock);
			return FALSE;
		}

		progress_dialogue = (GtkWindow *)gtk_window_new(GTK_WINDOW_DIALOG);
		gtk_window_set_title(progress_dialogue, _("Evolution progress"));
		gtk_window_set_policy(progress_dialogue, 0, 0, 1);
		gtk_window_set_position(progress_dialogue, GTK_WIN_POS_CENTER);
		progress_table = (GtkTable *)gtk_table_new(1, 2, FALSE);
		gtk_container_add((GtkContainer *)progress_dialogue, (GtkWidget *)progress_table);
	}

	data->bar = (GtkProgressBar *)gtk_progress_bar_new();
	gtk_progress_set_show_text((GtkProgress *)data->bar, TRUE);

	gtk_progress_set_percentage((GtkProgress *)data->bar, (gfloat)(data->pc/100.0));
	gtk_progress_set_format_string((GtkProgress *)data->bar, data->what);

	if (msg->ops->describe_msg) {
		char *desc = msg->ops->describe_msg(msg, FALSE);
		data->label = (GtkLabel *)gtk_label_new(desc);
		g_free(desc);
	} else {
		data->label = (GtkLabel *)gtk_label_new(_("Working"));
	}

	gtk_table_attach(progress_table, (GtkWidget *)data->label, 0, 1, progress_row, progress_row+1, GTK_EXPAND|GTK_FILL, 0, 3, 1);
	gtk_table_attach(progress_table, (GtkWidget *)data->bar, 1, 2, progress_row, progress_row+1, GTK_EXPAND|GTK_FILL, 0, 3, 1);
	progress_row++;
	
	gtk_widget_show_all((GtkWidget *)progress_table);
	gtk_widget_show((GtkWidget *)progress_dialogue);

	data->timeout_id = -1;

	MAIL_MT_UNLOCK(mail_msg_lock);

	return FALSE;
}

static void do_op_status(struct _mail_msg *mm)
{
	struct _op_status_msg *m = (struct _op_status_msg *)mm;
	struct _mail_msg *msg;
	struct _mail_msg_priv *data;
	char *out, *p, *o, c;

	g_assert(mail_gui_thread == pthread_self());

	MAIL_MT_LOCK(mail_msg_lock);

	msg = g_hash_table_lookup(mail_msg_active, 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;

	if (data->timeout_id == 0) {
		data->what = g_strdup(out);
		data->pc = m->pc;
		data->timeout_id = gtk_timeout_add(2000, op_status_timeout, m->data);
		MAIL_MT_UNLOCK(mail_msg_lock);
		return;
	}

	if (data->bar == NULL) {
		g_free(data->what);
		data->what = g_strdup(out);
		data->pc = m->pc;
		MAIL_MT_UNLOCK(mail_msg_lock);
		return;
	}

	gtk_progress_set_percentage((GtkProgress *)data->bar, (gfloat)(m->pc/100.0));
	gtk_progress_set_format_string((GtkProgress *)data->bar, out);

	MAIL_MT_UNLOCK(mail_msg_lock);
}

static void do_op_status_free(struct _mail_msg *mm)
{
	struct _op_status_msg *m = (struct _op_status_msg *)mm;

	g_free(m->what);
}

struct _mail_msg_op op_status_op = {
	NULL,
	do_op_status,
	NULL,
	do_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_op, NULL, sizeof(*m));
	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;
	e_msgport_put(mail_gui_port, (EMsg *)m);
}

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

/* FIXME FIXME FIXME This is a totally evil hack.  */

static GNOME_Evolution_ShellView
retrieve_shell_view_interface_from_control (BonoboControl *control)
{
	Bonobo_ControlFrame control_frame;
	GNOME_Evolution_ShellView shell_view_interface;
	CORBA_Environment ev;

	control_frame = bonobo_control_get_control_frame (control);

	if (control_frame == NULL)
		return CORBA_OBJECT_NIL;

	CORBA_exception_init (&ev);
	shell_view_interface = Bonobo_Unknown_queryInterface (control_frame,
							       "IDL:GNOME/Evolution/ShellView:1.0",
							       &ev);
	CORBA_exception_free (&ev);

	if (shell_view_interface != CORBA_OBJECT_NIL)
		gtk_object_set_data (GTK_OBJECT (control),
				     "mail_threads_shell_view_interface",
				     shell_view_interface);
	else
		g_warning ("Control frame doesn't have Evolution/ShellView.");

	return shell_view_interface;
}

static void
set_view_data(const char *current_message, int busy)
{
	EList *controls;
	EIterator *it;

	controls = folder_browser_factory_get_control_list ();
	for (it = e_list_get_iterator (controls); e_iterator_is_valid (it); e_iterator_next (it)) {
		BonoboControl *control;
		GNOME_Evolution_ShellView shell_view_interface;
		CORBA_Environment ev;

		control = BONOBO_CONTROL (e_iterator_get (it));

		shell_view_interface = gtk_object_get_data (GTK_OBJECT (control), "mail_threads_shell_view_interface");

		if (shell_view_interface == CORBA_OBJECT_NIL)
			shell_view_interface = retrieve_shell_view_interface_from_control (control);

		CORBA_exception_init (&ev);

		if (shell_view_interface != CORBA_OBJECT_NIL) {
			if ((current_message == NULL || current_message[0] == 0) && ! busy) {
				d(printf("clearing msg\n"));
				GNOME_Evolution_ShellView_unsetMessage (shell_view_interface, &ev);
			} else {
				d(printf("setting msg %s\n", current_message ? current_message : "(null)"));
				GNOME_Evolution_ShellView_setMessage (shell_view_interface,
								      current_message?current_message:"",
								      busy,
								      &ev);
			}
		}

		CORBA_exception_free (&ev);

		/* yeah we only set the first one.  Why?  Because it seems to leave
		   random ones lying around otherwise.  Shrug. */
		break;
	}
	gtk_object_unref(GTK_OBJECT(it));
}

static void
set_stop(int sensitive)
{
	EList *controls;
	EIterator *it;
	static int last = FALSE;

	if (last == sensitive)
		return;

	controls = folder_browser_factory_get_control_list ();
	for (it = e_list_get_iterator (controls); e_iterator_is_valid (it); e_iterator_next (it)) {
		BonoboControl *control;
		BonoboUIComponent *uic;

		control = BONOBO_CONTROL (e_iterator_get (it));
		uic = bonobo_control_get_ui_component (control);
		if (uic == CORBA_OBJECT_NIL || bonobo_ui_component_get_container(uic) == CORBA_OBJECT_NIL)
			continue;

		bonobo_ui_component_set_prop(uic, "/commands/MailStop", "sensitive", sensitive?"1":"0", NULL);
	}
	gtk_object_unref(GTK_OBJECT(it));
	last = sensitive;
}