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

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

#include <stdio.h>
#include <string.h>

#include <glib/gi18n.h>

#include "libedataserver/e-account-list.h"

#include "shell/e-shell.h"
#include "e-util/e-account-utils.h"
#include "e-util/gconf-bridge.h"

#include "e-mail-local.h"
#include "e-mail-session.h"
#include "em-event.h"
#include "em-filter-rule.h"
#include "em-utils.h"
#include "mail-folder-cache.h"
#include "mail-mt.h"
#include "mail-ops.h"
#include "mail-send-recv.h"
#include "mail-tools.h"

#define d(x)

/* ms between status updates to the gui */
#define STATUS_TIMEOUT (250)

/* pseudo-uri to key the send task on */
#define SEND_URI_KEY "send-task:"

/* Prefix for window size GConf keys */
#define GCONF_KEY_PREFIX "/apps/evolution/mail/send_recv"

/* send/receive email */

/* ********************************************************************** */
/*  This stuff below is independent of the stuff above */

/* this stuff is used to keep track of which folders filters have accessed, and
   what not. the thaw/refreeze thing doesn't really seem to work though */
struct _folder_info {
	gchar *uri;
	CamelFolder *folder;
	time_t update;
	gint count;		/* how many times updated, to slow it down as we go, if we have lots */
};

struct _send_data {
	GList *infos;

	GtkDialog *gd;
	gint cancelled;

	CamelFolder *inbox;	/* since we're never asked to update this one, do it ourselves */
	time_t inbox_update;

	GMutex *lock;
	GHashTable *folders;

	GHashTable *active;	/* send_info's by uri */
};

typedef enum {
	SEND_RECEIVE,		/* receiver */
	SEND_SEND,		/* sender */
	SEND_UPDATE,		/* imap-like 'just update folder info' */
	SEND_INVALID
} send_info_t;

typedef enum {
	SEND_ACTIVE,
	SEND_CANCELLED,
	SEND_COMPLETE
} send_state_t;

struct _send_info {
	send_info_t type;		/* 0 = fetch, 1 = send */
	EMailSession *session;
	GCancellable *cancellable;
	gchar *uri;
	gboolean keep_on_server;
	send_state_t state;
	GtkWidget *progress_bar;
	GtkWidget *cancel_button;
	GtkWidget *status_label;

	gint again;		/* need to run send again */

	gint timeout_id;
	gchar *what;
	gint pc;

	GtkWidget *send_account_label;
	gchar *send_url;

	/*time_t update;*/
	struct _send_data *data;
};

static CamelFolder *
		receive_get_folder		(CamelFilterDriver *d,
						 const gchar *uri,
						 gpointer data,
						 GError **error);

static struct _send_data *send_data = NULL;
static GtkWidget *send_recv_dialog = NULL;

static void
free_folder_info (struct _folder_info *info)
{
	/*camel_folder_thaw (info->folder);	*/
	mail_sync_folder (info->folder, NULL, NULL);
	g_object_unref (info->folder);
	g_free (info->uri);
	g_free (info);
}

static void
free_send_info (struct _send_info *info)
{
	if (info->session)
		g_object_unref (info->session);
	if (info->cancellable)
		g_object_unref (info->cancellable);
	g_free (info->uri);
	if (info->timeout_id != 0)
		g_source_remove (info->timeout_id);
	g_free (info->what);
	g_free (info->send_url);
	g_free (info);
}

static struct _send_data *
setup_send_data (void)
{
	struct _send_data *data;

	if (send_data == NULL) {
		send_data = data = g_malloc0 (sizeof (*data));
		data->lock = g_mutex_new ();
		data->folders = g_hash_table_new_full (
			g_str_hash, g_str_equal,
			(GDestroyNotify) NULL,
			(GDestroyNotify) free_folder_info);
		data->inbox = e_mail_local_get_folder (
			E_MAIL_LOCAL_FOLDER_LOCAL_INBOX);
		g_object_ref (data->inbox);
		data->active = g_hash_table_new_full (
			g_str_hash, g_str_equal,
			(GDestroyNotify) NULL,
			(GDestroyNotify) free_send_info);
	}
	return send_data;
}

static void
receive_cancel (GtkButton *button, struct _send_info *info)
{
	if (info->state == SEND_ACTIVE) {
		camel_operation_cancel (CAMEL_OPERATION (info->cancellable));
		if (info->status_label)
			gtk_label_set_text (
				GTK_LABEL (info->status_label),
				_("Canceling..."));
		info->state = SEND_CANCELLED;
	}
	if (info->cancel_button)
		gtk_widget_set_sensitive (info->cancel_button, FALSE);
}

static void
free_send_data (void)
{
	struct _send_data *data = send_data;

	g_return_if_fail (g_hash_table_size (data->active) == 0);

	if (data->inbox) {
		mail_sync_folder (data->inbox, NULL, NULL);
		/*camel_folder_thaw (data->inbox);		*/
		g_object_unref (data->inbox);
	}

	g_list_free (data->infos);
	g_hash_table_destroy (data->active);
	g_hash_table_destroy (data->folders);
	g_mutex_free (data->lock);
	g_free (data);
	send_data = NULL;
}

