/*
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "defs.h"

#include <glib.h>
#include <glib/gi18n.h>
#include <stdio.h>
#include <string.h>
#include "imap.h"
#include "imap_gtk.h"
#include "inc.h"
#include "xml.h"

#ifdef HAVE_LIBETPAN

#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#if HAVE_ICONV
#  include <iconv.h>
#endif

#if USE_OPENSSL
#  include "ssl.h"
#endif

#include "folder.h"
#include "session.h"
#include "procmsg.h"
#include "socket.h"
#include "recv.h"
#include "procheader.h"
#include "prefs_account.h"
#include "codeconv.h"
#include "md5.h"
#include "base64.h"
#include "utils.h"
#include "prefs_common.h"
#include "inputdialog.h"
#include "log.h"
#include "remotefolder.h"
#include "alertpanel.h"
#include "sylpheed.h"
#include "statusbar.h"
#include "msgcache.h"
#include "imap-thread.h"

typedef struct _IMAPFolder	IMAPFolder;
typedef struct _IMAPSession	IMAPSession;
typedef struct _IMAPNameSpace	IMAPNameSpace;
typedef struct _IMAPFolderItem	IMAPFolderItem;

#include "prefs_account.h"

#define IMAP_FOLDER(obj)	((IMAPFolder *)obj)
#define IMAP_FOLDER_ITEM(obj)	((IMAPFolderItem *)obj)
#define IMAP_SESSION(obj)	((IMAPSession *)obj)

struct _IMAPFolder
{
	RemoteFolder rfolder;

	/* list of IMAPNameSpace */
	GList *ns_personal;
	GList *ns_others;
	GList *ns_shared;
	gchar last_seen_separator;
	guint refcnt;
};

struct _IMAPSession
{
	Session session;

	gboolean authenticated;

	GSList *capability;
	gboolean uidplus;

	gchar *mbox;
	guint cmd_count;

	/* CLAWS */
	gboolean folder_content_changed;
	guint exists;
	Folder * folder;
	gboolean busy;
};

struct _IMAPNameSpace
{
	gchar *name;
	gchar separator;
};

#define IMAP_SUCCESS	0
#define IMAP_SOCKET	2
#define IMAP_AUTHFAIL	3
#define IMAP_PROTOCOL	4
#define IMAP_SYNTAX	5
#define IMAP_IOERR	6
#define IMAP_ERROR	7

#define IMAPBUFSIZE	8192

typedef enum
{
	IMAP_FLAG_SEEN		= 1 << 0,
	IMAP_FLAG_ANSWERED	= 1 << 1,
	IMAP_FLAG_FLAGGED	= 1 << 2,
	IMAP_FLAG_DELETED	= 1 << 3,
	IMAP_FLAG_DRAFT		= 1 << 4
} IMAPFlags;

#define IMAP_IS_SEEN(flags)	((flags & IMAP_FLAG_SEEN) != 0)
#define IMAP_IS_ANSWERED(flags)	((flags & IMAP_FLAG_ANSWERED) != 0)
#define IMAP_IS_FLAGGED(flags)	((flags & IMAP_FLAG_FLAGGED) != 0)
#define IMAP_IS_DELETED(flags)	((flags & IMAP_FLAG_DELETED) != 0)
#define IMAP_IS_DRAFT(flags)	((flags & IMAP_FLAG_DRAFT) != 0)


#define IMAP4_PORT	143
#if USE_OPENSSL
#define IMAPS_PORT	993
#endif

#define IMAP_CMD_LIMIT	1000

struct _IMAPFolderItem
{
	FolderItem item;

	guint lastuid;
	guint uid_next;
	GSList *uid_list;
	gboolean batching;

	time_t use_cache;
	gint c_messages;
	guint32 c_uid_next;
	guint32 c_uid_validity;
	gint c_unseen;

	GHashTable *flags_set_table;
	GHashTable *flags_unset_table;
};

static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);

static void imap_folder_init		(Folder		*folder,
					 const gchar	*name,
					 const gchar	*path);

static Folder	*imap_folder_new	(const gchar	*name,
					 const gchar	*path);
static void	 imap_folder_destroy	(Folder		*folder);

static IMAPSession *imap_session_new	(Folder         *folder,
					 const PrefsAccount 	*account);
static void 	imap_session_authenticate(IMAPSession 		*session,
				      	  const PrefsAccount 	*account);
static void 	imap_session_destroy	(Session 	*session);

static gchar   *imap_fetch_msg		(Folder 	*folder, 
					 FolderItem 	*item, 
					 gint 		 uid);
static gchar   *imap_fetch_msg_full	(Folder 	*folder, 
					 FolderItem 	*item, 
					 gint 		 uid,
					 gboolean	 headers,
					 gboolean	 body);
static gint 	imap_add_msg		(Folder 	*folder,
			 		 FolderItem 	*dest,
			 		 const gchar 	*file, 
					 MsgFlags 	*flags);
static gint 	imap_add_msgs		(Folder 	*folder, 
					 FolderItem 	*dest,
			  		 GSList 	*file_list,
			  		 GRelation 	*relation);

static gint 	imap_copy_msg		(Folder 	*folder,
			  		 FolderItem 	*dest, 
					 MsgInfo 	*msginfo);
static gint 	imap_copy_msgs		(Folder 	*folder, 
					 FolderItem 	*dest, 
		    			 MsgInfoList 	*msglist, 
					 GRelation 	*relation);

static gint 	imap_remove_msg		(Folder 	*folder, 
					 FolderItem 	*item, 
					 gint 		 uid);
static gint 	imap_remove_msgs	(Folder 	*folder, 
					 FolderItem 	*dest, 
		    			 MsgInfoList 	*msglist, 
					 GRelation 	*relation);
static gint 	imap_remove_all_msg	(Folder 	*folder, 
					 FolderItem 	*item);

static gboolean imap_is_msg_changed	(Folder 	*folder,
				    	 FolderItem 	*item, 
					 MsgInfo 	*msginfo);

static gint 	imap_close		(Folder 	*folder, 
					 FolderItem 	*item);

static gint 	imap_scan_tree		(Folder 	*folder);

static gint 	imap_create_tree	(Folder 	*folder);

static FolderItem *imap_create_folder	(Folder 	*folder,
				      	 FolderItem 	*parent,
				      	 const gchar 	*name);
static gint 	imap_rename_folder	(Folder 	*folder,
			       		 FolderItem 	*item, 
					 const gchar 	*name);
static gint 	imap_remove_folder	(Folder 	*folder, 
					 FolderItem 	*item);

static FolderItem *imap_folder_item_new	(Folder		*folder);
static void imap_folder_item_destroy	(Folder		*folder,
					 FolderItem	*item);

static IMAPSession *imap_session_get	(Folder		*folder);

static gint imap_auth			(IMAPSession	*session,
					 const gchar	*user,
					 const gchar	*pass,
					 IMAPAuthType	 type);

static gint imap_scan_tree_recursive	(IMAPSession	*session,
					 FolderItem	*item);

static void imap_create_missing_folders	(Folder		*folder);
static FolderItem *imap_create_special_folder
					(Folder			*folder,
					 SpecialFolderItemType	 stype,
					 const gchar		*name);

static gint imap_do_copy_msgs		(Folder		*folder,
					 FolderItem	*dest,
					 MsgInfoList	*msglist,
					 GRelation	*relation);

static void imap_delete_all_cached_messages	(FolderItem	*item);
static void imap_set_batch		(Folder		*folder,
					 FolderItem	*item,
					 gboolean	 batch);
static gint imap_set_message_flags	(IMAPSession	*session,
					 MsgNumberList	*numlist,
					 IMAPFlags	 flags,
					 gboolean	 is_set);
static gint imap_select			(IMAPSession	*session,
					 IMAPFolder	*folder,
					 const gchar	*path,
					 gint		*exists,
					 gint		*recent,
					 gint		*unseen,
					 guint32	*uid_validity,
					 gboolean	 block);
static gint imap_status			(IMAPSession	*session,
					 IMAPFolder	*folder,
					 const gchar	*path,
					 IMAPFolderItem *item,
					 gint		*messages,
					 guint32	*uid_next,
					 guint32	*uid_validity,
					 gint		*unseen,
					 gboolean	 block);

static IMAPNameSpace *imap_find_namespace	(IMAPFolder	*folder,
						 const gchar	*path);
static gchar imap_get_path_separator		(IMAPFolder	*folder,
						 const gchar	*path);
static gchar *imap_get_real_path		(IMAPFolder	*folder,
						 const gchar	*path);
static void imap_synchronise		(FolderItem	*item);

static void imap_free_capabilities	(IMAPSession 	*session);

/* low-level IMAP4rev1 commands */
static gint imap_cmd_login	(IMAPSession	*session,
				 const gchar	*user,
				 const gchar	*pass,
				 const gchar 	*type);
static gint imap_cmd_logout	(IMAPSession	*session);
static gint imap_cmd_noop	(IMAPSession	*session);
#if USE_OPENSSL
static gint imap_cmd_starttls	(IMAPSession	*session);
#endif
static gint imap_cmd_select	(IMAPSession	*session,
				 const gchar	*folder,
				 gint		*exists,
				 gint		*recent,
				 gint		*unseen,
				 guint32	*uid_validity,
				 gboolean	 block);
static gint imap_cmd_examine	(IMAPSession	*session,
				 const gchar	*folder,
				 gint		*exists,
				 gint		*recent,
				 gint		*unseen,
				 guint32	*uid_validity,
				 gboolean	 block);
static gint imap_cmd_create	(IMAPSession	*sock,
				 const gchar	*folder);
static gint imap_cmd_rename	(IMAPSession	*sock,
				 const gchar	*oldfolder,
				 const gchar	*newfolder);
static gint imap_cmd_delete	(IMAPSession	*session,
				 const gchar	*folder);
static gint imap_cmd_fetch	(IMAPSession	*sock,
				 guint32	 uid,
				 const gchar	*filename,
				 gboolean	 headers,
				 gboolean	 body);
static gint imap_cmd_append	(IMAPSession	*session,
				 const gchar	*destfolder,
				 const gchar	*file,
				 IMAPFlags	 flags,
				 guint32	*new_uid);
static gint imap_cmd_copy       (IMAPSession *session,
				 struct mailimap_set * set,
				 const gchar *destfolder,
				 GRelation *uid_mapping);
static gint imap_cmd_store	(IMAPSession	*session,
				 struct mailimap_set * set,
				 IMAPFlags flags,
				 int do_add);
static gint imap_cmd_expunge	(IMAPSession	*session);

static void imap_path_separator_subst		(gchar		*str,
						 gchar		 separator);

static gchar *imap_utf8_to_modified_utf7	(const gchar	*from);
static gchar *imap_modified_utf7_to_utf8	(const gchar	*mutf7_str);

static gboolean imap_rename_folder_func		(GNode		*node,
						 gpointer	 data);
static gint imap_get_num_list			(Folder 	*folder,
						 FolderItem 	*item,
						 GSList	       **list,
						 gboolean	*old_uids_valid);
static GSList *imap_get_msginfos		(Folder		*folder,
						 FolderItem	*item,
						 GSList		*msgnum_list);
static MsgInfo *imap_get_msginfo 		(Folder 	*folder,
						 FolderItem 	*item,
						 gint 		 num);
static gboolean imap_scan_required		(Folder 	*folder,
						 FolderItem 	*item);
static void imap_change_flags			(Folder 	*folder,
						 FolderItem 	*item,
						 MsgInfo 	*msginfo,
						 MsgPermFlags 	 newflags);
static gint imap_get_flags			(Folder 	*folder,
						 FolderItem 	*item,
                    				 MsgInfoList 	*msglist,
						 GRelation 	*msgflags);
static gchar *imap_folder_get_path		(Folder		*folder);
static gchar *imap_item_get_path		(Folder		*folder,
						 FolderItem	*item);
static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item);


/* data types conversion libetpan <-> sylpheed */
static GSList * imap_list_from_lep(IMAPFolder * folder,
				   clist * list, const gchar * real_path, gboolean all);
static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist);
static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist);
static GSList * imap_uid_list_from_lep(clist * list);
static GSList * imap_uid_list_from_lep_tab(carray * list);
static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
				       FolderItem *item);
static void imap_lep_set_free(GSList *seq_list);
static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags);

typedef struct _hashtable_data {
	IMAPSession *session;
	GSList *msglist;
	IMAPFolderItem *item;
} hashtable_data;

static FolderClass imap_class;

typedef struct _thread_data {
	gchar *server;
	gushort port;
	gboolean done;
	SockInfo *sock;
#ifdef USE_OPENSSL
	SSLType ssl_type;
#endif
} thread_data;

FolderClass *imap_get_class(void)
{
	if (imap_class.idstr == NULL) {
		imap_class.type = F_IMAP;
		imap_class.idstr = "imap";
		imap_class.uistr = "IMAP4";

		/* Folder functions */
		imap_class.new_folder = imap_folder_new;
		imap_class.destroy_folder = imap_folder_destroy;
		imap_class.scan_tree = imap_scan_tree;
		imap_class.create_tree = imap_create_tree;

		/* FolderItem functions */
		imap_class.item_new = imap_folder_item_new;
		imap_class.item_destroy = imap_folder_item_destroy;
		imap_class.item_get_path = imap_item_get_path;
		imap_class.create_folder = imap_create_folder;
		imap_class.rename_folder = imap_rename_folder;
		imap_class.remove_folder = imap_remove_folder;
		imap_class.close = imap_close;
		imap_class.get_num_list = imap_get_num_list;
		imap_class.scan_required = imap_scan_required;
		imap_class.set_xml = folder_set_xml;
		imap_class.get_xml = folder_get_xml;
		imap_class.item_set_xml = imap_item_set_xml;
		imap_class.item_get_xml = imap_item_get_xml;

		/* Message functions */
		imap_class.get_msginfo = imap_get_msginfo;
		imap_class.get_msginfos = imap_get_msginfos;
		imap_class.fetch_msg = imap_fetch_msg;
		imap_class.fetch_msg_full = imap_fetch_msg_full;
		imap_class.add_msg = imap_add_msg;
		imap_class.add_msgs = imap_add_msgs;
		imap_class.copy_msg = imap_copy_msg;
		imap_class.copy_msgs = imap_copy_msgs;
		imap_class.remove_msg = imap_remove_msg;
		imap_class.remove_msgs = imap_remove_msgs;
		imap_class.remove_all_msg = imap_remove_all_msg;
		imap_class.is_msg_changed = imap_is_msg_changed;
		imap_class.change_flags = imap_change_flags;
		imap_class.get_flags = imap_get_flags;
		imap_class.set_batch = imap_set_batch;
		imap_class.synchronise = imap_synchronise;
#ifdef USE_PTREAD
		pthread_mutex_init(&imap_mutex, NULL);
#endif
	}
	
	return &imap_class;
}