static void
cancel_send_info (gpointer key, struct _send_info *info, gpointer data)
{
	receive_cancel (GTK_BUTTON (info->cancel_button), info);
}

static void
hide_send_info (gpointer key, struct _send_info *info, gpointer data)
{
	info->cancel_button = NULL;
	info->progress_bar = NULL;
	info->status_label = NULL;

	if (info->timeout_id != 0) {
		g_source_remove (info->timeout_id);
		info->timeout_id = 0;
	}
}

static void
dialog_destroy_cb (struct _send_data *data, GObject *deadbeef)
{
	g_hash_table_foreach (data->active, (GHFunc) hide_send_info, NULL);
	data->gd = NULL;
	send_recv_dialog = NULL;
}

static void
dialog_response (GtkDialog *gd, gint button, struct _send_data *data)
{
	switch (button) {
	case GTK_RESPONSE_CANCEL:
		d(printf("cancelled whole thing\n"));
		if (!data->cancelled) {
			data->cancelled = TRUE;
			g_hash_table_foreach (data->active, (GHFunc)cancel_send_info, NULL);
		}
		gtk_dialog_set_response_sensitive (gd, GTK_RESPONSE_CANCEL, FALSE);
		break;
	default:
		d(printf("hiding dialog\n"));
		g_hash_table_foreach (data->active, (GHFunc)hide_send_info, NULL);
		data->gd = NULL;
		/*gtk_widget_destroy((GtkWidget *)gd);*/
		break;
	}
}

static GStaticMutex status_lock = G_STATIC_MUTEX_INIT;
static gchar *format_url (const gchar *internal_url, const gchar *account_name);

static gint
operation_status_timeout (gpointer data)
{
	struct _send_info *info = data;

	if (info->progress_bar) {
		g_static_mutex_lock (&status_lock);

		gtk_progress_bar_set_fraction (
			GTK_PROGRESS_BAR (info->progress_bar),
			info->pc / 100.0);
		if (info->what)
			gtk_label_set_text (
				GTK_LABEL (info->status_label),
				info->what);
		if (info->send_url && info->send_account_label) {
			gchar *tmp = format_url (info->send_url, NULL);

			g_free (info->send_url);
			info->send_url = NULL;

			gtk_label_set_markup (
				GTK_LABEL (info->send_account_label),
				tmp);

			g_free (tmp);
		}

		g_static_mutex_unlock (&status_lock);

		return TRUE;
	}

	return FALSE;
}

static void
set_send_status (struct _send_info *info, const gchar *desc, gint pc)
{
	g_static_mutex_lock (&status_lock);

	g_free (info->what);
	info->what = g_strdup (desc);
	info->pc = pc;

	g_static_mutex_unlock (&status_lock);
}

static void
set_send_account (struct _send_info *info, const gchar *account_url)
{
	g_static_mutex_lock (&status_lock);

	g_free (info->send_url);
	info->send_url = g_strdup (account_url);

	g_static_mutex_unlock (&status_lock);
}

/* for camel operation status */
static void
operation_status (CamelOperation *op,
                  const gchar *what,
                  gint pc,
                  struct _send_info *info)
{
	set_send_status (info, what, pc);
}

static gchar *
format_url (const gchar *internal_url, const gchar *account_name)
{
	CamelURL *url;
	gchar *pretty_url = NULL;

	url = camel_url_new (internal_url, NULL);

	if (account_name) {
		if (url->host && *url->host)
			pretty_url = g_strdup_printf (
				"<b>%s (%s)</b>: %s",
				account_name, url->protocol, url->host);
		else if (url->path)
			pretty_url = g_strdup_printf (
				"<b>%s (%s)</b>: %s",
				account_name, url->protocol, url->path);
		else
			pretty_url = g_strdup_printf (
				"<b>%s (%s)</b>",
				account_name, url->protocol);

	} else if (url) {
		if (url->host && *url->host)
			pretty_url = g_strdup_printf (
				"<b>%s</b>: %s",
				url->protocol, url->host);
		else if (url->path)
			pretty_url = g_strdup_printf (
				"<b>%s</b>: %s",
				url->protocol, url->path);
		else
			pretty_url = g_strdup_printf (
				"<b>%s</b>", url->protocol);
	}

	if (url)
		camel_url_free (url);

	return pretty_url;
}

static send_info_t
get_receive_type (const gchar *url)
{
	CamelProvider *provider;

	/* mbox pointing to a file is a 'Local delivery' source
	   which requires special processing */
	if (em_utils_is_local_delivery_mbox_file (url))
		return SEND_RECEIVE;

	provider = camel_provider_get (url, NULL);

	if (!provider)
		return SEND_INVALID;

	if (provider->object_types[CAMEL_PROVIDER_STORE]) {
		if (provider->flags & CAMEL_PROVIDER_IS_STORAGE)
			return SEND_UPDATE;
		else
			return SEND_RECEIVE;
	} else if (provider->object_types[CAMEL_PROVIDER_TRANSPORT]) {
		return SEND_SEND;
	}

	return SEND_INVALID;
}

static struct _send_data *
build_dialog (GtkWindow *parent,
              EMailSession *session,
              EAccountList *accounts,
              CamelFolder *outbox,
              const gchar *destination,
              gboolean allow_send)
{
	GtkDialog *gd;
	GtkWidget *table;
	gint row, num_sources;
	GList *list = NULL;
	struct _send_data *data;
	GtkWidget *container;
	GtkWidget *send_icon;
	GtkWidget *recv_icon;
	GtkWidget *scrolled_window;
	GtkWidget *label;
	GtkWidget *status_label;
	GtkWidget *progress_bar;
	GtkWidget *cancel_button;
	struct _send_info *info;
	gchar *pretty_url;
	EAccount *account;
	EIterator *iter;
	EMEventTargetSendReceive *target;

	send_recv_dialog = gtk_dialog_new_with_buttons (
		_("Send & Receive Mail"), parent, 0, NULL);
	gd = GTK_DIALOG (send_recv_dialog);
	gtk_window_set_modal ((GtkWindow *) gd, FALSE);

	gconf_bridge_bind_window_size (
		gconf_bridge_get (), GCONF_KEY_PREFIX,
		GTK_WINDOW (send_recv_dialog));

	gtk_widget_ensure_style ((GtkWidget *)gd);

	container = gtk_dialog_get_action_area (gd);
	gtk_container_set_border_width (GTK_CONTAINER (container), 6);

	container = gtk_dialog_get_content_area (gd);
	gtk_container_set_border_width (GTK_CONTAINER (container), 0);

	cancel_button = gtk_button_new_with_mnemonic (_("Cancel _All"));
	gtk_button_set_image (
		GTK_BUTTON (cancel_button),
		gtk_image_new_from_stock (
			GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON));
	gtk_widget_show (cancel_button);
	gtk_dialog_add_action_widget (gd, cancel_button, GTK_RESPONSE_CANCEL);

	gtk_window_set_icon_name (GTK_WINDOW (gd), "mail-send-receive");

	num_sources = 0;

	iter = e_list_get_iterator ((EList *) accounts);
	while (e_iterator_is_valid (iter)) {
		account = (EAccount *) e_iterator_get (iter);

		if (account->source->url)
			num_sources++;

		e_iterator_next (iter);
	}

	g_object_unref (iter);

	/* Check to see if we have to send any mails.
	 * If we don't, don't display the SMTP row in the table. */
	if (outbox && destination
	 && (camel_folder_get_message_count (outbox) -
		camel_folder_get_deleted_message_count (outbox)) == 0)
		num_sources--;

	table = gtk_table_new (num_sources, 4, FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (table), 6);
	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
	gtk_table_set_col_spacings (GTK_TABLE (table), 6);

	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
	gtk_container_set_border_width (
		GTK_CONTAINER (scrolled_window), 6);
	gtk_scrolled_window_set_policy (
		GTK_SCROLLED_WINDOW (scrolled_window),
		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);

	container = gtk_dialog_get_content_area (gd);
	gtk_scrolled_window_add_with_viewport (
		GTK_SCROLLED_WINDOW (scrolled_window), table);
	gtk_box_pack_start (
		GTK_BOX (container), scrolled_window, TRUE, TRUE, 0);
	gtk_widget_show (scrolled_window);

	/* must bet setup after send_recv_dialog as it may re-trigger send-recv button */
	data = setup_send_data ();

	row = 0;
	iter = e_list_get_iterator ((EList *) accounts);
	while (e_iterator_is_valid (iter)) {
		EAccountService *source;

		account = (EAccount *) e_iterator_get (iter);

		source = account->source;
		if (!account->enabled || !source->url) {
			e_iterator_next (iter);
			continue;
		}

		/* see if we have an outstanding download active */
		info = g_hash_table_lookup (data->active, source->url);
		if (info == NULL) {
			send_info_t type;

			type = get_receive_type (source->url);
			if (type == SEND_INVALID || type == SEND_SEND) {
				e_iterator_next (iter);
				continue;
			}

			info = g_malloc0 (sizeof (*info));
			info->type = type;
			info->session = g_object_ref (session);

			d(printf("adding source %s\n", source->url));

			info->uri = g_strdup (source->url);
			info->keep_on_server = source->keep_on_server;
			info->cancellable = camel_operation_new ();
			info->state = allow_send ? SEND_ACTIVE : SEND_COMPLETE;
			info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info);

			g_signal_connect (
				info->cancellable, "status",
				G_CALLBACK (operation_status), info);

			g_hash_table_insert (data->active, info->uri, info);
			list = g_list_prepend (list, info);
		} else if (info->progress_bar != NULL) {
			/* incase we get the same source pop up again */
			e_iterator_next (iter);
			continue;
		} else if (info->timeout_id == 0)
			info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info);

		recv_icon = gtk_image_new_from_icon_name (
			"mail-inbox", GTK_ICON_SIZE_LARGE_TOOLBAR);
		pretty_url = format_url (source->url, account->name);
		label = gtk_label_new (NULL);
		gtk_label_set_ellipsize (
			GTK_LABEL (label), PANGO_ELLIPSIZE_END);
		gtk_label_set_markup (GTK_LABEL (label), pretty_url);
		g_free (pretty_url);

		progress_bar = gtk_progress_bar_new ();

		cancel_button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);

		status_label = gtk_label_new (
			(info->type == SEND_UPDATE) ?
			_("Updating...") : _("Waiting..."));
		gtk_label_set_ellipsize (
			GTK_LABEL (status_label), PANGO_ELLIPSIZE_END);

		/* g_object_set(data->label, "bold", TRUE, NULL); */
		gtk_misc_set_alignment (GTK_MISC (label), 0, .5);
		gtk_misc_set_alignment (GTK_MISC (status_label), 0, .5);

		gtk_table_attach (
			GTK_TABLE (table), recv_icon,
			0, 1, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), label,
			1, 2, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), progress_bar,
			2, 3, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), cancel_button,
			3, 4, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), status_label,
			1, 2, row+1, row+2, GTK_EXPAND | GTK_FILL, 0, 0, 0);

		info->progress_bar = progress_bar;
		info->status_label = status_label;
		info->cancel_button = cancel_button;
		info->data = data;

		g_signal_connect (
			cancel_button, "clicked",
			G_CALLBACK (receive_cancel), info);
		e_iterator_next (iter);
		row = row + 2;
	}

	g_object_unref (iter);

	/* we also need gd during emition to be able to catch Cancel All */
	data->gd = gd;
	target = em_event_target_new_send_receive (
		em_event_peek (), table, data, row, EM_EVENT_SEND_RECEIVE);
	e_event_emit (
		(EEvent *) em_event_peek (), "mail.sendreceive",
		(EEventTarget *) target);

	/* Skip displaying the SMTP row if we've got no outbox, destination or unsent mails */
	if (allow_send && outbox && destination
	 && (camel_folder_get_message_count (outbox) -
		camel_folder_get_deleted_message_count (outbox)) != 0) {
		info = g_hash_table_lookup (data->active, SEND_URI_KEY);
		if (info == NULL) {
			info = g_malloc0 (sizeof (*info));
			info->type = SEND_SEND;
			d(printf("adding dest %s\n", destination));

			info->uri = g_strdup (destination);
			info->keep_on_server = FALSE;
			info->cancellable = camel_operation_new ();
			info->state = SEND_ACTIVE;
			info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info);

			g_signal_connect (
				info->cancellable, "status",
				G_CALLBACK (operation_status), info);

			g_hash_table_insert (data->active, (gpointer) SEND_URI_KEY, info);
			list = g_list_prepend (list, info);
		} else if (info->timeout_id == 0)
			info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info);

		send_icon = gtk_image_new_from_icon_name (
			"mail-outbox", GTK_ICON_SIZE_LARGE_TOOLBAR);
		pretty_url = format_url (destination, NULL);
		label = gtk_label_new (NULL);
		gtk_label_set_ellipsize (
			GTK_LABEL (label), PANGO_ELLIPSIZE_END);
		gtk_label_set_markup (GTK_LABEL (label), pretty_url);

		g_free (pretty_url);

		progress_bar = gtk_progress_bar_new ();
		cancel_button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);

		status_label = gtk_label_new (_("Waiting..."));
		gtk_label_set_ellipsize (
			GTK_LABEL (status_label), PANGO_ELLIPSIZE_END);

		gtk_misc_set_alignment (GTK_MISC (label), 0, .5);
		gtk_misc_set_alignment (GTK_MISC (status_label), 0, .5);

		gtk_table_attach (
			GTK_TABLE (table), send_icon,
			0, 1, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), label,
			1, 2, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), progress_bar,
			2, 3, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), cancel_button,
			3, 4, row, row+2, 0, 0, 0, 0);
		gtk_table_attach (
			GTK_TABLE (table), status_label,
			1, 2, row+1, row+2, GTK_EXPAND | GTK_FILL, 0, 0, 0);

		info->progress_bar = progress_bar;
		info->cancel_button = cancel_button;
		info->data = data;
		info->status_label = status_label;
		info->send_account_label = label;

		g_signal_connect (
			cancel_button, "clicked",
			G_CALLBACK (receive_cancel), info);
	}

	gtk_widget_show_all (table);

	if (parent != NULL)
		gtk_widget_show (GTK_WIDGET (gd));

	g_signal_connect (gd, "response", G_CALLBACK (dialog_response), data);

	g_object_weak_ref ((GObject *) gd, (GWeakNotify) dialog_destroy_cb, data);

	data->infos = list;

	return data;
}