static Folder *imap_folder_new(const gchar *name, const gchar *path)
{
	Folder *folder;

	folder = (Folder *)g_new0(IMAPFolder, 1);
	folder->klass = &imap_class;
	imap_folder_init(folder, name, path);

	return folder;
}

static void imap_folder_destroy(Folder *folder)
{
	while (imap_folder_get_refcnt(folder) > 0)
		gtk_main_iteration();
	
	folder_remote_folder_destroy(REMOTE_FOLDER(folder));
	imap_done(folder);
}

static void imap_folder_init(Folder *folder, const gchar *name,
			     const gchar *path)
{
	folder_remote_folder_init((Folder *)folder, name, path);
}

static FolderItem *imap_folder_item_new(Folder *folder)
{
	IMAPFolderItem *item;
	
	item = g_new0(IMAPFolderItem, 1);
	item->lastuid = 0;
	item->uid_next = 0;
	item->uid_list = NULL;

	return (FolderItem *)item;
}

static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
{
	IMAPFolderItem *item = (IMAPFolderItem *)_item;

	g_return_if_fail(item != NULL);
	g_slist_free(item->uid_list);

	g_free(_item);
}

static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
{
	IMAPFolderItem *item = (IMAPFolderItem *)node->data;
	
	item->lastuid = 0;
	g_slist_free(item->uid_list);
	item->uid_list = NULL;
	
	return FALSE;
}

static void imap_reset_uid_lists(Folder *folder)
{
	if(folder->node == NULL)
		return;
	
	/* Destroy all uid lists and rest last uid */
	g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);	
}

void imap_get_capabilities(IMAPSession *session)
{
	struct mailimap_capability_data *capabilities = NULL;
	clistiter *cur;

	if (session->capability != NULL)
		return;

	capabilities = imap_threaded_capability(session->folder);

	if (capabilities == NULL)
		return;

	for(cur = clist_begin(capabilities->cap_list) ; cur != NULL ;
	    cur = clist_next(cur)) {
		struct mailimap_capability * cap = 
			clist_content(cur);
		if (!cap || cap->cap_data.cap_name == NULL)
			continue;
		session->capability = g_slist_append
				(session->capability,
				 g_strdup(cap->cap_data.cap_name));
		debug_print("got capa %s\n", cap->cap_data.cap_name);
	}
	mailimap_capability_data_free(capabilities);
}

gboolean imap_has_capability(IMAPSession *session, const gchar *cap) 
{
	GSList *cur;
	for (cur = session->capability; cur; cur = cur->next) {
		if (!g_ascii_strcasecmp(cur->data, cap))
			return TRUE;
	}
	return FALSE;
}

static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
		      IMAPAuthType type)
{
	gint ok = IMAP_ERROR;
	static time_t last_login_err = 0;
	
	imap_get_capabilities(session);

	switch(type) {
	case IMAP_AUTH_ANON:
		ok = imap_cmd_login(session, user, pass, "ANONYMOUS");
		break;
	case IMAP_AUTH_CRAM_MD5:
		ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
		break;
	case IMAP_AUTH_LOGIN:
		ok = imap_cmd_login(session, user, pass, "LOGIN");
		break;
	default:
		debug_print("capabilities:\n"
				"\t ANONYMOUS %d\n"
				"\t CRAM-MD5 %d\n"
				"\t LOGIN %d\n", 
			imap_has_capability(session, "ANONYMOUS"),
			imap_has_capability(session, "CRAM-MD5"),
			imap_has_capability(session, "LOGIN"));
		if (imap_has_capability(session, "CRAM-MD5"))
			ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
		if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
			ok = imap_cmd_login(session, user, pass, "LOGIN");
	}
	if (ok == IMAP_SUCCESS)
		session->authenticated = TRUE;
	else {
		gchar *ext_info = NULL;
		
		if (type == IMAP_AUTH_CRAM_MD5) {
			ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
				     "compiled with SASL support and the "
				     "CRAM-MD5 SASL plugin is installed.");
		} else {
			ext_info = "";
		}
		
		if (time(NULL) - last_login_err > 10) {
			if (!prefs_common.no_recv_err_panel) {
				alertpanel_error(_("Connection to %s failed: "
					"login refused.%s"),
					SESSION(session)->server, ext_info);
			} else {
				log_error(_("Connection to %s failed: "
					"login refused.%s\n"),
					SESSION(session)->server, ext_info);
			}
		}
		last_login_err = time(NULL);
	}
	return ok;
}

static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
{
	RemoteFolder *rfolder = REMOTE_FOLDER(folder);
	/* Check if this is the first try to establish a
	   connection, if yes we don't try to reconnect */
	debug_print("reconnecting\n");
	if (rfolder->session == NULL) {
		log_warning(_("Connecting to %s failed"),
			    folder->account->recv_server);
		session_destroy(SESSION(session));
		session = NULL;
	} else {
		log_warning(_("IMAP4 connection to %s has been"
			    " disconnected. Reconnecting...\n"),
			    folder->account->recv_server);
		statusbar_print_all(_("IMAP4 connection to %s has been"
			    " disconnected. Reconnecting...\n"),
			    folder->account->recv_server);
		SESSION(session)->state = SESSION_DISCONNECTED;
		session_destroy(SESSION(session));
		/* Clear folders session to make imap_session_get create
		   a new session, because of rfolder->session == NULL
		   it will not try to reconnect again and so avoid an
		   endless loop */
		rfolder->session = NULL;
		session = imap_session_get(folder);
		rfolder->session = SESSION(session);
		statusbar_pop_all();
	}
	return session;
}

#define lock_session() {\
	debug_print("locking session\n"); \
	session->busy = TRUE;\
}

#define unlock_session() {\
	debug_print("unlocking session\n"); \
	session->busy = FALSE;\
}

static IMAPSession *imap_session_get(Folder *folder)
{
	RemoteFolder *rfolder = REMOTE_FOLDER(folder);
	IMAPSession *session = NULL;
	static time_t last_failure = 0;

	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
	g_return_val_if_fail(folder->account != NULL, NULL);
	
	if (prefs_common.work_offline && 
	    !inc_offline_should_override(
		_("Sylpheed-Claws needs network access in order "
		  "to access the IMAP server."))) {
		return NULL;
	}

	/* Make sure we have a session */
	if (rfolder->session != NULL) {
		session = IMAP_SESSION(rfolder->session);
		/* don't do that yet... 
		if (session->busy) {
			return NULL;
		} */
	} else {
		imap_reset_uid_lists(folder);
		if (time(NULL) - last_failure <= 2)
			return NULL;
		session = imap_session_new(folder, folder->account);
	}
	if(session == NULL) {
		last_failure = time(NULL);
		return NULL;
	}

	/* Make sure session is authenticated */
	if (!IMAP_SESSION(session)->authenticated)
		imap_session_authenticate(IMAP_SESSION(session), folder->account);
	
	if (!IMAP_SESSION(session)->authenticated) {
		session_destroy(SESSION(session));
		rfolder->session = NULL;
		last_failure = time(NULL);
		return NULL;
	}

	/* I think the point of this code is to avoid sending a
	 * keepalive if we've used the session recently and therefore
	 * think it's still alive.  Unfortunately, most of the code
	 * does not yet check for errors on the socket, and so if the
	 * connection drops we don't notice until the timeout expires.
	 * A better solution than sending a NOOP every time would be
	 * for every command to be prepared to retry until it is
	 * successfully sent. -- mbp */
	if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
		/* verify that the session is still alive */
		if (imap_cmd_noop(session) != IMAP_SUCCESS) {
			debug_print("disconnected!\n");
			session = imap_reconnect_if_possible(folder, session);
		}
	}

	rfolder->session = SESSION(session);
	
	return IMAP_SESSION(session);
}

static IMAPSession *imap_session_new(Folder * folder,
				     const PrefsAccount *account)
{
	IMAPSession *session;
	gushort port;
	int r;
	int authenticated;
	
#ifdef USE_OPENSSL
	/* FIXME: IMAP over SSL only... */ 
	SSLType ssl_type;

	port = account->set_imapport ? account->imapport
		: account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
	ssl_type = account->ssl_imap;	
#else
	if (account->ssl_imap != SSL_NONE) {
		if (alertpanel_full(_("Insecure connection"),
			_("This connection is configured to be secured "
			  "using SSL, but SSL is not available in this "
			  "build of Sylpheed-Claws. \n\n"
			  "Do you want to continue connecting to this "
			  "server? The communication would not be "
			  "secure."),
			  GTK_STOCK_CANCEL, _("Con_tinue connecting"), 
			  NULL, FALSE, NULL, ALERT_WARNING,
			  G_ALERTDEFAULT) != G_ALERTALTERNATE)
			return NULL;
	}
	port = account->set_imapport ? account->imapport
		: IMAP4_PORT;
#endif

	imap_init(folder);
	statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
	if (account->set_tunnelcmd) {
		r = imap_threaded_connect_cmd(folder,
					      account->tunnelcmd,
					      account->recv_server,
					      port);
	}
	else {
#ifdef USE_OPENSSL
		if (ssl_type == SSL_TUNNEL) {
			r = imap_threaded_connect_ssl(folder,
						      account->recv_server,
						      port);
		}
		else 
#endif
		{
			r = imap_threaded_connect(folder,
						  account->recv_server,
						  port);
		}
	}
	
	statusbar_pop_all();
	if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
		authenticated = TRUE;
	}
	else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
		authenticated = FALSE;
	}
	else {
		if(!prefs_common.no_recv_err_panel) {
			alertpanel_error(_("Can't connect to IMAP4 server: %s:%d"),
					 account->recv_server, port);
		} else {
			log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
					 account->recv_server, port);
		} 
		
		return NULL;
	}
	
	session = g_new0(IMAPSession, 1);
	session_init(SESSION(session));
	SESSION(session)->type             = SESSION_IMAP;
	SESSION(session)->server           = g_strdup(account->recv_server);
 	SESSION(session)->sock             = NULL;
	
	SESSION(session)->destroy          = imap_session_destroy;

	session->capability = NULL;
	
	session->authenticated = authenticated;
	session->mbox = NULL;
	session->cmd_count = 0;
	session->folder = folder;
	IMAP_FOLDER(session->folder)->last_seen_separator = 0;

#if USE_OPENSSL
	if (account->ssl_imap == SSL_STARTTLS) {
		gint ok;

		ok = imap_cmd_starttls(session);
		if (ok != IMAP_SUCCESS) {
			log_warning(_("Can't start TLS session.\n"));
			session_destroy(SESSION(session));
			return NULL;
		}

		imap_free_capabilities(session);
		session->authenticated = FALSE;
		session->uidplus = FALSE;
		session->cmd_count = 1;
	}
#endif
	log_message("IMAP connection is %s-authenticated\n",
		    (session->authenticated) ? "pre" : "un");
	
	return session;
}

static void imap_session_authenticate(IMAPSession *session, 
				      const PrefsAccount *account)
{
	gchar *pass;

	g_return_if_fail(account->userid != NULL);

	pass = account->passwd;
	if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
		gchar *tmp_pass;
		tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
		if (!tmp_pass)
			return;
		Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
		g_free(tmp_pass);
	} else if (account->imap_auth_type == IMAP_AUTH_ANON) {
		pass = "";
	}
	statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
				account->recv_server);
	if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
		imap_threaded_disconnect(session->folder);
		imap_cmd_logout(session);
		statusbar_pop_all();
		
		return;
	}
	statusbar_pop_all();
	session->authenticated = TRUE;
}

static void imap_session_destroy(Session *session)
{
	if (session->state != SESSION_DISCONNECTED)
		imap_threaded_disconnect(IMAP_SESSION(session)->folder);
	
	imap_free_capabilities(IMAP_SESSION(session));
	g_free(IMAP_SESSION(session)->mbox);
	sock_close(session->sock);
	session->sock = NULL;
}

static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
{
	return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
}

static guint get_size_with_crs(MsgInfo *info) 
{
	FILE *fp = NULL;
	guint cnt = 0;
	gchar buf[4096];
	
	if (info == NULL)
		return -1;
	
	fp = procmsg_open_message(info);
	if (!fp)
		return -1;
	
	while (fgets(buf, sizeof (buf), fp) != NULL) {
		cnt += strlen(buf);
		if (!strstr(buf, "\r") && strstr(buf, "\n"))
			cnt++;
	}
	
	fclose(fp);
	return cnt;
}

static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
				  gboolean headers, gboolean body)
{
	gchar *path, *filename;
	IMAPSession *session;
	gint ok;

	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(item != NULL, NULL);

	if (uid == 0)
		return NULL;

	path = folder_item_get_path(item);
	if (!is_dir_exist(path))
		make_dir_hier(path);
	filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
	g_free(path);
	debug_print("trying to fetch cached %s\n", filename);
	if (is_file_exist(filename)) {
		/* see whether the local file represents the whole message
		 * or not. As the IMAP server reports size with \r chars,
		 * we have to update the local file (UNIX \n only) size */
		MsgInfo *msginfo = imap_parse_msg(filename, item);
		MsgInfo *cached = msgcache_get_msg(item->cache,uid);
		guint have_size = get_size_with_crs(msginfo);

		if (cached)
			debug_print("message %d has been already %scached (%d/%d).\n", uid,
				have_size == cached->size ? "fully ":"",
				have_size, (int)cached->size);
		
		if (cached && (cached->size == have_size || !body)) {
			procmsg_msginfo_free(cached);
			procmsg_msginfo_free(msginfo);
			file_strip_crs(filename);
			return filename;
		} else if (!cached) {
			debug_print("message not cached, considering file complete\n");
			procmsg_msginfo_free(msginfo);
			file_strip_crs(filename);
			return filename;
		} else {
			procmsg_msginfo_free(cached);
			procmsg_msginfo_free(msginfo);
		}
	}

	session = imap_session_get(folder);
	
	if (!session) {
		g_free(filename);
		return NULL;
	}

	lock_session();

	debug_print("IMAP fetching messages\n");
	ok = imap_select(session, IMAP_FOLDER(folder), item->path,
			 NULL, NULL, NULL, NULL, FALSE);
	if (ok != IMAP_SUCCESS) {
		g_warning("can't select mailbox %s\n", item->path);
		g_free(filename);
		unlock_session();
		return NULL;
	}

	debug_print("getting message %d...\n", uid);
	ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);

	if (ok != IMAP_SUCCESS) {
		g_warning("can't fetch message %d\n", uid);
		g_free(filename);
		unlock_session();
		return NULL;
	}

	unlock_session();
	file_strip_crs(filename);
	return filename;
}