static void
update_folders (gchar *uri, struct _folder_info *info, gpointer data)
{
	time_t now = *((time_t *)data);

	d(printf("checking update for folder: %s\n", info->uri));

	/* let it flow through to the folders every 10 seconds */
	/* we back off slowly as we progress */
	if (now > info->update+10+info->count*5) {
		d(printf("upating a folder: %s\n", info->uri));
		/*camel_folder_thaw(info->folder);
		  camel_folder_freeze (info->folder);*/
		info->update = now;
		info->count++;
	}
}

static void
receive_status (CamelFilterDriver *driver,
                enum camel_filter_status_t status,
                gint pc,
                const gchar *desc,
                gpointer data)
{
	struct _send_info *info = data;
	time_t now = time (NULL);

	/* let it flow through to the folder, every now and then too? */
	g_hash_table_foreach (info->data->folders, (GHFunc)update_folders, &now);

	if (info->data->inbox && now > info->data->inbox_update+20) {
		d(printf("updating inbox too\n"));
		/* this doesn't seem to work right :( */
		/*camel_folder_thaw(info->data->inbox);
		  camel_folder_freeze (info->data->inbox);*/
		info->data->inbox_update = now;
	}

	/* we just pile them onto the port, assuming it can handle it.
	   We could also have a receiver port and see if they've been processed
	   yet, so if this is necessary its not too hard to add */
	/* the mail_gui_port receiver will free everything for us */
	switch (status) {
	case CAMEL_FILTER_STATUS_START:
	case CAMEL_FILTER_STATUS_END:
		set_send_status (info, desc, pc);
		break;
	case CAMEL_FILTER_STATUS_ACTION:
		set_send_account (info, desc);
		break;
	default:
		break;
	}
}