static gint imap_add_msg(Folder *folder, FolderItem *dest, 
			 const gchar *file, MsgFlags *flags)
{
	gint ret;
	GSList file_list;
	MsgFileInfo fileinfo;

	g_return_val_if_fail(file != NULL, -1);

	fileinfo.msginfo = NULL;
	fileinfo.file = (gchar *)file;
	fileinfo.flags = flags;
	file_list.data = &fileinfo;
	file_list.next = NULL;

	ret = imap_add_msgs(folder, dest, &file_list, NULL);
	return ret;
}

static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
		   GRelation *relation)
{
	gchar *destdir;
	IMAPSession *session;
	guint32 last_uid = 0;
	GSList *cur;
	MsgFileInfo *fileinfo;
	gint ok;
	gint curnum = 0, total = 0;


	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(dest != NULL, -1);
	g_return_val_if_fail(file_list != NULL, -1);
	
	session = imap_session_get(folder);
	if (!session) {
		return -1;
	}
	lock_session();
	destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);

	statusbar_print_all(_("Adding messages..."));
	total = g_slist_length(file_list);
	for (cur = file_list; cur != NULL; cur = cur->next) {
		IMAPFlags iflags = 0;
		guint32 new_uid = 0;
		gchar *real_file = NULL;
		gboolean file_is_tmp = FALSE;
		fileinfo = (MsgFileInfo *)cur->data;

		statusbar_progress_all(curnum, total, 1);
		curnum++;

		if (fileinfo->flags) {
			if (MSG_IS_MARKED(*fileinfo->flags))
				iflags |= IMAP_FLAG_FLAGGED;
			if (MSG_IS_REPLIED(*fileinfo->flags))
				iflags |= IMAP_FLAG_ANSWERED;
			if (!MSG_IS_UNREAD(*fileinfo->flags))
				iflags |= IMAP_FLAG_SEEN;
		}
		
		if (fileinfo->flags) {
			if ((MSG_IS_QUEUED(*fileinfo->flags) 
			     || MSG_IS_DRAFT(*fileinfo->flags))
			&& !folder_has_parent_of_type(dest, F_QUEUE)
			&& !folder_has_parent_of_type(dest, F_DRAFT)) {
				real_file = get_tmp_file();
				file_is_tmp = TRUE;
				if (procmsg_remove_special_headers(
						fileinfo->file, 
						real_file) !=0) {
					g_free(real_file);
					g_free(destdir);
					unlock_session();
					return -1;
				}
			}  else if (!(MSG_IS_QUEUED(*fileinfo->flags) 
				      || MSG_IS_DRAFT(*fileinfo->flags))
				    && (folder_has_parent_of_type(dest, F_QUEUE)
				    || folder_has_parent_of_type(dest, F_DRAFT))) {
				return -1;
			} 
		}
		if (real_file == NULL)
			real_file = g_strdup(fileinfo->file);
		
		if (folder_has_parent_of_type(dest, F_QUEUE) ||
		    folder_has_parent_of_type(dest, F_OUTBOX) ||
		    folder_has_parent_of_type(dest, F_DRAFT) ||
		    folder_has_parent_of_type(dest, F_TRASH))
			iflags |= IMAP_FLAG_SEEN;

		ok = imap_cmd_append(session, destdir, real_file, iflags, 
				     &new_uid);

		if (ok != IMAP_SUCCESS) {
			g_warning("can't append message %s\n", real_file);
			if (file_is_tmp)
				g_unlink(real_file);
			g_free(real_file);
			g_free(destdir);
			unlock_session();
			statusbar_progress_all(0,0,0);
			statusbar_pop_all();
			return -1;
		} else {
			debug_print("appended new message as %d\n", new_uid);
			/* put the local file in the imapcache, so that we don't
			 * have to fetch it back later. */
			if (new_uid > 0) {
				gchar *cache_path = folder_item_get_path(dest);
				if (!is_dir_exist(cache_path))
					make_dir_hier(cache_path);
				if (is_dir_exist(cache_path)) {
					gchar *cache_file = g_strconcat(
						cache_path, G_DIR_SEPARATOR_S, 
						itos(new_uid), NULL);
					copy_file(real_file, cache_file, TRUE);
					debug_print("copied to cache: %s\n", cache_file);
					g_free(cache_file);
				}
				g_free(cache_path);
			}
		}

		if (relation != NULL)
			g_relation_insert(relation, fileinfo->msginfo != NULL ? 
					  (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
					  GINT_TO_POINTER(dest->last_num + 1));
		if (new_uid == 0) {
			new_uid = dest->last_num+1;
		}
		if (last_uid < new_uid)
			last_uid = new_uid;
		if (file_is_tmp)
			g_unlink(real_file);

		g_free(real_file);
	}
	statusbar_progress_all(0,0,0);
	statusbar_pop_all();
	
	imap_cmd_expunge(session);
	unlock_session();
	
	g_free(destdir);

	return last_uid;
}

static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, 
			      MsgInfoList *msglist, GRelation *relation)
{
	FolderItem *src;
	gchar *destdir;
	GSList *seq_list, *cur;
	MsgInfo *msginfo;
	IMAPSession *session;
	gint ok = IMAP_SUCCESS;
	GRelation *uid_mapping;
	gint last_num = 0;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(dest != NULL, -1);
	g_return_val_if_fail(msglist != NULL, -1);
	
	session = imap_session_get(folder);
	
	if (!session) {
		return -1;
	}
	lock_session();
	msginfo = (MsgInfo *)msglist->data;

	src = msginfo->folder;
	if (src == dest) {
		g_warning("the src folder is identical to the dest.\n");
		unlock_session();
		return -1;
	}

	ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
			 NULL, NULL, NULL, NULL, FALSE);
	if (ok != IMAP_SUCCESS) {
		unlock_session();
		return ok;
	}

	destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
	seq_list = imap_get_lep_set_from_msglist(msglist);
	uid_mapping = g_relation_new(2);
	g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
	
	statusbar_print_all(_("Copying messages..."));
	for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
		struct mailimap_set * seq_set;
		seq_set = cur->data;

		debug_print("Copying messages from %s to %s ...\n",
			    src->path, destdir);

		ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
		if (ok != IMAP_SUCCESS) {
			g_relation_destroy(uid_mapping);
			imap_lep_set_free(seq_list);
			unlock_session();
			return -1;
		}
	}

	for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
		MsgInfo *msginfo = (MsgInfo *)cur->data;
		GTuples *tuples;

		tuples = g_relation_select(uid_mapping, 
					   GINT_TO_POINTER(msginfo->msgnum),
					   0);
		if (tuples->len > 0) {
			gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
			g_relation_insert(relation, msginfo,
					  GPOINTER_TO_INT(num));
			if (num > last_num)
				last_num = num;
		} else
			g_relation_insert(relation, msginfo,
					  GPOINTER_TO_INT(0));
		g_tuples_destroy(tuples);
	}
	statusbar_pop_all();

	g_relation_destroy(uid_mapping);
	imap_lep_set_free(seq_list);

	g_free(destdir);
	
	IMAP_FOLDER_ITEM(dest)->lastuid = 0;
	IMAP_FOLDER_ITEM(dest)->uid_next = 0;
	g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
	IMAP_FOLDER_ITEM(dest)->uid_list = NULL;

	unlock_session();
	if (ok == IMAP_SUCCESS)
		return last_num;
	else
		return -1;
}

static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
{
	GSList msglist;

	g_return_val_if_fail(msginfo != NULL, -1);

	msglist.data = msginfo;
	msglist.next = NULL;

	return imap_copy_msgs(folder, dest, &msglist, NULL);
}

static gint imap_copy_msgs(Folder *folder, FolderItem *dest, 
		    MsgInfoList *msglist, GRelation *relation)
{
	MsgInfo *msginfo;
	GSList *file_list;
	gint ret;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(dest != NULL, -1);
	g_return_val_if_fail(msglist != NULL, -1);

	msginfo = (MsgInfo *)msglist->data;
	g_return_val_if_fail(msginfo->folder != NULL, -1);

	/* if from/to are the same "type" (with or without extra headers),
	 * copy them via imap */
	if (folder == msginfo->folder->folder &&
	    !folder_has_parent_of_type(msginfo->folder, F_DRAFT) &&
	    !folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
	    !folder_has_parent_of_type(dest, F_DRAFT) &&
	    !folder_has_parent_of_type(dest, F_QUEUE)) {
		ret = imap_do_copy_msgs(folder, dest, msglist, relation);
		return ret;
	} else if (folder == msginfo->folder->folder &&
	    (folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
	     folder_has_parent_of_type(msginfo->folder, F_QUEUE)) && 
	    (folder_has_parent_of_type(dest, F_DRAFT) ||
	     folder_has_parent_of_type(dest, F_QUEUE))) {
		ret = imap_do_copy_msgs(folder, dest, msglist, relation);
		return ret;
	}
	/* else reupload them */
	file_list = procmsg_get_message_file_list(msglist);
	g_return_val_if_fail(file_list != NULL, -1);

	ret = imap_add_msgs(folder, dest, file_list, relation);
	procmsg_message_file_list_free(file_list);

	return ret;
}


static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest, 
			        MsgInfoList *msglist, GRelation *relation)
{
	gchar *destdir, *dir;
	GSList *numlist = NULL, *cur;
	MsgInfo *msginfo;
	IMAPSession *session;
	gint ok = IMAP_SUCCESS;
	GRelation *uid_mapping;
	
	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(dest != NULL, -1);
	g_return_val_if_fail(msglist != NULL, -1);

	session = imap_session_get(folder);
	if (!session) {
		return -1;
	}
	lock_session();
	msginfo = (MsgInfo *)msglist->data;

	ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
			 NULL, NULL, NULL, NULL, FALSE);
	if (ok != IMAP_SUCCESS) {
		unlock_session();
		return ok;
	}

	destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
	for (cur = msglist; cur; cur = cur->next) {
		msginfo = (MsgInfo *)cur->data;
		if (!MSG_IS_DELETED(msginfo->flags))
			numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
	}
	numlist = g_slist_reverse(numlist);

	uid_mapping = g_relation_new(2);
	g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);

	ok = imap_set_message_flags
		(IMAP_SESSION(REMOTE_FOLDER(folder)->session),
		numlist, IMAP_FLAG_DELETED, TRUE);
	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't set deleted flags\n"));
		unlock_session();
		return ok;
	}
	ok = imap_cmd_expunge(session);
	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't expunge\n"));
		unlock_session();
		return ok;
	}
	
	dir = folder_item_get_path(msginfo->folder);
	if (is_dir_exist(dir)) {
		for (cur = msglist; cur; cur = cur->next) {
			msginfo = (MsgInfo *)cur->data;
			remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
		}
	}
	g_free(dir);

	g_relation_destroy(uid_mapping);
	g_slist_free(numlist);

	g_free(destdir);
	unlock_session();
	if (ok == IMAP_SUCCESS)
		return 0;
	else
		return -1;
}

static gint imap_remove_msgs(Folder *folder, FolderItem *dest, 
		    MsgInfoList *msglist, GRelation *relation)
{
	MsgInfo *msginfo;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(dest != NULL, -1);
	if (msglist == NULL)
		return 0;

	msginfo = (MsgInfo *)msglist->data;
	g_return_val_if_fail(msginfo->folder != NULL, -1);

	return imap_do_remove_msgs(folder, dest, msglist, relation);
}

static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
{
	GSList *list = folder_item_get_msg_list(item);
	gint res = imap_remove_msgs(folder, item, list, NULL);
	procmsg_msg_list_free(list);
	return res;
}

static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
				    MsgInfo *msginfo)
{
	/* TODO: properly implement this method */
	return FALSE;
}

static gint imap_close(Folder *folder, FolderItem *item)
{
	return 0;
}

static gint imap_scan_tree(Folder *folder)
{
	FolderItem *item = NULL;
	IMAPSession *session;
	gchar *root_folder = NULL;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(folder->account != NULL, -1);

	session = imap_session_get(folder);
	if (!session) {
		if (!folder->node) {
			folder_tree_destroy(folder);
			item = folder_item_new(folder, folder->name, NULL);
			item->folder = folder;
			folder->node = item->node = g_node_new(item);
		}
		return -1;
	}

	lock_session();
	if (folder->account->imap_dir && *folder->account->imap_dir) {
		gchar *real_path;
		int r;
		clist * lep_list;

		Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
		extract_quote(root_folder, '"');
		subst_char(root_folder,
			   imap_get_path_separator(IMAP_FOLDER(folder),
						   root_folder),
			   '/');
		strtailchomp(root_folder, '/');
		real_path = imap_get_real_path
			(IMAP_FOLDER(folder), root_folder);
		debug_print("IMAP root directory: %s\n", real_path);

		/* check if root directory exist */

		r = imap_threaded_list(session->folder, "", real_path,
				       &lep_list);
		if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
			if (!folder->node) {
				item = folder_item_new(folder, folder->name, NULL);
				item->folder = folder;
				folder->node = item->node = g_node_new(item);
			}
			unlock_session();
			return -1;
		}
		mailimap_list_result_free(lep_list);
		
		g_free(real_path);
	}

	if (folder->node)
		item = FOLDER_ITEM(folder->node->data);
	if (!item || ((item->path || root_folder) &&
		      strcmp2(item->path, root_folder) != 0)) {
		folder_tree_destroy(folder);
		item = folder_item_new(folder, folder->name, root_folder);
		item->folder = folder;
		folder->node = item->node = g_node_new(item);
	}

	imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
	imap_create_missing_folders(folder);
	unlock_session();

	return 0;
}

static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
{
	Folder *folder;
	IMAPFolder *imapfolder;
	FolderItem *new_item;
	GSList *item_list, *cur;
	GNode *node;
	gchar *real_path;
	gchar *wildcard_path;
	gchar separator;
	gchar wildcard[3];
	clist * lep_list;
	int r;
	
	g_return_val_if_fail(item != NULL, -1);
	g_return_val_if_fail(item->folder != NULL, -1);
	g_return_val_if_fail(item->no_sub == FALSE, -1);

	folder = item->folder;
	imapfolder = IMAP_FOLDER(folder);

	separator = imap_get_path_separator(imapfolder, item->path);

	if (folder->ui_func)
		folder->ui_func(folder, item, folder->ui_func_data);

	if (item->path) {
		wildcard[0] = separator;
		wildcard[1] = '%';
		wildcard[2] = '\0';
		real_path = imap_get_real_path(imapfolder, item->path);
	} else {
		wildcard[0] = '%';
		wildcard[1] = '\0';
		real_path = g_strdup("");
	}

	Xstrcat_a(wildcard_path, real_path, wildcard,
		  {g_free(real_path); return IMAP_ERROR;});
	lep_list = NULL;
	r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
	if (r != MAILIMAP_NO_ERROR) {
		item_list = NULL;
	}
	else {
		item_list = imap_list_from_lep(imapfolder,
					       lep_list, real_path, FALSE);
		mailimap_list_result_free(lep_list);
	}
	
	g_free(real_path);

	node = item->node->children;
	while (node != NULL) {
		FolderItem *old_item = FOLDER_ITEM(node->data);
		GNode *next = node->next;

		new_item = NULL;
		for (cur = item_list; cur != NULL; cur = cur->next) {
			FolderItem *cur_item = FOLDER_ITEM(cur->data);
			if (!strcmp2(old_item->path, cur_item->path)) {
				new_item = cur_item;
				break;
			}
		}
		if (!new_item) {
			debug_print("folder '%s' not found. removing...\n",
				    old_item->path);
			folder_item_remove(old_item);
		} else {
			old_item->no_sub = new_item->no_sub;
			old_item->no_select = new_item->no_select;
			if (old_item->no_sub == TRUE && node->children) {
				debug_print("folder '%s' doesn't have "
					    "subfolders. removing...\n",
					    old_item->path);
				folder_item_remove_children(old_item);
			}
		}

		node = next;
	}

	for (cur = item_list; cur != NULL; cur = cur->next) {
		FolderItem *cur_item = FOLDER_ITEM(cur->data);
		new_item = NULL;

		for (node = item->node->children; node != NULL;
		     node = node->next) {
			if (!strcmp2(FOLDER_ITEM(node->data)->path,
				     cur_item->path)) {
				new_item = FOLDER_ITEM(node->data);
				folder_item_destroy(cur_item);
				cur_item = NULL;
				break;
			}
		}
		if (!new_item) {
			new_item = cur_item;
			debug_print("new folder '%s' found.\n", new_item->path);
			folder_item_append(item, new_item);
		}

		if (!strcmp(new_item->path, "INBOX")) {
			new_item->stype = F_INBOX;
			folder->inbox = new_item;
		} else if (!folder_item_parent(item) || item->stype == F_INBOX) {
			gchar *base;

			base = g_path_get_basename(new_item->path);

			if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
				new_item->stype = F_OUTBOX;
				folder->outbox = new_item;
			} else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
				new_item->stype = F_DRAFT;
				folder->draft = new_item;
			} else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
				new_item->stype = F_QUEUE;
				folder->queue = new_item;
			} else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
				new_item->stype = F_TRASH;
				folder->trash = new_item;
			}
			g_free(base);
		}

		if (new_item->no_sub == FALSE)
			imap_scan_tree_recursive(session, new_item);
	}

	g_slist_free(item_list);

	return IMAP_SUCCESS;
}

static gint imap_create_tree(Folder *folder)
{
	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(folder->node != NULL, -1);
	g_return_val_if_fail(folder->node->data != NULL, -1);
	g_return_val_if_fail(folder->account != NULL, -1);

	imap_scan_tree(folder);
	imap_create_missing_folders(folder);

	return 0;
}

static void imap_create_missing_folders(Folder *folder)
{
	g_return_if_fail(folder != NULL);

	if (!folder->inbox)
		folder->inbox = imap_create_special_folder
			(folder, F_INBOX, "INBOX");
	if (!folder->trash)
		folder->trash = imap_create_special_folder
			(folder, F_TRASH, "Trash");
	if (!folder->queue)
		folder->queue = imap_create_special_folder
			(folder, F_QUEUE, "Queue");
	if (!folder->outbox)
		folder->outbox = imap_create_special_folder
			(folder, F_OUTBOX, "Sent");
	if (!folder->draft)
		folder->draft = imap_create_special_folder
			(folder, F_DRAFT, "Drafts");
}

static FolderItem *imap_create_special_folder(Folder *folder,
					      SpecialFolderItemType stype,
					      const gchar *name)
{
	FolderItem *item;
	FolderItem *new_item;

	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(folder->node != NULL, NULL);
	g_return_val_if_fail(folder->node->data != NULL, NULL);
	g_return_val_if_fail(folder->account != NULL, NULL);
	g_return_val_if_fail(name != NULL, NULL);

	item = FOLDER_ITEM(folder->node->data);
	new_item = imap_create_folder(folder, item, name);

	if (!new_item) {
		g_warning("Can't create '%s'\n", name);
		if (!folder->inbox) return NULL;

		new_item = imap_create_folder(folder, folder->inbox, name);
		if (!new_item)
			g_warning("Can't create '%s' under INBOX\n", name);
		else
			new_item->stype = stype;
	} else
		new_item->stype = stype;

	return new_item;
}

static gchar *imap_folder_get_path(Folder *folder)
{
	gchar *folder_path;

	g_return_val_if_fail(folder != NULL, NULL);
        g_return_val_if_fail(folder->account != NULL, NULL);

        folder_path = g_strconcat(get_imap_cache_dir(),
                                  G_DIR_SEPARATOR_S,
                                  folder->account->recv_server,
                                  G_DIR_SEPARATOR_S,
                                  folder->account->userid,
                                  NULL);

	return folder_path;
}

static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
{
	gchar *folder_path, *path;

	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(item != NULL, NULL);
	folder_path = imap_folder_get_path(folder);

	g_return_val_if_fail(folder_path != NULL, NULL);
        if (folder_path[0] == G_DIR_SEPARATOR) {
                if (item->path)
                        path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
                                           item->path, NULL);
                else
                        path = g_strdup(folder_path);
        } else {
                if (item->path)
                        path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
                                           folder_path, G_DIR_SEPARATOR_S,
                                           item->path, NULL);
                else
                        path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
                                           folder_path, NULL);
        }
        g_free(folder_path);

	return path;
}

static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
			       const gchar *name)
{
	gchar *dirpath, *imap_path;
	IMAPSession *session;
	FolderItem *new_item;
	gchar separator;
	gchar *new_name;
	const gchar *p;
	gint ok;
	gboolean no_select = FALSE, no_sub = FALSE;
	
	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(folder->account != NULL, NULL);
	g_return_val_if_fail(parent != NULL, NULL);
	g_return_val_if_fail(name != NULL, NULL);

	session = imap_session_get(folder);
	if (!session) {
		return NULL;
	}

	lock_session();
	if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
		dirpath = g_strdup(name);
	}else if (parent->path)
		dirpath = g_strconcat(parent->path, "/", name, NULL);
	else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
		dirpath = g_strdup(name);
	else if (folder->account->imap_dir && *folder->account->imap_dir) {
		gchar *imap_dir;

		Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
		strtailchomp(imap_dir, '/');
		dirpath = g_strconcat(imap_dir, "/", name, NULL);
	} else
		dirpath = g_strdup(name);
		
	

	/* keep trailing directory separator to create a folder that contains
	   sub folder */
	imap_path = imap_utf8_to_modified_utf7(dirpath);

	strtailchomp(dirpath, '/');
	Xstrdup_a(new_name, name, {
		g_free(dirpath); 
		unlock_session();		
		return NULL;});

	separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
	imap_path_separator_subst(imap_path, separator);
	/* remove trailing / for display */
	strtailchomp(new_name, '/');

	if (strcmp(dirpath, "INBOX") != 0) {
		GPtrArray *argbuf;
		gboolean exist = FALSE;
		int r;
		clist * lep_list;
		
		argbuf = g_ptr_array_new();
		r = imap_threaded_list(folder, "", imap_path, &lep_list);
		if (r != MAILIMAP_NO_ERROR) {
			log_warning(_("can't create mailbox: LIST failed\n"));
			g_free(imap_path);
			g_free(dirpath);
			ptr_array_free_strings(argbuf);
			g_ptr_array_free(argbuf, TRUE);
			unlock_session();
			return NULL;
		}
		
		if (clist_count(lep_list) > 0)
			exist = TRUE;
		mailimap_list_result_free(lep_list);
		lep_list = NULL;
		if (!exist) {
			ok = imap_cmd_create(session, imap_path);
			if (ok != IMAP_SUCCESS) {
				log_warning(_("can't create mailbox\n"));
				g_free(imap_path);
				g_free(dirpath);
				unlock_session();
				return NULL;
			}
			r = imap_threaded_list(folder, "", imap_path, &lep_list);
			if (r == MAILIMAP_NO_ERROR) {
				GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
					       lep_list, dirpath, TRUE);
				if (item_list) {
					FolderItem *cur_item = FOLDER_ITEM(item_list->data);
					no_select = cur_item->no_select;
					no_sub = cur_item->no_sub;
					g_slist_free(item_list);
				} 
				mailimap_list_result_free(lep_list);
			}

		}
	} else {
		clist *lep_list;
		int r;
		/* just get flags */
		r = imap_threaded_list(folder, "", "INBOX", &lep_list);
		if (r == MAILIMAP_NO_ERROR) {
			GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
				       lep_list, dirpath, TRUE);
			if (item_list) {
				FolderItem *cur_item = FOLDER_ITEM(item_list->data);
				no_select = cur_item->no_select;
				no_sub = cur_item->no_sub;
				g_slist_free(item_list);
			} 
			mailimap_list_result_free(lep_list);
		}
	}

	new_item = folder_item_new(folder, new_name, dirpath);
	new_item->no_select = no_select;
	new_item->no_sub = no_sub;
	folder_item_append(parent, new_item);
	g_free(imap_path);
	g_free(dirpath);

	dirpath = folder_item_get_path(new_item);
	if (!is_dir_exist(dirpath))
		make_dir_hier(dirpath);
	g_free(dirpath);
	unlock_session();
	return new_item;
}

static gint imap_rename_folder(Folder *folder, FolderItem *item,
			       const gchar *name)
{
	gchar *dirpath;
	gchar *newpath;
	gchar *real_oldpath;
	gchar *real_newpath;
	gchar *paths[2];
	gchar *old_cache_dir;
	gchar *new_cache_dir;
	IMAPSession *session;
	gchar separator;
	gint ok;
	gint exists, recent, unseen;
	guint32 uid_validity;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(item != NULL, -1);
	g_return_val_if_fail(item->path != NULL, -1);
	g_return_val_if_fail(name != NULL, -1);

	session = imap_session_get(folder);
	if (!session) {
		return -1;
	}
	lock_session();

	if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
		g_warning(_("New folder name must not contain the namespace "
			    "path separator"));
		unlock_session();
		return -1;
	}

	real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);

	g_free(session->mbox);
	session->mbox = NULL;
	ok = imap_cmd_examine(session, "INBOX",
			      &exists, &recent, &unseen, &uid_validity, FALSE);
	if (ok != IMAP_SUCCESS) {
		g_free(real_oldpath);
		unlock_session();
		return -1;
	}

	separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
	if (strchr(item->path, G_DIR_SEPARATOR)) {
		dirpath = g_path_get_dirname(item->path);
		newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
		g_free(dirpath);
	} else
		newpath = g_strdup(name);

	real_newpath = imap_utf8_to_modified_utf7(newpath);
	imap_path_separator_subst(real_newpath, separator);

	ok = imap_cmd_rename(session, real_oldpath, real_newpath);
	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't rename mailbox: %s to %s\n"),
			    real_oldpath, real_newpath);
		g_free(real_oldpath);
		g_free(newpath);
		g_free(real_newpath);
		unlock_session();
		return -1;
	}

	g_free(item->name);
	item->name = g_strdup(name);

	old_cache_dir = folder_item_get_path(item);

	paths[0] = g_strdup(item->path);
	paths[1] = newpath;
	g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
			imap_rename_folder_func, paths);

	if (is_dir_exist(old_cache_dir)) {
		new_cache_dir = folder_item_get_path(item);
		if (rename(old_cache_dir, new_cache_dir) < 0) {
			FILE_OP_ERROR(old_cache_dir, "rename");
		}
		g_free(new_cache_dir);
	}

	g_free(old_cache_dir);
	g_free(paths[0]);
	g_free(newpath);
	g_free(real_oldpath);
	g_free(real_newpath);
	unlock_session();
	return 0;
}

static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
{
	gint ok;
	IMAPSession *session;
	gchar *path;
	gchar *cache_dir;

	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(item != NULL, -1);
	g_return_val_if_fail(item->path != NULL, -1);

	session = imap_session_get(folder);
	if (!session) {
		return -1;
	}
	lock_session();
	path = imap_get_real_path(IMAP_FOLDER(folder), item->path);

	ok = imap_cmd_delete(session, path);
	if (ok != IMAP_SUCCESS) {
		gchar *tmp = g_strdup_printf("%s%c", path, 
				imap_get_path_separator(IMAP_FOLDER(folder), path));
		g_free(path);
		path = tmp;
		ok = imap_cmd_delete(session, path);
	}

	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't delete mailbox\n"));
		g_free(path);
		unlock_session();
		return -1;
	}

	g_free(path);
	cache_dir = folder_item_get_path(item);
	if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
		g_warning("can't remove directory '%s'\n", cache_dir);
	g_free(cache_dir);
	folder_item_remove(item);
	unlock_session();
	return 0;
}

static gint imap_remove_folder(Folder *folder, FolderItem *item)
{
	GNode *node, *next;

	g_return_val_if_fail(item != NULL, -1);
	g_return_val_if_fail(item->folder != NULL, -1);
	g_return_val_if_fail(item->node != NULL, -1);

	node = item->node->children;
	while (node != NULL) {
		next = node->next;
		if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
			return -1;
		node = next;
	}
	debug_print("IMAP removing %s\n", item->path);

	if (imap_remove_all_msg(folder, item) < 0)
		return -1;
	return imap_remove_folder_real(folder, item);
}

typedef struct _uncached_data {
	IMAPSession *session;
	FolderItem *item;
	MsgNumberList *numlist;
	guint cur;
	guint total;
	gboolean done;
} uncached_data;

static void *imap_get_uncached_messages_thread(void *data)
{
	uncached_data *stuff = (uncached_data *)data;
	IMAPSession *session = stuff->session;
	FolderItem *item = stuff->item;
	MsgNumberList *numlist = stuff->numlist;
	
	GSList *newlist = NULL;
	GSList *llast = NULL;
	GSList *seq_list, *cur;

	debug_print("uncached_messages\n");
	
	if (session == NULL || item == NULL || item->folder == NULL
	    || FOLDER_CLASS(item->folder) != &imap_class) {
		stuff->done = TRUE;
		return NULL;
	}
	
	seq_list = imap_get_lep_set_from_numlist(numlist);
	debug_print("get msgs info\n");
	for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
		struct mailimap_set * imapset;
		unsigned int i;
		int r;
		carray * env_list;
		int count;
		
		imapset = cur->data;
		
		r = imap_threaded_fetch_env(session->folder,
					    imapset, &env_list);
		if (r != MAILIMAP_NO_ERROR)
			continue;
		
		session_set_access_time(SESSION(session));

		count = 0;
		for(i = 0 ; i < carray_count(env_list) ; i ++) {
			struct imap_fetch_env_info * info;
			MsgInfo * msginfo;
			
			info = carray_get(env_list, i);
			msginfo = imap_envelope_from_lep(info, item);
			if (msginfo == NULL)
				continue;
			msginfo->folder = item;
			if (!newlist)
				llast = newlist = g_slist_append(newlist, msginfo);
			else {
				llast = g_slist_append(llast, msginfo);
				llast = llast->next;
			}
			count ++;
		}
		
		imap_fetch_env_free(env_list);
	}
	
	for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
		struct mailimap_set * imapset;
		
		imapset = cur->data;
		mailimap_set_free(imapset);
	}
	
	session_set_access_time(SESSION(session));
	stuff->done = TRUE;
	return newlist;
}