/* when receive/send is complete */
static void
receive_done (const gchar *uri, gpointer data)
{
	struct _send_info *info = data;

	/* if we've been called to run again - run again */
	if (info->type == SEND_SEND && info->state == SEND_ACTIVE && info->again) {
		CamelFolder *local_outbox;

		local_outbox = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_OUTBOX);

		info->again = 0;
		mail_send_queue (
			info->session,
			local_outbox, info->uri,
			E_FILTER_SOURCE_OUTGOING,
			info->cancellable,
			receive_get_folder, info,
			receive_status, info,
			receive_done, info);
		return;
	}

	if (info->progress_bar) {
		const gchar *text;

		gtk_progress_bar_set_fraction (
			GTK_PROGRESS_BAR (info->progress_bar), 1.0);

		if (info->state == SEND_CANCELLED)
			text = _("Canceled.");
		else {
			text = _("Complete.");
			info->state = SEND_COMPLETE;
		}

		gtk_label_set_text (GTK_LABEL (info->status_label), text);
	}

	if (info->cancel_button)
		gtk_widget_set_sensitive (info->cancel_button, FALSE);

	/* remove/free this active download */
	d(printf("%s: freeing info %p\n", G_STRFUNC, info));
	if (info->type == SEND_SEND)
		g_hash_table_steal (info->data->active, SEND_URI_KEY);
	else
		g_hash_table_steal (info->data->active, info->uri);
	info->data->infos = g_list_remove (info->data->infos, info);

	if (g_hash_table_size (info->data->active) == 0) {
		if (info->data->gd)
			gtk_widget_destroy ((GtkWidget *)info->data->gd);
		free_send_data ();
	}

	free_send_info (info);
}

/* although we dont do anythign smart here yet, there is no need for this interface to
   be available to anyone else.
   This can also be used to hook into which folders are being updated, and occasionally
   let them refresh */
static CamelFolder *
receive_get_folder (CamelFilterDriver *d,
                    const gchar *uri,
                    gpointer data,
                    GError **error)
{
	struct _send_info *info = data;
	CamelFolder *folder;
	struct _folder_info *oldinfo;
	gpointer oldkey, oldinfoptr;

	g_mutex_lock (info->data->lock);
	oldinfo = g_hash_table_lookup (info->data->folders, uri);
	g_mutex_unlock (info->data->lock);
	if (oldinfo) {
		g_object_ref (oldinfo->folder);
		return oldinfo->folder;
	}
	/* FIXME Not passing a GCancellable here. */
	folder = e_mail_session_uri_to_folder_sync (
		info->session, uri, 0, NULL, error);
	if (!folder)
		return NULL;

	/* we recheck that the folder hasn't snuck in while we were loading it... */
	/* and we assume the newer one is the same, but unref the old one anyway */
	g_mutex_lock (info->data->lock);

	if (g_hash_table_lookup_extended (info->data->folders, uri, &oldkey, &oldinfoptr)) {
		oldinfo = (struct _folder_info *) oldinfoptr;
		g_object_unref (oldinfo->folder);
		oldinfo->folder = folder;
	} else {
		/*camel_folder_freeze (folder);		*/
		oldinfo = g_malloc0 (sizeof (*oldinfo));
		oldinfo->folder = folder;
		oldinfo->uri = g_strdup (uri);
		g_hash_table_insert (info->data->folders, oldinfo->uri, oldinfo);
	}

	g_object_ref (folder);

	g_mutex_unlock (info->data->lock);

	return folder;
}

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