#define MAX_MSG_NUM 50

static GSList *imap_get_uncached_messages(IMAPSession *session,
					FolderItem *item,
					MsgNumberList *numlist)
{
	GSList *result = NULL;
	GSList * cur;
	uncached_data *data = g_new0(uncached_data, 1);
	int finished;
	
	finished = 0;
	cur = numlist;
	data->total = g_slist_length(numlist);
	debug_print("messages list : %i\n", data->total);

	while (cur != NULL) {
		GSList * partial_result;
		int count;
		GSList * newlist;
		GSList * llast;
		
		llast = NULL;
		count = 0;
		newlist = NULL;
		while (count < MAX_MSG_NUM) {
			void * p;
			
			p = cur->data;
			
			if (newlist == NULL)
				llast = newlist = g_slist_append(newlist, p);
			else {
				llast = g_slist_append(llast, p);
				llast = llast->next;
			}
			count ++;
			
			cur = cur->next;
			if (cur == NULL)
				break;
		}
		
		data->done = FALSE;
		data->session = session;
		data->item = item;
		data->numlist = newlist;
		data->cur += count;
		
		if (prefs_common.work_offline && 
		    !inc_offline_should_override(
			_("Sylpheed-Claws needs network access in order "
			  "to access the IMAP server."))) {
			g_free(data);
			return NULL;
		}
		
		partial_result =
			(GSList *)imap_get_uncached_messages_thread(data);
		
		statusbar_progress_all(data->cur,data->total, 1);
		
		g_slist_free(newlist);
		
		result = g_slist_concat(result, partial_result);
	}
	g_free(data);
	
	statusbar_progress_all(0,0,0);
	statusbar_pop_all();
	
	return result;
}

static void imap_delete_all_cached_messages(FolderItem *item)
{
	gchar *dir;

	g_return_if_fail(item != NULL);
	g_return_if_fail(item->folder != NULL);
	g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);

	debug_print("Deleting all cached messages...\n");

	dir = folder_item_get_path(item);
	if (is_dir_exist(dir))
		remove_all_numbered_files(dir);
	g_free(dir);

	debug_print("done.\n");
}

static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
						    const gchar *path)
{
	IMAPNameSpace *namespace = NULL;
	gchar *tmp_path, *name;

	if (!path) path = "";

	for (; ns_list != NULL; ns_list = ns_list->next) {
		IMAPNameSpace *tmp_ns = ns_list->data;

		Xstrcat_a(tmp_path, path, "/", return namespace);
		Xstrdup_a(name, tmp_ns->name, return namespace);
		if (tmp_ns->separator && tmp_ns->separator != '/') {
			subst_char(tmp_path, tmp_ns->separator, '/');
			subst_char(name, tmp_ns->separator, '/');
		}
		if (strncmp(tmp_path, name, strlen(name)) == 0)
			namespace = tmp_ns;
	}

	return namespace;
}

static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
					  const gchar *path)
{
	IMAPNameSpace *namespace;

	g_return_val_if_fail(folder != NULL, NULL);

	namespace = imap_find_namespace_from_list(folder->ns_personal, path);
	if (namespace) return namespace;
	namespace = imap_find_namespace_from_list(folder->ns_others, path);
	if (namespace) return namespace;
	namespace = imap_find_namespace_from_list(folder->ns_shared, path);
	if (namespace) return namespace;

	return NULL;
}

gchar imap_get_path_separator_for_item(FolderItem *item)
{
	Folder *folder = NULL;
	IMAPFolder *imap_folder = NULL;
	if (!item)
		return '/';
	folder = item->folder;
	
	if (!folder)
		return '/';
	
	imap_folder = IMAP_FOLDER(folder);
	
	if (!imap_folder)
		return '/';
	
	return imap_get_path_separator(imap_folder, item->path);
}

static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
{
	IMAPNameSpace *namespace;
	gchar separator = '/';
	IMAPSession *session = imap_session_get(FOLDER(folder));
	g_return_val_if_fail(session != NULL, '/');

	if (folder->last_seen_separator == 0) {
		clist * lep_list;
		int r = imap_threaded_list((Folder *)folder, "", "", &lep_list);
		if (r != MAILIMAP_NO_ERROR) {
			log_warning(_("LIST failed\n"));
			return '/';
		}
		
		if (clist_count(lep_list) > 0) {
			clistiter * iter = clist_begin(lep_list); 
			struct mailimap_mailbox_list * mb;
			mb = clist_content(iter);
		
			folder->last_seen_separator = mb->mb_delimiter;
			debug_print("got separator: %c\n", folder->last_seen_separator);
		}
		mailimap_list_result_free(lep_list);
	}

	if (folder->last_seen_separator != 0) {
		debug_print("using separator: %c\n", folder->last_seen_separator);
		return folder->last_seen_separator;
	}

	namespace = imap_find_namespace(folder, path);
	if (namespace && namespace->separator)
		separator = namespace->separator;

	return separator;
}

static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
{
	gchar *real_path;
	gchar separator;

	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(path != NULL, NULL);

	real_path = imap_utf8_to_modified_utf7(path);
	separator = imap_get_path_separator(folder, path);
	imap_path_separator_subst(real_path, separator);

	return real_path;
}

static gint imap_set_message_flags(IMAPSession *session,
				   MsgNumberList *numlist,
				   IMAPFlags flags,
				   gboolean is_set)
{
	gint ok = 0;
	GSList *seq_list;
	GSList * cur;

	seq_list = imap_get_lep_set_from_numlist(numlist);
	
	for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
		struct mailimap_set * imapset;
		
		imapset = cur->data;
		
		ok = imap_cmd_store(session, imapset,
				    flags, is_set);
	}
	
	imap_lep_set_free(seq_list);
	
	return IMAP_SUCCESS;
}

typedef struct _select_data {
	IMAPSession *session;
	gchar *real_path;
	gint *exists;
	gint *recent;
	gint *unseen;
	guint32 *uid_validity;
	gboolean done;
} select_data;

static gint imap_select(IMAPSession *session, IMAPFolder *folder,
			const gchar *path,
			gint *exists, gint *recent, gint *unseen,
			guint32 *uid_validity, gboolean block)
{
	gchar *real_path;
	gint ok;
	gint exists_, recent_, unseen_;
	guint32 uid_validity_;
	
	if (!exists && !recent && !unseen && !uid_validity) {
		if (session->mbox && strcmp(session->mbox, path) == 0)
			return IMAP_SUCCESS;
	}
	if (!exists)
		exists = &exists_;
	if (!recent)
		recent = &recent_;
	if (!unseen)
		unseen = &unseen_;
	if (!uid_validity)
		uid_validity = &uid_validity_;

	g_free(session->mbox);
	session->mbox = NULL;

	real_path = imap_get_real_path(folder, path);

	ok = imap_cmd_select(session, real_path,
			     exists, recent, unseen, uid_validity, block);
	if (ok != IMAP_SUCCESS)
		log_warning(_("can't select folder: %s\n"), real_path);
	else {
		session->mbox = g_strdup(path);
		session->folder_content_changed = FALSE;
	}
	g_free(real_path);

	return ok;
}

static gint imap_status(IMAPSession *session, IMAPFolder *folder,
			const gchar *path, IMAPFolderItem *item,
			gint *messages,
			guint32 *uid_next, guint32 *uid_validity,
			gint *unseen, gboolean block)
{
	int r;
	clistiter * iter;
	struct mailimap_mailbox_data_status * data_status;
	int got_values;
	gchar *real_path;
	guint mask = 0;
	
	real_path = imap_get_real_path(folder, path);

	if (messages) {
		mask |= 1 << 0;
	}
	if (uid_next) {
		mask |= 1 << 2;
	}
	if (uid_validity) {
		mask |= 1 << 3;
	}
	if (unseen) {
		mask |= 1 << 4;
	}
	r = imap_threaded_status(FOLDER(folder), real_path, 
		&data_status, mask);

	g_free(real_path);
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("status err %d\n", r);
		return IMAP_ERROR;
	}
	
	if (data_status->st_info_list == NULL) {
		mailimap_mailbox_data_status_free(data_status);
		debug_print("status->st_info_list == NULL\n");
		return IMAP_ERROR;
	}
	
	got_values = 0;
	for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
	    iter = clist_next(iter)) {
		struct mailimap_status_info * info;		
		
		info = clist_content(iter);
		switch (info->st_att) {
		case MAILIMAP_STATUS_ATT_MESSAGES:
			* messages = info->st_value;
			got_values |= 1 << 0;
			break;
			
		case MAILIMAP_STATUS_ATT_UIDNEXT:
			* uid_next = info->st_value;
			got_values |= 1 << 2;
			break;
			
		case MAILIMAP_STATUS_ATT_UIDVALIDITY:
			* uid_validity = info->st_value;
			got_values |= 1 << 3;
			break;
			
		case MAILIMAP_STATUS_ATT_UNSEEN:
			* unseen = info->st_value;
			got_values |= 1 << 4;
			break;
		}
	}
	mailimap_mailbox_data_status_free(data_status);
	
	if (got_values != mask) {
		debug_print("status: incomplete values received (%d)\n", got_values);
		return IMAP_ERROR;
	}
	return IMAP_SUCCESS;
}

static void imap_free_capabilities(IMAPSession *session)
{
	slist_free_strings(session->capability);
	g_slist_free(session->capability);
	session->capability = NULL;
}

/* low-level IMAP4rev1 commands */

static gint imap_cmd_login(IMAPSession *session,
			   const gchar *user, const gchar *pass,
			   const gchar *type)
{
	int r;
	gint ok;

	log_print("IMAP4> Logging %s to %s using %s\n", 
			user,
			SESSION(session)->server,
			type);
	r = imap_threaded_login(session->folder, user, pass, type);
	if (r != MAILIMAP_NO_ERROR) {
		log_print("IMAP4< Error logging in to %s\n",
				SESSION(session)->server);
		ok = IMAP_ERROR;
	} else {
		log_print("IMAP4< Login to %s successful\n",
				SESSION(session)->server);
		ok = IMAP_SUCCESS;
	}
	return ok;
}

static gint imap_cmd_logout(IMAPSession *session)
{
	imap_threaded_disconnect(session->folder);

	return IMAP_SUCCESS;
}

static gint imap_cmd_noop(IMAPSession *session)
{
	int r;
	unsigned int exists;
	
	r = imap_threaded_noop(session->folder, &exists);
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("noop err %d\n", r);
		return IMAP_ERROR;
	}
	session->exists = exists;
	session_set_access_time(SESSION(session));

	return IMAP_SUCCESS;
}

#if USE_OPENSSL
static gint imap_cmd_starttls(IMAPSession *session)
{
	int r;
	
	r = imap_threaded_starttls(session->folder, 
		SESSION(session)->server, SESSION(session)->port);
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("starttls err %d\n", r);
		return IMAP_ERROR;
	}
	return IMAP_SUCCESS;
}
#endif

static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
			    gint *exists, gint *recent, gint *unseen,
			    guint32 *uid_validity, gboolean block)
{
	int r;

	r = imap_threaded_select(session->folder, folder,
				 exists, recent, unseen, uid_validity);
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("select err %d\n", r);
		return IMAP_ERROR;
	}
	return IMAP_SUCCESS;
}

static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
			     gint *exists, gint *recent, gint *unseen,
			     guint32 *uid_validity, gboolean block)
{
	int r;

	r = imap_threaded_examine(session->folder, folder,
				  exists, recent, unseen, uid_validity);
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("examine err %d\n", r);
		
		return IMAP_ERROR;
	}
	return IMAP_SUCCESS;
}

static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
{
	int r;

	r = imap_threaded_create(session->folder, folder);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}

	return IMAP_SUCCESS;
}

static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
			    const gchar *new_folder)
{
	int r;

	r = imap_threaded_rename(session->folder, old_folder,
				 new_folder);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}

	return IMAP_SUCCESS;
}

static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
{
	int r;
	

	r = imap_threaded_delete(session->folder, folder);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}

	return IMAP_SUCCESS;
}

typedef struct _fetch_data {
	IMAPSession *session;
	guint32 uid;
	const gchar *filename;
	gboolean headers;
	gboolean body;
	gboolean done;
} fetch_data;

static void *imap_cmd_fetch_thread(void *data)
{
	fetch_data *stuff = (fetch_data *)data;
	IMAPSession *session = stuff->session;
	guint32 uid = stuff->uid;
	const gchar *filename = stuff->filename;
	int r;
	
	if (stuff->body) {
		r = imap_threaded_fetch_content(session->folder,
					       uid, 1, filename);
	}
	else {
		r = imap_threaded_fetch_content(session->folder,
						uid, 0, filename);
	}
	if (r != MAILIMAP_NO_ERROR) {
		debug_print("fetch err %d\n", r);
		return GINT_TO_POINTER(IMAP_ERROR);
	}
	return GINT_TO_POINTER(IMAP_SUCCESS);
}

static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
				const gchar *filename, gboolean headers,
				gboolean body)
{
	fetch_data *data = g_new0(fetch_data, 1);
	int result = 0;
	data->done = FALSE;
	data->session = session;
	data->uid = uid;
	data->filename = filename;
	data->headers = headers;
	data->body = body;

	if (prefs_common.work_offline && 
	    !inc_offline_should_override(
		_("Sylpheed-Claws needs network access in order "
		  "to access the IMAP server."))) {
		g_free(data);
		return -1;
	}
	statusbar_print_all(_("Fetching message..."));
	result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
	statusbar_pop_all();
	g_free(data);
	return result;
}


static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
			    const gchar *file, IMAPFlags flags, 
			    guint32 *new_uid)
{
	struct mailimap_flag_list * flag_list;
	int r;
	
	g_return_val_if_fail(file != NULL, IMAP_ERROR);

	flag_list = imap_flag_to_lep(flags);
	r = imap_threaded_append(session->folder, destfolder,
			 file, flag_list, new_uid);
	mailimap_flag_list_free(flag_list);

	if (r != MAILIMAP_NO_ERROR) {
		debug_print("append err %d\n", r);
		return IMAP_ERROR;
	}
	return IMAP_SUCCESS;
}

static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
			  const gchar *destfolder, GRelation *uid_mapping)
{
	int r;
	
	g_return_val_if_fail(session != NULL, IMAP_ERROR);
	g_return_val_if_fail(set != NULL, IMAP_ERROR);
	g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);

	r = imap_threaded_copy(session->folder, set, destfolder);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}

	return IMAP_SUCCESS;
}