static void
get_folders (CamelStore *store, GPtrArray *folders, CamelFolderInfo *info)
{
	while (info) {
		if (camel_store_can_refresh_folder (store, info, NULL)) {
			CamelURL *url = camel_url_new (info->uri, NULL);

			if (url && (!camel_url_get_param (url, "noselect") ||
				!g_str_equal (camel_url_get_param (
				url, "noselect"), "yes")))
				g_ptr_array_add (folders, g_strdup (info->uri));

			if (url)
				camel_url_free (url);
		}

		get_folders (store, folders, info->child);
		info = info->next;
	}
}

struct _refresh_folders_msg {
	MailMsg base;

	struct _send_info *info;
	GPtrArray *folders;
	CamelStore *store;
	CamelFolderInfo *finfo;
};

static gchar *
refresh_folders_desc (struct _refresh_folders_msg *m)
{
	return g_strdup_printf(_("Checking for new mail"));
}

static void
refresh_folders_exec (struct _refresh_folders_msg *m,
                      GCancellable *cancellable,
                      GError **error)
{
	CamelFolder *folder;
	gint i;
	GError *local_error = NULL;

	get_folders (m->store, m->folders, m->finfo);

	for (i=0;i<m->folders->len;i++) {
		folder = e_mail_session_uri_to_folder_sync (
			m->info->session,
			m->folders->pdata[i], 0,
			cancellable, &local_error);
		if (folder) {
			/* FIXME Not passing a GError here. */
			camel_folder_synchronize_sync (
				folder, FALSE, cancellable, NULL);
			camel_folder_refresh_info_sync (folder, cancellable, NULL);
			g_object_unref (folder);
		} else if (local_error != NULL) {
			g_warning ("Failed to refresh folders: %s", local_error->message);
			g_clear_error (&local_error);
		}

		if (g_cancellable_is_cancelled (m->info->cancellable))
			break;
	}
}

static void
refresh_folders_done (struct _refresh_folders_msg *m)
{
	receive_done("", m->info);
}

static void
refresh_folders_free (struct _refresh_folders_msg *m)
{
	gint i;

	for (i=0;i<m->folders->len;i++)
		g_free (m->folders->pdata[i]);
	g_ptr_array_free (m->folders, TRUE);

	camel_store_free_folder_info (m->store, m->finfo);
	g_object_unref (m->store);
}

static MailMsgInfo refresh_folders_info = {
	sizeof (struct _refresh_folders_msg),
	(MailMsgDescFunc) refresh_folders_desc,
	(MailMsgExecFunc) refresh_folders_exec,
	(MailMsgDoneFunc) refresh_folders_done,
	(MailMsgFreeFunc) refresh_folders_free
};

static gboolean
receive_update_got_folderinfo (MailFolderCache *folder_cache,
                               CamelStore *store,
                               CamelFolderInfo *info,
                               gpointer data)
{
	if (info) {
		GPtrArray *folders = g_ptr_array_new ();
		struct _refresh_folders_msg *m;
		struct _send_info *sinfo = data;

		m = mail_msg_new (&refresh_folders_info);
		m->store = store;
		g_object_ref (store);
		m->folders = folders;
		m->info = sinfo;
		m->finfo = info;

		mail_msg_unordered_push (m);

		/* do not free folder info, we will free it later */
		return FALSE;
	} else {
		receive_done ("", data);
	}

	return TRUE;
}

static void
receive_update_got_store (gchar *uri, CamelStore *store, gpointer data)
{
	MailFolderCache *folder_cache;
	struct _send_info *info = data;

	folder_cache = e_mail_session_get_folder_cache (info->session);

	if (store) {
		mail_folder_cache_note_store (
			folder_cache,
			CAMEL_SESSION (info->session),
			store, info->cancellable,
			receive_update_got_folderinfo, info);
	} else {
		receive_done("", info);
	}
}

static GtkWidget *
send_receive (GtkWindow *parent, EMailSession *session, gboolean allow_send)
{
	CamelFolder *local_outbox;
	struct _send_data *data;
	EAccountList *accounts;
	EAccount *account;
	GList *scan;

	if (send_recv_dialog != NULL) {
		if (parent != NULL && gtk_widget_get_realized (send_recv_dialog)) {
			gtk_window_present (GTK_WINDOW (send_recv_dialog));
		}
		return send_recv_dialog;
	}

	if (!camel_session_get_online (CAMEL_SESSION (session)))
		return send_recv_dialog;

	account = e_get_default_account ();
	if (!account || !account->transport->url)
		return send_recv_dialog;

	accounts = e_get_account_list ();

	local_outbox = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_OUTBOX);
	data = build_dialog (
		parent, session, accounts,
		local_outbox, account->transport->url, allow_send);
	scan = data->infos;
	while (scan) {
		struct _send_info *info = scan->data;

		switch (info->type) {
		case SEND_RECEIVE:
			mail_fetch_mail (
				session, info->uri,
				info->keep_on_server,
				E_FILTER_SOURCE_INCOMING,
				info->cancellable,
				receive_get_folder, info,
				receive_status, info,
				receive_done, info);
			break;
		case SEND_SEND:
			/* todo, store the folder in info? */
			mail_send_queue (
				session, local_outbox, info->uri,
				E_FILTER_SOURCE_OUTGOING,
				info->cancellable,
				receive_get_folder, info,
				receive_status, info,
				receive_done, info);
			break;
		case SEND_UPDATE:
			mail_get_store (
				session, info->uri, info->cancellable,
				receive_update_got_store, info);
			break;
		default:
			break;
		}
		scan = scan->next;
	}

	return send_recv_dialog;
}