static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
			   IMAPFlags flags, int do_add)
{
	int r;
	struct mailimap_flag_list * flag_list;
	struct mailimap_store_att_flags * store_att_flags;
	
	flag_list = imap_flag_to_lep(flags);
	
	if (do_add)
		store_att_flags =
			mailimap_store_att_flags_new_add_flags_silent(flag_list);
	else
		store_att_flags =
			mailimap_store_att_flags_new_remove_flags_silent(flag_list);
	
	r = imap_threaded_store(session->folder, set, store_att_flags);
	mailimap_store_att_flags_free(store_att_flags);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}
	
	return IMAP_SUCCESS;
}

static gint imap_cmd_expunge(IMAPSession *session)
{
	int r;
	
	if (prefs_common.work_offline && 
	    !inc_offline_should_override(
		_("Sylpheed-Claws needs network access in order "
		  "to access the IMAP server."))) {
		return -1;
	}

	r = imap_threaded_expunge(session->folder);
	if (r != MAILIMAP_NO_ERROR) {
		
		return IMAP_ERROR;
	}

	return IMAP_SUCCESS;
}

static void imap_path_separator_subst(gchar *str, gchar separator)
{
	gchar *p;
	gboolean in_escape = FALSE;

	if (!separator || separator == '/') return;

	for (p = str; *p != '\0'; p++) {
		if (*p == '/' && !in_escape)
			*p = separator;
		else if (*p == '&' && *(p + 1) != '-' && !in_escape)
			in_escape = TRUE;
		else if (*p == '-' && in_escape)
			in_escape = FALSE;
	}
}

static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
{
	static iconv_t cd = (iconv_t)-1;
	static gboolean iconv_ok = TRUE;
	GString *norm_utf7;
	gchar *norm_utf7_p;
	size_t norm_utf7_len;
	const gchar *p;
	gchar *to_str, *to_p;
	size_t to_len;
	gboolean in_escape = FALSE;

	if (!iconv_ok) return g_strdup(mutf7_str);

	if (cd == (iconv_t)-1) {
		cd = iconv_open(CS_INTERNAL, CS_UTF_7);
		if (cd == (iconv_t)-1) {
			g_warning("iconv cannot convert UTF-7 to %s\n",
				  CS_INTERNAL);
			iconv_ok = FALSE;
			return g_strdup(mutf7_str);
		}
	}

	/* modified UTF-7 to normal UTF-7 conversion */
	norm_utf7 = g_string_new(NULL);

	for (p = mutf7_str; *p != '\0'; p++) {
		/* replace: '&'  -> '+',
			    "&-" -> '&',
			    escaped ','  -> '/' */
		if (!in_escape && *p == '&') {
			if (*(p + 1) != '-') {
				g_string_append_c(norm_utf7, '+');
				in_escape = TRUE;
			} else {
				g_string_append_c(norm_utf7, '&');
				p++;
			}
		} else if (in_escape && *p == ',') {
			g_string_append_c(norm_utf7, '/');
		} else if (in_escape && *p == '-') {
			g_string_append_c(norm_utf7, '-');
			in_escape = FALSE;
		} else {
			g_string_append_c(norm_utf7, *p);
		}
	}

	norm_utf7_p = norm_utf7->str;
	norm_utf7_len = norm_utf7->len;
	to_len = strlen(mutf7_str) * 5;
	to_p = to_str = g_malloc(to_len + 1);

	if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
		  &to_p, &to_len) == -1) {
		g_warning(_("iconv cannot convert UTF-7 to %s\n"),
			  conv_get_locale_charset_str());
		g_string_free(norm_utf7, TRUE);
		g_free(to_str);
		return g_strdup(mutf7_str);
	}

	/* second iconv() call for flushing */
	iconv(cd, NULL, NULL, &to_p, &to_len);
	g_string_free(norm_utf7, TRUE);
	*to_p = '\0';

	return to_str;
}

static gchar *imap_utf8_to_modified_utf7(const gchar *from)
{
	static iconv_t cd = (iconv_t)-1;
	static gboolean iconv_ok = TRUE;
	gchar *norm_utf7, *norm_utf7_p;
	size_t from_len, norm_utf7_len;
	GString *to_str;
	gchar *from_tmp, *to, *p;
	gboolean in_escape = FALSE;

	if (!iconv_ok) return g_strdup(from);

	if (cd == (iconv_t)-1) {
		cd = iconv_open(CS_UTF_7, CS_INTERNAL);
		if (cd == (iconv_t)-1) {
			g_warning(_("iconv cannot convert %s to UTF-7\n"),
				  CS_INTERNAL);
			iconv_ok = FALSE;
			return g_strdup(from);
		}
	}

	/* UTF-8 to normal UTF-7 conversion */
	Xstrdup_a(from_tmp, from, return g_strdup(from));
	from_len = strlen(from);
	norm_utf7_len = from_len * 5;
	Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
	norm_utf7_p = norm_utf7;

#define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))

	while (from_len > 0) {
		if (*from_tmp == '+') {
			*norm_utf7_p++ = '+';
			*norm_utf7_p++ = '-';
			norm_utf7_len -= 2;
			from_tmp++;
			from_len--;
		} else if (IS_PRINT(*(guchar *)from_tmp)) {
			/* printable ascii char */
			*norm_utf7_p = *from_tmp;
			norm_utf7_p++;
			norm_utf7_len--;
			from_tmp++;
			from_len--;
		} else {
			size_t conv_len = 0;

			/* unprintable char: convert to UTF-7 */
			p = from_tmp;
			while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
				conv_len += g_utf8_skip[*(guchar *)p];
				p += g_utf8_skip[*(guchar *)p];
			}

			from_len -= conv_len;
			if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
				  &conv_len,
				  &norm_utf7_p, &norm_utf7_len) == -1) {
				g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
				return g_strdup(from);
			}

			/* second iconv() call for flushing */
			iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
		}
	}

#undef IS_PRINT

	*norm_utf7_p = '\0';
	to_str = g_string_new(NULL);
	for (p = norm_utf7; p < norm_utf7_p; p++) {
		/* replace: '&' -> "&-",
			    '+' -> '&',
			    "+-" -> '+',
			    BASE64 '/' -> ',' */
		if (!in_escape && *p == '&') {
			g_string_append(to_str, "&-");
		} else if (!in_escape && *p == '+') {
			if (*(p + 1) == '-') {
				g_string_append_c(to_str, '+');
				p++;
			} else {
				g_string_append_c(to_str, '&');
				in_escape = TRUE;
			}
		} else if (in_escape && *p == '/') {
			g_string_append_c(to_str, ',');
		} else if (in_escape && *p == '-') {
			g_string_append_c(to_str, '-');
			in_escape = FALSE;
		} else {
			g_string_append_c(to_str, *p);
		}
	}

	if (in_escape) {
		in_escape = FALSE;
		g_string_append_c(to_str, '-');
	}

	to = to_str->str;
	g_string_free(to_str, FALSE);

	return to;
}

static gboolean imap_rename_folder_func(GNode *node, gpointer data)
{
	FolderItem *item = node->data;
	gchar **paths = data;
	const gchar *oldpath = paths[0];
	const gchar *newpath = paths[1];
	gchar *base;
	gchar *new_itempath;
	gint oldpathlen;

	oldpathlen = strlen(oldpath);
	if (strncmp(oldpath, item->path, oldpathlen) != 0) {
		g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
		return TRUE;
	}

	base = item->path + oldpathlen;
	while (*base == G_DIR_SEPARATOR) base++;
	if (*base == '\0')
		new_itempath = g_strdup(newpath);
	else
		new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
					   NULL);
	g_free(item->path);
	item->path = new_itempath;

	return FALSE;
}

typedef struct _get_list_uid_data {
	Folder *folder;
	IMAPSession *session;
	IMAPFolderItem *item;
	GSList **msgnum_list;
	gboolean done;
} get_list_uid_data;

static void *get_list_of_uids_thread(void *data)
{
	get_list_uid_data *stuff = (get_list_uid_data *)data;
	Folder *folder = stuff->folder;
	IMAPFolderItem *item = stuff->item;
	GSList **msgnum_list = stuff->msgnum_list;
	gint ok, nummsgs = 0, lastuid_old;
	IMAPSession *session;
	GSList *uidlist, *elem;
	struct mailimap_set * set;
	clist * lep_uidlist;
	int r;

	session = stuff->session;
	if (session == NULL) {
		stuff->done = TRUE;
		return GINT_TO_POINTER(-1);
	}
	/* no session locking here, it's already locked by caller */
	ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
			 NULL, NULL, NULL, NULL, TRUE);
	if (ok != IMAP_SUCCESS) {
		stuff->done = TRUE;
		return GINT_TO_POINTER(-1);
	}

	uidlist = NULL;
	
	set = mailimap_set_new_interval(item->lastuid + 1, 0);

	r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, set,
				 &lep_uidlist);
	mailimap_set_free(set);
	
	if (r == MAILIMAP_NO_ERROR) {
		GSList * fetchuid_list;
		
		fetchuid_list =
			imap_uid_list_from_lep(lep_uidlist);
		mailimap_search_result_free(lep_uidlist);
		
		uidlist = g_slist_concat(fetchuid_list, uidlist);
	}
	else {
		GSList * fetchuid_list;
		carray * lep_uidtab;
		
		r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
					    &lep_uidtab);
		if (r == MAILIMAP_NO_ERROR) {
			fetchuid_list =
				imap_uid_list_from_lep_tab(lep_uidtab);
			imap_fetch_uid_list_free(lep_uidtab);
			uidlist = g_slist_concat(fetchuid_list, uidlist);
		}
	}
	
	lastuid_old = item->lastuid;
	*msgnum_list = g_slist_copy(item->uid_list);
	nummsgs = g_slist_length(*msgnum_list);
	debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));

	for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
		guint msgnum;

		msgnum = GPOINTER_TO_INT(elem->data);
		if (msgnum > lastuid_old) {
			*msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
			item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
			nummsgs++;

			if(msgnum > item->lastuid)
				item->lastuid = msgnum;
		}
	}
	g_slist_free(uidlist);
	stuff->done = TRUE;
	return GINT_TO_POINTER(nummsgs);
}

static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
{
	gint result;
	get_list_uid_data *data = g_new0(get_list_uid_data, 1);
	data->done = FALSE;
	data->folder = folder;
	data->item = item;
	data->msgnum_list = msgnum_list;
	data->session = session;
	if (prefs_common.work_offline && 
	    !inc_offline_should_override(
		_("Sylpheed-Claws needs network access in order "
		  "to access the IMAP server."))) {
		g_free(data);
		return -1;
	}

	result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
	g_free(data);
	return result;

}

gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
{
	IMAPFolderItem *item = (IMAPFolderItem *)_item;
	IMAPSession *session;
	gint ok, nummsgs = 0, exists, uid_val, uid_next = 0;
	GSList *uidlist = NULL;
	gchar *dir;
	gboolean selected_folder;
	
	debug_print("get_num_list\n");
	
	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(item != NULL, -1);
	g_return_val_if_fail(item->item.path != NULL, -1);
	g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
	g_return_val_if_fail(folder->account != NULL, -1);

	session = imap_session_get(folder);
	g_return_val_if_fail(session != NULL, -1);
	lock_session();

	if (FOLDER_ITEM(item)->path) 
		statusbar_print_all(_("Scanning folder %s%c%s ..."),
				      FOLDER_ITEM(item)->folder->name, 
				      G_DIR_SEPARATOR,
				      FOLDER_ITEM(item)->path);
	else
		statusbar_print_all(_("Scanning folder %s ..."),
				      FOLDER_ITEM(item)->folder->name);

	selected_folder = (session->mbox != NULL) &&
			  (!strcmp(session->mbox, item->item.path));
	if (selected_folder && time(NULL) - item->use_cache < 2) {
		ok = imap_cmd_noop(session);
		if (ok != IMAP_SUCCESS) {
			debug_print("disconnected!\n");
			session = imap_reconnect_if_possible(folder, session);
			if (session == NULL) {
				statusbar_pop_all();
				unlock_session();
				return -1;
			}
		}
		exists = session->exists;

		uid_next = item->c_uid_next;
		uid_val = item->c_uid_validity;
		*old_uids_valid = TRUE;
	} else {
		if (item->use_cache && time(NULL) - item->use_cache < 2) {
			exists = item->c_messages;
			uid_next = item->c_uid_next;
			uid_val = item->c_uid_validity;
			ok = IMAP_SUCCESS;
			debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
		} else {
			ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
				 &exists, &uid_next, &uid_val, NULL, FALSE);
		}
		item->item.last_num = uid_next - 1;
		
		item->use_cache = (time_t)0;
		if (ok != IMAP_SUCCESS) {
			statusbar_pop_all();
			unlock_session();
			return -1;
		}
		if(item->item.mtime == uid_val)
			*old_uids_valid = TRUE;
		else {
			*old_uids_valid = FALSE;

			debug_print("Freeing imap uid cache\n");
			item->lastuid = 0;
			g_slist_free(item->uid_list);
			item->uid_list = NULL;
		
    			item->item.mtime = uid_val;

			imap_delete_all_cached_messages((FolderItem *)item);
		}
	}

	/* If old uid_next matches new uid_next we can be sure no message
	   was added to the folder */
	debug_print("uid_next is %d and item->uid_next %d \n", 
		uid_next, item->uid_next);
	if (uid_next == item->uid_next) {
		nummsgs = g_slist_length(item->uid_list);

		/* If number of messages is still the same we
                   know our caches message numbers are still valid,
                   otherwise if the number of messages has decrease
		   we discard our cache to start a new scan to find
		   out which numbers have been removed */
		if (exists == nummsgs) {
			debug_print("exists == nummsgs\n");
			*msgnum_list = g_slist_copy(item->uid_list);
			statusbar_pop_all();
			unlock_session();
			return nummsgs;
		} else if (exists < nummsgs) {
			debug_print("Freeing imap uid cache");
			item->lastuid = 0;
			g_slist_free(item->uid_list);
			item->uid_list = NULL;
		}
	}

	if (exists == 0) {
		*msgnum_list = NULL;
		statusbar_pop_all();
		unlock_session();
		return 0;
	}

	nummsgs = get_list_of_uids(session, folder, item, &uidlist);

	if (nummsgs < 0) {
		statusbar_pop_all();
		unlock_session();
		return -1;
	}

	if (nummsgs != exists) {
		/* Cache contains more messages then folder, we have cached
                   an old UID of a message that was removed and new messages
                   have been added too, otherwise the uid_next check would
		   not have failed */
		debug_print("Freeing imap uid cache");
		item->lastuid = 0;
		g_slist_free(item->uid_list);
		item->uid_list = NULL;

		g_slist_free(*msgnum_list);

		nummsgs = get_list_of_uids(session, folder, item, &uidlist);
	}

	*msgnum_list = uidlist;

	dir = folder_item_get_path((FolderItem *)item);
	debug_print("removing old messages from %s\n", dir);
	remove_numbered_files_not_in_list(dir, *msgnum_list);
	g_free(dir);
	
	item->uid_next = uid_next;
	
	debug_print("get_num_list - ok - %i\n", nummsgs);
	statusbar_pop_all();
	unlock_session();
	return nummsgs;
}