GtkWidget *
mail_send_receive (GtkWindow *parent, EMailSession *session)
{
	return send_receive (parent, session, TRUE);
}

GtkWidget *
mail_receive (GtkWindow *parent, EMailSession *session)
{
	return send_receive (parent, session, FALSE);
}

struct _auto_data {
	EAccount *account;
	EMailSession *session;
	gint period;		/* in seconds */
	gint timeout_id;
};

static GHashTable *auto_active;

static gboolean
auto_timeout (gpointer data)
{
	struct _auto_data *info = data;

	if (camel_session_get_online (CAMEL_SESSION (info->session))) {
		const gchar *uri;
		gboolean keep_on_server;

		uri = e_account_get_string (
			info->account, E_ACCOUNT_SOURCE_URL);
		keep_on_server = e_account_get_bool (
			info->account, E_ACCOUNT_SOURCE_KEEP_ON_SERVER);
		mail_receive_uri (info->session, uri, keep_on_server);
	}

	return TRUE;
}

static void
auto_account_removed (EAccountList *eal, EAccount *ea, gpointer dummy)
{
	struct _auto_data *info = g_object_get_data((GObject *)ea, "mail-autoreceive");

	g_return_if_fail (info != NULL);

	if (info->timeout_id) {
		g_source_remove (info->timeout_id);
		info->timeout_id = 0;
	}
}

static void
auto_account_finalised (struct _auto_data *info)
{
	if (info->session != NULL)
		g_object_unref (info->session);
	if (info->timeout_id)
		g_source_remove (info->timeout_id);
	g_free (info);
}

static void
auto_account_commit (struct _auto_data *info)
{
	gint period, check;

	check = info->account->enabled
		&& e_account_get_bool (info->account, E_ACCOUNT_SOURCE_AUTO_CHECK)
		&& e_account_get_string (info->account, E_ACCOUNT_SOURCE_URL);
	period = e_account_get_int (info->account, E_ACCOUNT_SOURCE_AUTO_CHECK_TIME)*60;
	period = MAX (60, period);

	if (info->timeout_id
	    && (!check
		|| period != info->period)) {
		g_source_remove (info->timeout_id);
		info->timeout_id = 0;
	}
	info->period = period;
	if (check && info->timeout_id == 0)
		info->timeout_id = g_timeout_add_seconds (info->period, auto_timeout, info);
}

static void
auto_account_added (EAccountList *eal,
                    EAccount *ea,
                    EMailSession *session)
{
	struct _auto_data *info;

	info = g_malloc0 (sizeof (*info));
	info->account = ea;
	info->session = g_object_ref (session);
	g_object_set_data_full (
		G_OBJECT (ea), "mail-autoreceive", info,
		(GDestroyNotify) auto_account_finalised);
	auto_account_commit (info);
}

static void
auto_account_changed (EAccountList *eal, EAccount *ea, gpointer dummy)
{
	struct _auto_data *info = g_object_get_data((GObject *)ea, "mail-autoreceive");

	g_return_if_fail (info != NULL);

	auto_account_commit (info);
}

static void
auto_online (EShell *shell)
{
	EIterator *iter;
	EAccountList *accounts;
	EShellSettings *shell_settings;
	struct _auto_data *info;
	gboolean can_update_all;

	if (!e_shell_get_online (shell))
		return;

	shell_settings = e_shell_get_shell_settings (shell);

	can_update_all =
		e_shell_settings_get_boolean (
			shell_settings, "mail-check-on-start") &&
		e_shell_settings_get_boolean (
			shell_settings, "mail-check-all-on-start");

	accounts = e_get_account_list ();
	for (iter = e_list_get_iterator ((EList *)accounts);
	     e_iterator_is_valid (iter);
	     e_iterator_next (iter)) {
		EAccount *account = (EAccount *) e_iterator_get (iter);

		if (!account || !account->enabled)
			continue;

		info = g_object_get_data (
			G_OBJECT (account), "mail-autoreceive");
		if (info && (info->timeout_id || can_update_all))
			auto_timeout (info);
	}

	if (iter)
		g_object_unref (iter);
}