static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
{
	MsgInfo *msginfo;
	MsgFlags flags;

	flags.perm_flags = MSG_NEW|MSG_UNREAD;
	flags.tmp_flags = 0;

	g_return_val_if_fail(item != NULL, NULL);
	g_return_val_if_fail(file != NULL, NULL);

	if (folder_has_parent_of_type(item, F_QUEUE)) {
		MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
	} else if (folder_has_parent_of_type(item, F_DRAFT)) {
		MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
	}

	msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
	if (!msginfo) return NULL;
	
	msginfo->plaintext_file = g_strdup(file);
	msginfo->folder = item;

	return msginfo;
}

GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
			  GSList *msgnum_list)
{
	IMAPSession *session;
	MsgInfoList *ret = NULL;
	gint ok;
	
	debug_print("get_msginfos\n");
	
	g_return_val_if_fail(folder != NULL, NULL);
	g_return_val_if_fail(item != NULL, NULL);
	g_return_val_if_fail(msgnum_list != NULL, NULL);

	session = imap_session_get(folder);
	g_return_val_if_fail(session != NULL, NULL);
	lock_session();
	debug_print("IMAP getting msginfos\n");
	ok = imap_select(session, IMAP_FOLDER(folder), item->path,
			 NULL, NULL, NULL, NULL, FALSE);
	if (ok != IMAP_SUCCESS) {
		unlock_session();
		return NULL;
	}
	if (!(folder_has_parent_of_type(item, F_DRAFT) || 
	      folder_has_parent_of_type(item, F_QUEUE))) {
		ret = g_slist_concat(ret,
			imap_get_uncached_messages(session, item,
						   msgnum_list));
	} else {
		MsgNumberList *sorted_list, *elem, *llast = NULL;
		gint startnum, lastnum;

 		sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);

		startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);

		llast = g_slist_last(ret);
		for (elem = sorted_list;; elem = g_slist_next(elem)) {
			guint num = 0;

			if (elem)
				num = GPOINTER_TO_INT(elem->data);

			if (num > lastnum + 1 || elem == NULL) {
				int i;
				for (i = startnum; i <= lastnum; ++i) {
					gchar *file;
			
					file = imap_fetch_msg(folder, item, i);
					if (file != NULL) {
						MsgInfo *msginfo = imap_parse_msg(file, item);
						if (msginfo != NULL) {
							msginfo->msgnum = i;
							if (llast == NULL)
								llast = ret = g_slist_append(ret, msginfo);
							else {
								llast = g_slist_append(llast, msginfo);
								llast = llast->next;
							}
						}
						g_free(file);
					}
					session_set_access_time(SESSION(session));
				}

				if (elem == NULL)
					break;

				startnum = num;
			}
			lastnum = num;
		}

		g_slist_free(sorted_list);
	}
	unlock_session();
	return ret;
}

MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
{
	MsgInfo *msginfo = NULL;
	MsgInfoList *msginfolist;
	MsgNumberList numlist;

	numlist.next = NULL;
	numlist.data = GINT_TO_POINTER(uid);

	msginfolist = imap_get_msginfos(folder, item, &numlist);
	if (msginfolist != NULL) {
		msginfo = msginfolist->data;
		g_slist_free(msginfolist);
	}

	return msginfo;
}

gboolean imap_scan_required(Folder *folder, FolderItem *_item)
{
	IMAPSession *session;
	IMAPFolderItem *item = (IMAPFolderItem *)_item;
	gint ok, exists = 0, unseen = 0;
	guint32 uid_next, uid_val;
	gboolean selected_folder;
	
	g_return_val_if_fail(folder != NULL, FALSE);
	g_return_val_if_fail(item != NULL, FALSE);
	g_return_val_if_fail(item->item.folder != NULL, FALSE);
	g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);

	if (item->item.path == NULL)
		return FALSE;

	session = imap_session_get(folder);
	g_return_val_if_fail(session != NULL, FALSE);
	lock_session();
	selected_folder = (session->mbox != NULL) &&
			  (!strcmp(session->mbox, item->item.path));
	if (selected_folder && time(NULL) - item->use_cache < 2) {
		ok = imap_cmd_noop(session);
		if (ok != IMAP_SUCCESS) {
			debug_print("disconnected!\n");
			session = imap_reconnect_if_possible(folder, session);
			if (session == NULL)
				return FALSE;
			lock_session();
		}

		if (session->folder_content_changed
		||  session->exists != item->item.total_msgs) {
			unlock_session();
			return TRUE;
		}
	} else {
		ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
				 &exists, &uid_next, &uid_val, &unseen, FALSE);
		if (ok != IMAP_SUCCESS) {
			unlock_session();
			return FALSE;
		}

		item->use_cache = time(NULL);
		item->c_messages = exists;
		item->c_uid_next = uid_next;
		item->c_uid_validity = uid_val;
		item->c_unseen = unseen;
		item->item.last_num = uid_next - 1;
		debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n", 
			uid_next, item->uid_next, exists, item->item.total_msgs);
		if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
		    || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
			unlock_session();
			return TRUE;
		}
	}
	unlock_session();
	return FALSE;
}

void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
{
	IMAPSession *session;
	IMAPFlags flags_set = 0, flags_unset = 0;
	gint ok = IMAP_SUCCESS;
	MsgNumberList numlist;
	hashtable_data *ht_data = NULL;

	g_return_if_fail(folder != NULL);
	g_return_if_fail(folder->klass == &imap_class);
	g_return_if_fail(item != NULL);
	g_return_if_fail(item->folder == folder);
	g_return_if_fail(msginfo != NULL);
	g_return_if_fail(msginfo->folder == item);

	session = imap_session_get(folder);
	if (!session) {
		return;
	}

	if (!MSG_IS_MARKED(msginfo->flags) &&  (newflags & MSG_MARKED))
		flags_set |= IMAP_FLAG_FLAGGED;
	if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
		flags_unset |= IMAP_FLAG_FLAGGED;

	if (!MSG_IS_UNREAD(msginfo->flags) &&  (newflags & MSG_UNREAD))
		flags_unset |= IMAP_FLAG_SEEN;
	if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
		flags_set |= IMAP_FLAG_SEEN;

	if (!MSG_IS_REPLIED(msginfo->flags) &&  (newflags & MSG_REPLIED))
		flags_set |= IMAP_FLAG_ANSWERED;
	if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
		flags_unset |= IMAP_FLAG_ANSWERED;

	if (!MSG_IS_DELETED(msginfo->flags) &&  (newflags & MSG_DELETED))
		flags_set |= IMAP_FLAG_DELETED;
	if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
		flags_unset |= IMAP_FLAG_DELETED;

	if (!flags_set && !flags_unset) {
		/* the changed flags were not translatable to IMAP-speak.
		 * like MSG_POSTFILTERED, so just apply. */
		msginfo->flags.perm_flags = newflags;
		return;
	}

	lock_session();
	if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
	    NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
	    	unlock_session();
		return;
	}
	numlist.next = NULL;
	numlist.data = GINT_TO_POINTER(msginfo->msgnum);

	if (IMAP_FOLDER_ITEM(item)->batching) {
		/* instead of performing an UID STORE command for each message change,
		 * as a lot of them can change "together", we just fill in hashtables
		 * and defer the treatment so that we're able to send only one
		 * command.
		 */
		debug_print("IMAP batch mode on, deferring flags change\n");
		if (flags_set) {
			ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table, 
				GINT_TO_POINTER(flags_set));
			if (ht_data == NULL) {
				ht_data = g_new0(hashtable_data, 1);
				ht_data->session = session;
				ht_data->item = IMAP_FOLDER_ITEM(item);
				g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table, 
					GINT_TO_POINTER(flags_set), ht_data);
			}
			if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
				ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
		} 
		if (flags_unset) {
			ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table, 
				GINT_TO_POINTER(flags_unset));
			if (ht_data == NULL) {
				ht_data = g_new0(hashtable_data, 1);
				ht_data->session = session;
				ht_data->item = IMAP_FOLDER_ITEM(item);
				g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table, 
					GINT_TO_POINTER(flags_unset), ht_data);
			}
			if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
				ht_data->msglist = g_slist_prepend(ht_data->msglist, 
					GINT_TO_POINTER(msginfo->msgnum));		
		}
	} else {
		debug_print("IMAP changing flags\n");
		if (flags_set) {
			ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
			if (ok != IMAP_SUCCESS) {
				unlock_session();
				return;
			}
		}

		if (flags_unset) {
			ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
			if (ok != IMAP_SUCCESS) {
				unlock_session();
				return;
			}
		}
	}
	msginfo->flags.perm_flags = newflags;
	unlock_session();
	return;
}

static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
{
	gint ok;
	IMAPSession *session;
	gchar *dir;
	MsgNumberList numlist;
	
	g_return_val_if_fail(folder != NULL, -1);
	g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
	g_return_val_if_fail(item != NULL, -1);

	session = imap_session_get(folder);
	if (!session) return -1;
	lock_session();
	ok = imap_select(session, IMAP_FOLDER(folder), item->path,
			 NULL, NULL, NULL, NULL, FALSE);
	if (ok != IMAP_SUCCESS) {
		unlock_session();
		return ok;
	}
	numlist.next = NULL;
	numlist.data = GINT_TO_POINTER(uid);
	
	ok = imap_set_message_flags
		(IMAP_SESSION(REMOTE_FOLDER(folder)->session),
		&numlist, IMAP_FLAG_DELETED, TRUE);
	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't set deleted flags: %d\n"), uid);
		unlock_session();
		return ok;
	}

	if (!session->uidplus) {
		ok = imap_cmd_expunge(session);
	} else {
		gchar *uidstr;

		uidstr = g_strdup_printf("%u", uid);
		ok = imap_cmd_expunge(session);
		g_free(uidstr);
	}
	if (ok != IMAP_SUCCESS) {
		log_warning(_("can't expunge\n"));
		unlock_session();
		return ok;
	}

	IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
	    IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
	dir = folder_item_get_path(item);
	if (is_dir_exist(dir))
		remove_numbered_files(dir, uid, uid);
	g_free(dir);
	unlock_session();
	return IMAP_SUCCESS;
}

static gint compare_msginfo(gconstpointer a, gconstpointer b)
{
	return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
}

static guint gslist_find_next_num(MsgNumberList **list, guint num)
{
	GSList *elem;

	g_return_val_if_fail(list != NULL, -1);

	for (elem = *list; elem != NULL; elem = g_slist_next(elem))
		if (GPOINTER_TO_INT(elem->data) >= num)
			break;
	*list = elem;
	return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
}

/*
 * NEW and DELETED flags are not syncronized
 * - The NEW/RECENT flags in IMAP folders can not really be directly
 *   modified by Sylpheed
 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
 *   meaning, in IMAP it always removes the messages from the FolderItem
 *   in Sylpheed it can mean to move the message to trash
 */

typedef struct _get_flags_data {
	Folder *folder;
	FolderItem *item;
	MsgInfoList *msginfo_list;
	GRelation *msgflags;
	gboolean full_search;
	gboolean done;
} get_flags_data;

static /*gint*/ void *imap_get_flags_thread(void *data)
{
	get_flags_data *stuff = (get_flags_data *)data;
	Folder *folder = stuff->folder;
	FolderItem *item = stuff->item;
	MsgInfoList *msginfo_list = stuff->msginfo_list;
	GRelation *msgflags = stuff->msgflags;
	gboolean full_search = stuff->full_search;
	IMAPSession *session;
	GSList *sorted_list = NULL;
	GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
	GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
	GSList *elem;
	GSList *seq_list, *cur;
	gboolean reverse_seen = FALSE;
	GString *cmd_buf;
	gint ok;
	gint exists_cnt, unseen_cnt;
	gboolean selected_folder;
	
	if (folder == NULL || item == NULL) {
		stuff->done = TRUE;
		return GINT_TO_POINTER(-1);
	}

	session = imap_session_get(folder);
	if (session == NULL) {
		stuff->done = TRUE;
		return GINT_TO_POINTER(-1);
	}
	lock_session();
	selected_folder = (session->mbox != NULL) &&
			  (!strcmp(session->mbox, item->path));

	if (!selected_folder) {
		ok = imap_select(session, IMAP_FOLDER(folder), item->path,
			&exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
		if (ok != IMAP_SUCCESS) {
			stuff->done = TRUE;
			unlock_session();
			return GINT_TO_POINTER(-1);
		}

		if (unseen_cnt > exists_cnt / 2)
			reverse_seen = TRUE;
	} 
	else {
		if (item->unread_msgs > item->total_msgs / 2)
			reverse_seen = TRUE;
	}

	cmd_buf = g_string_new(NULL);

	sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
	if (!full_search) {
		seq_list = imap_get_lep_set_from_msglist(msginfo_list);
	} else {
		struct mailimap_set * set;
		set = mailimap_set_new_interval(1, 0);
		seq_list = g_slist_append(NULL, set);
	}

	for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
		struct mailimap_set * imapset;
		clist * lep_uidlist;
		int r;
		
		imapset = cur->data;
		if (reverse_seen) {
			r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
						 full_search ? NULL:imapset, &lep_uidlist);
		}
		else {
			r = imap_threaded_search(folder,
						 IMAP_SEARCH_TYPE_UNSEEN,
						 full_search ? NULL:imapset, &lep_uidlist);
		}
		if (r == MAILIMAP_NO_ERROR) {
			GSList * uidlist;
			
			uidlist = imap_uid_list_from_lep(lep_uidlist);
			mailimap_search_result_free(lep_uidlist);
			
			unseen = g_slist_concat(unseen, uidlist);
		}
		
		r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
					 full_search ? NULL:imapset, &lep_uidlist);
		if (r == MAILIMAP_NO_ERROR) {
			GSList * uidlist;

			uidlist = imap_uid_list_from_lep(lep_uidlist);
			mailimap_search_result_free(lep_uidlist);

			flagged = g_slist_concat(flagged, uidlist);
		}

		if (item->opened || item->processing_pending || item == folder->inbox) {
			r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
						 full_search ? NULL:imapset, &lep_uidlist);
			if (r == MAILIMAP_NO_ERROR) {
				GSList * uidlist;

				uidlist = imap_uid_list_from_lep(lep_uidlist);
				mailimap_search_result_free(lep_uidlist);

				answered = g_slist_concat(answered, uidlist);
			}

			r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
						 full_search ? NULL:imapset, &lep_uidlist);
			if (r == MAILIMAP_NO_ERROR) {
				GSList * uidlist;

				uidlist = imap_uid_list_from_lep(lep_uidlist);
				mailimap_search_result_free(lep_uidlist);

				deleted = g_slist_concat(deleted, uidlist);
			}
		}
	}

	p_unseen = unseen;
	p_answered = answered;
	p_flagged = flagged;
	p_deleted = deleted;

	for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
		MsgInfo *msginfo;
		MsgPermFlags flags;
		gboolean wasnew;
		
		msginfo = (MsgInfo *) elem->data;
		flags = msginfo->flags.perm_flags;
		wasnew = (flags & MSG_NEW);
		if (item->opened || item->processing_pending || item == folder->inbox) {
			flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
		} else {
			flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
		}
		if (reverse_seen)
			flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
		if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
			if (!reverse_seen) {
				flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
			} else {
				flags &= ~(MSG_UNREAD | MSG_NEW);
			}
		}
		
		if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
			flags |= MSG_MARKED;
		else
			flags &= ~MSG_MARKED;

		if (item->opened || item->processing_pending || item == folder->inbox) {
			if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
				flags |= MSG_REPLIED;
			else
				flags &= ~MSG_REPLIED;
			if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
				flags |= MSG_DELETED;
			else
				flags &= ~MSG_DELETED;
		}
		g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
	}

	imap_lep_set_free(seq_list);
	g_slist_free(flagged);
	g_slist_free(deleted);
	g_slist_free(answered);
	g_slist_free(unseen);
	g_slist_free(sorted_list);
	g_string_free(cmd_buf, TRUE);

	stuff->done = TRUE;
	unlock_session();
	return GINT_TO_POINTER(0);
}

static gint imap_get_flags(Folder *folder, FolderItem *item,
                           MsgInfoList *msginfo_list, GRelation *msgflags)
{
	gint result;
	get_flags_data *data = g_new0(get_flags_data, 1);
	data->done = FALSE;
	data->folder = folder;
	data->item = item;
	data->msginfo_list = msginfo_list;
	data->msgflags = msgflags;
	data->full_search = FALSE;

	GSList *tmp = NULL, *cur;
	
	if (prefs_common.work_offline && 
	    !inc_offline_should_override(
		_("Sylpheed-Claws needs network access in order "
		  "to access the IMAP server."))) {
		g_free(data);
		return -1;
	}

	tmp = folder_item_get_msg_list(item);

	if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
		data->full_search = TRUE;
	
	for (cur = tmp; cur; cur = cur->next)
		procmsg_msginfo_free((MsgInfo *)cur->data);
	
	g_slist_free(tmp);

	result = GPOINTER_TO_INT(imap_get_flags_thread(data));
	
	g_free(data);
	return result;

}

static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
{
	gboolean flags_set = GPOINTER_TO_INT(user_data);
	gint flags_value = GPOINTER_TO_INT(key);
	hashtable_data *data = (hashtable_data *)value;
	IMAPFolderItem *_item = data->item;
	FolderItem *item = (FolderItem *)_item;
	gint ok = IMAP_ERROR;
	IMAPSession *session = imap_session_get(item->folder);

	data->msglist = g_slist_reverse(data->msglist);
	
	debug_print("IMAP %ssetting flags to %d for %d messages\n",
		flags_set?"":"un",
		flags_value,
		g_slist_length(data->msglist));
	
	if (session) {
		lock_session();
		ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
			 NULL, NULL, NULL, NULL, FALSE);
	}
	if (ok == IMAP_SUCCESS) {
		imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
	} else {
		g_warning("can't select mailbox %s\n", item->path);
	}
	if (session)
		unlock_session();
	g_slist_free(data->msglist);	
	g_free(data);
	return TRUE;
}

static void process_hashtable(IMAPFolderItem *item)
{
	if (item->flags_set_table) {
		g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
		g_hash_table_destroy(item->flags_set_table);
		item->flags_set_table = NULL;
	}
	if (item->flags_unset_table) {
		g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
		g_hash_table_destroy(item->flags_unset_table);
		item->flags_unset_table = NULL;
	}
}

static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
{
	IMAPFolderItem *item = (IMAPFolderItem *)_item;

	g_return_if_fail(item != NULL);
	
	if (item->batching == batch)
		return;
	
	if (batch) {
		item->batching = TRUE;
		debug_print("IMAP switching to batch mode\n");
		if (!item->flags_set_table) {
			item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
		}
		if (!item->flags_unset_table) {
			item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
		}
	} else {
		debug_print("IMAP switching away from batch mode\n");
		/* process stuff */
		process_hashtable(item);
		item->batching = FALSE;
	}
}



/* data types conversion libetpan <-> sylpheed */



#define ETPAN_IMAP_MB_MARKED      1
#define ETPAN_IMAP_MB_UNMARKED    2
#define ETPAN_IMAP_MB_NOSELECT    4
#define ETPAN_IMAP_MB_NOINFERIORS 8

static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
{
  int flags;
  clistiter * cur;
  
  flags = 0;
  if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
    switch (imap_flags->mbf_sflag) {
    case MAILIMAP_MBX_LIST_SFLAG_MARKED:
      flags |= ETPAN_IMAP_MB_MARKED;
      break;
    case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
      flags |= ETPAN_IMAP_MB_NOSELECT;
      break;
    case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
      flags |= ETPAN_IMAP_MB_UNMARKED;
      break;
    }
  }
  
  for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
      cur = clist_next(cur)) {
    struct mailimap_mbx_list_oflag * oflag;
    
    oflag = clist_content(cur);
    
    switch (oflag->of_type) {
    case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
      flags |= ETPAN_IMAP_MB_NOINFERIORS;
      break;
    }
  }
  
  return flags;
}

static GSList * imap_list_from_lep(IMAPFolder * folder,
				   clist * list, const gchar * real_path, gboolean all)
{
	clistiter * iter;
	GSList * item_list = NULL, *llast = NULL;
	
	for(iter = clist_begin(list) ; iter != NULL ;
	    iter = clist_next(iter)) {
		struct mailimap_mailbox_list * mb;
		int flags;
		char delimiter;
		char * name;
		char * dup_name;
		gchar * base;
		gchar * loc_name;
		gchar * loc_path;
		FolderItem *new_item;
		
		mb = clist_content(iter);

		if (mb == NULL)
			continue;

		flags = 0;
		if (mb->mb_flag != NULL)
			flags = imap_flags_to_flags(mb->mb_flag);
		
		delimiter = mb->mb_delimiter;
		name = mb->mb_name;
		
		dup_name = strdup(name);		
		if (delimiter != '\0')
			subst_char(dup_name, delimiter, '/');
		
		base = g_path_get_basename(dup_name);
		if (base[0] == '.') {
			g_free(base);
			free(dup_name);
			continue;
		}
		
		if (!all && strcmp(dup_name, real_path) == 0) {
			g_free(base);
			free(dup_name);
			continue;
		}

		if (!all && dup_name[strlen(dup_name)-1] == '/') {
			g_free(base);
			free(dup_name);
			continue;
		}
		
		loc_name = imap_modified_utf7_to_utf8(base);
		loc_path = imap_modified_utf7_to_utf8(dup_name);
		
		new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
		if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
			new_item->no_sub = TRUE;
		if (strcmp(dup_name, "INBOX") != 0 &&
		    ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
			new_item->no_select = TRUE;
		
		if (item_list == NULL)
			llast = item_list = g_slist_append(item_list, new_item);
		else {
			llast = g_slist_append(llast, new_item);
			llast = llast->next;
		}
		debug_print("folder '%s' found.\n", loc_path);
		g_free(base);
		g_free(loc_path);
		g_free(loc_name);
		
		free(dup_name);
	}
	
	return item_list;
}

static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
{
	GSList *sorted_list, *cur;
	guint first, last, next;
	GSList *ret_list = NULL, *llast = NULL;
	unsigned int count;
	struct mailimap_set * current_set;
	unsigned int item_count;
	
	if (numlist == NULL)
		return NULL;
	
	count = 0;
	current_set = mailimap_set_new_empty();
	
	sorted_list = g_slist_copy(numlist);
	sorted_list = g_slist_sort(sorted_list, g_int_compare);

	first = GPOINTER_TO_INT(sorted_list->data);
	
	item_count = 0;
	for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
		if (GPOINTER_TO_INT(cur->data) == 0)
			continue;
		
		item_count ++;

		last = GPOINTER_TO_INT(cur->data);
		if (cur->next)
			next = GPOINTER_TO_INT(cur->next->data);
		else
			next = 0;

		if (last + 1 != next || next == 0) {

			struct mailimap_set_item * item;
			item = mailimap_set_item_new(first, last);
			mailimap_set_add(current_set, item);
			count ++;
			
			first = next;
			
			if (count >= IMAP_SET_MAX_COUNT) {
				if (ret_list == NULL)
					llast = ret_list = g_slist_append(ret_list,
							  current_set);
				else {
					llast = g_slist_append(llast, current_set);
					llast = llast->next;
				}
				current_set = mailimap_set_new_empty();
				count = 0;
				item_count = 0;
			}
		}
	}
	
	if (clist_count(current_set->set_list) > 0) {
		ret_list = g_slist_append(ret_list,
					  current_set);
	}
	
	g_slist_free(sorted_list);

	return ret_list;
}

static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
{
	MsgNumberList *numlist = NULL;
	MsgInfoList *cur;
	GSList *seq_list;

	for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
		MsgInfo *msginfo = (MsgInfo *) cur->data;

		numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
	}
	numlist = g_slist_reverse(numlist);
	seq_list = imap_get_lep_set_from_numlist(numlist);
	g_slist_free(numlist);

	return seq_list;
}

static GSList * imap_uid_list_from_lep(clist * list)
{
	clistiter * iter;
	GSList * result;
	
	result = NULL;
	
	for(iter = clist_begin(list) ; iter != NULL ;
	    iter = clist_next(iter)) {
		uint32_t * puid;
		
		puid = clist_content(iter);
		result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
	}
	
	result = g_slist_reverse(result);
	return result;
}

static GSList * imap_uid_list_from_lep_tab(carray * list)
{
	unsigned int i;
	GSList * result;
	
	result = NULL;
	
	for(i = 0 ; i < carray_count(list) ; i ++) {
		uint32_t * puid;
		
		puid = carray_get(list, i);
		result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
	}
	result = g_slist_reverse(result);
	return result;
}

static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
				       FolderItem *item)
{
	MsgInfo *msginfo = NULL;
	guint32 uid = 0;
	size_t size = 0;
	MsgFlags flags = {0, 0};
	
	if (info->headers == NULL)
		return NULL;

	MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
	if (folder_has_parent_of_type(item, F_QUEUE)) {
		MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
	} else if (folder_has_parent_of_type(item, F_DRAFT)) {
		MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
	}
	flags.perm_flags = info->flags;
	
	uid = info->uid;
	size = info->size;
	msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
	
	if (msginfo) {
		msginfo->msgnum = uid;
		msginfo->size = size;
	}

	return msginfo;
}

static void imap_lep_set_free(GSList *seq_list)
{
	GSList * cur;
	
	for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
		struct mailimap_set * imapset;
		
		imapset = cur->data;
		mailimap_set_free(imapset);
	}
	g_slist_free(seq_list);
}

static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
{
	struct mailimap_flag_list * flag_list;
	
	flag_list = mailimap_flag_list_new_empty();
	
	if (IMAP_IS_SEEN(flags))
		mailimap_flag_list_add(flag_list,
				       mailimap_flag_new_seen());
	if (IMAP_IS_ANSWERED(flags))
		mailimap_flag_list_add(flag_list,
				       mailimap_flag_new_answered());
	if (IMAP_IS_FLAGGED(flags))
		mailimap_flag_list_add(flag_list,
				       mailimap_flag_new_flagged());
	if (IMAP_IS_DELETED(flags))
		mailimap_flag_list_add(flag_list,
				       mailimap_flag_new_deleted());
	if (IMAP_IS_DRAFT(flags))
		mailimap_flag_list_add(flag_list,
				       mailimap_flag_new_draft());
	
	return flag_list;
}

guint imap_folder_get_refcnt(Folder *folder)
{
	return ((IMAPFolder *)folder)->refcnt;
}

void imap_folder_ref(Folder *folder)
{
	((IMAPFolder *)folder)->refcnt++;
}

void imap_folder_unref(Folder *folder)
{
	if (((IMAPFolder *)folder)->refcnt > 0)
		((IMAPFolder *)folder)->refcnt--;
}

#else /* HAVE_LIBETPAN */

static FolderClass imap_class;

static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);

static Folder	*imap_folder_new	(const gchar	*name,
					 const gchar	*path)
{
	return NULL;
}
static gint 	imap_create_tree	(Folder 	*folder)
{
	return -1;
}
static FolderItem *imap_create_folder	(Folder 	*folder,
				      	 FolderItem 	*parent,
				      	 const gchar 	*name)
{
	return NULL;
}
static gint 	imap_rename_folder	(Folder 	*folder,
			       		 FolderItem 	*item, 
					 const gchar 	*name)
{
	return -1;
}

gchar imap_get_path_separator_for_item(FolderItem *item)
{
	return '/';
}

FolderClass *imap_get_class(void)
{
	if (imap_class.idstr == NULL) {
		imap_class.type = F_IMAP;
		imap_class.idstr = "imap";
		imap_class.uistr = "IMAP4";

		imap_class.new_folder = imap_folder_new;
		imap_class.create_tree = imap_create_tree;
		imap_class.create_folder = imap_create_folder;
		imap_class.rename_folder = imap_rename_folder;

		imap_class.set_xml = folder_set_xml;
		imap_class.get_xml = folder_get_xml;
		imap_class.item_set_xml = imap_item_set_xml;
		imap_class.item_get_xml = imap_item_get_xml;
		/* nothing implemented */
	}

	return &imap_class;
}
#endif

void imap_synchronise(FolderItem *item) 
{
	imap_gtk_synchronise(item);
}

static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
{
#ifdef HAVE_LIBETPAN
	GList *cur;
#endif
	folder_item_set_xml(folder, item, tag);
	
#ifdef HAVE_LIBETPAN
	for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
		XMLAttr *attr = (XMLAttr *) cur->data;

		if (!attr || !attr->name || !attr->value) continue;
		if (!strcmp(attr->name, "uidnext"))
			IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
	}
#endif
}

static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
{
	XMLTag *tag;

	tag = folder_item_get_xml(folder, item);

#ifdef HAVE_LIBETPAN
	xml_tag_add_attr(tag, xml_attr_new_int("uidnext", 
			IMAP_FOLDER_ITEM(item)->uid_next));

#endif
	return tag;
}