/* call to setup initial, and after changes are made to the config */
/* FIXME: Need a cleanup funciton for when object is deactivated */
void
mail_autoreceive_init (EMailBackend *backend)
{
	EShellBackend *shell_backend;
	EShellSettings *shell_settings;
	EMailSession *session;
	EAccountList *accounts;
	EIterator *iter;
	EShell *shell;

	g_return_if_fail (E_IS_MAIL_BACKEND (backend));

	if (auto_active)
		return;

	accounts = e_get_account_list ();
	auto_active = g_hash_table_new (g_str_hash, g_str_equal);

	session = e_mail_backend_get_session (backend);

	g_signal_connect (
		accounts, "account-added",
		G_CALLBACK (auto_account_added), session);
	g_signal_connect (
		accounts, "account-removed",
		G_CALLBACK (auto_account_removed), NULL);
	g_signal_connect (
		accounts, "account-changed",
		G_CALLBACK (auto_account_changed), NULL);

	for (iter = e_list_get_iterator ((EList *)accounts);
	     e_iterator_is_valid (iter);
	     e_iterator_next (iter))
		auto_account_added (
			accounts, (EAccount *)
			e_iterator_get (iter), session);

	shell_backend = E_SHELL_BACKEND (backend);
	shell = e_shell_backend_get_shell (shell_backend);
	shell_settings = e_shell_get_shell_settings (shell);

	if (e_shell_settings_get_boolean (
		shell_settings, "mail-check-on-start")) {
		auto_online (shell);

		/* also flush outbox on start */
		if (e_shell_get_online (shell))
			mail_send (session);
	}

	g_signal_connect (
		shell, "notify::online",
		G_CALLBACK (auto_online), NULL);
}

/* We setup the download info's in a hashtable, if we later
 * need to build the gui, we insert them in to add them. */
void
mail_receive_uri (EMailSession *session,
                  const gchar *uri,
                  gboolean keep_on_server)
{
	struct _send_info *info;
	struct _send_data *data;
	CamelFolder *local_outbox;
	send_info_t type;

	data = setup_send_data ();
	info = g_hash_table_lookup (data->active, uri);
	if (info != NULL) {
		d(printf("download of %s still in progress\n", uri));
		return;
	}

	d(printf("starting non-interactive download of '%s'\n", uri));

	type = get_receive_type (uri);
	if (type == SEND_INVALID || type == SEND_SEND) {
		d(printf ("unsupported provider: '%s'\n", uri));
		return;
	}

	info = g_malloc0 (sizeof (*info));
	info->type = type;
	info->session = g_object_ref (session);
	info->progress_bar = NULL;
	info->status_label = NULL;
	info->uri = g_strdup (uri);
	info->keep_on_server = keep_on_server;
	info->cancellable = camel_operation_new ();
	info->cancel_button = NULL;
	info->data = data;
	info->state = SEND_ACTIVE;
	info->timeout_id = 0;

	g_signal_connect (
		info->cancellable, "status",
		G_CALLBACK (operation_status), info);

	d(printf("Adding new info %p\n", info));

	g_hash_table_insert (data->active, info->uri, info);

	switch (info->type) {
	case SEND_RECEIVE:
		mail_fetch_mail (
			info->session, info->uri,
			info->keep_on_server,
			E_FILTER_SOURCE_INCOMING,
			info->cancellable,
			receive_get_folder, info,
			receive_status, info,
			receive_done, info);
		break;
	case SEND_SEND:
		/* todo, store the folder in info? */
		local_outbox = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_OUTBOX);
		mail_send_queue (
			info->session,
			local_outbox, info->uri,
			E_FILTER_SOURCE_OUTGOING,
			info->cancellable,
			receive_get_folder, info,
			receive_status, info,
			receive_done, info);
		break;
	case SEND_UPDATE:
		mail_get_store (
			info->session,
			info->uri, info->cancellable,
			receive_update_got_store, info);
		break;
	default:
		g_return_if_reached ();
	}
}

void
mail_send (EMailSession *session)
{
	CamelFolder *local_outbox;
	EAccountService *transport;
	struct _send_info *info;
	struct _send_data *data;
	send_info_t type;

	transport = e_get_default_transport ();
	if (!transport || !transport->url)
		return;

	data = setup_send_data ();
	info = g_hash_table_lookup (data->active, SEND_URI_KEY);
	if (info != NULL) {
		info->again++;
		d(printf("send of %s still in progress\n", transport->url));
		return;
	}

	d(printf("starting non-interactive send of '%s'\n", transport->url));

	type = get_receive_type (transport->url);
	if (type == SEND_INVALID) {
		d(printf ("unsupported provider: '%s'\n", transport->url));
		return;
	}

	info = g_malloc0 (sizeof (*info));
	info->type = SEND_SEND;
	info->session = g_object_ref (session);
	info->progress_bar = NULL;
	info->status_label = NULL;
	info->uri = g_strdup (transport->url);
	info->keep_on_server = FALSE;
	info->cancellable = NULL;
	info->cancel_button = NULL;
	info->data = data;
	info->state = SEND_ACTIVE;
	info->timeout_id = 0;

	d(printf("Adding new info %p\n", info));

	g_hash_table_insert (data->active, (gpointer) SEND_URI_KEY, info);

	/* todo, store the folder in info? */
	local_outbox = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_OUTBOX);
	mail_send_queue (
		session,
		local_outbox, info->uri,
		E_FILTER_SOURCE_OUTGOING,
		info->cancellable,
		receive_get_folder, info,
		receive_status, info,
		receive_done, info);
}
