/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* mail-importer.c
 *
 * Authors: Iain Holmes <iain@ximian.com>
 *          Michael Zucchi <notzed@ximian.com>
 *
 * Copyright (C) 2001-2003 Ximian, Inc.
 * Copyright (C) 2004 Novell Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

#include <string.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include <glib.h>
#include <glib/gstdio.h>

#include <gmodule.h>
#include <libgnome/gnome-util.h>
#include <libgnome/gnome-i18n.h>
#include <camel/camel-folder.h>
#include <camel/camel-store.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-mime-parser.h>
#include <camel/camel-exception.h>
#include <camel/camel-stream-mem.h>

#include "mail/mail-mt.h"
#include "mail/mail-component.h"
#include "mail/mail-tools.h"

#include "mail-importer.h"

/**
 * mail_importer_make_local_folder:
 * @folderpath: 
 * 
 * Check a local folder exists at path @folderpath, and if not, create it.
 * 
 * Return value: The physical uri of the folder, or NULL if the folder did
 * not exist and could not be created.
 **/
char *
mail_importer_make_local_folder(const char *folderpath)
{
	return g_strdup_printf("mbox:/home/notzed/.evolution/mail/local/%s", folderpath);
}

/**
 * mail_importer_add_line:
 * importer: A MailImporter structure.
 * str: Next line of the mbox.
 * finished: TRUE if @str is the last line of the message.
 *
 * Adds lines to the message until it is finished, and then adds
 * the complete message to the folder.
 */
void
mail_importer_add_line (MailImporter *importer,
			const char *str,
			gboolean finished)
{
	CamelMimeMessage *msg;
	CamelMessageInfo *info;
	CamelException *ex;
	
	if (importer->mstream == NULL)
		importer->mstream = CAMEL_STREAM_MEM (camel_stream_mem_new ());

	camel_stream_write (CAMEL_STREAM (importer->mstream), str,  strlen (str));
	
	if (finished == FALSE)
		return;

	camel_stream_reset (CAMEL_STREAM (importer->mstream));
	info = camel_message_info_new(NULL);
	camel_message_info_set_flags(info, CAMEL_MESSAGE_SEEN, ~0);

	msg = camel_mime_message_new ();
	camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg),
						  CAMEL_STREAM (importer->mstream));
	
	camel_object_unref (importer->mstream);
	importer->mstream = NULL;

	ex = camel_exception_new ();
	camel_folder_append_message (importer->folder, msg, info, NULL, ex);
	camel_object_unref (msg);

	camel_exception_free (ex);
	camel_message_info_free(info);
}

struct _BonoboObject *mail_importer_factory_cb(struct _BonoboGenericFactory *factory, const char *iid, void *data)
{
#if 0
	if (strcmp(iid, ELM_INTELLIGENT_IMPORTER_IID) == 0)
		return elm_intelligent_importer_new();
	else if (strcmp(iid, PINE_INTELLIGENT_IMPORTER_IID) == 0)
		return pine_intelligent_importer_new();
	else if (strcmp(iid, NETSCAPE_INTELLIGENT_IMPORTER_IID) == 0)
		return netscape_intelligent_importer_new();
	else if (strcmp(iid, MBOX_IMPORTER_IID) == 0)
		return mbox_importer_new();
	else if (strcmp(iid, OUTLOOK_IMPORTER_IID) == 0)
		return outlook_importer_new();
#endif
	return NULL;
}

struct _import_mbox_msg {
	struct _mail_msg msg;
	
	char *path;
	char *uri;
	CamelOperation *cancel;

	void (*done)(void *data, CamelException *ex);
	void *done_data;
};

static char *
import_mbox_describe(struct _mail_msg *mm, int complete)
{
	return g_strdup (_("Importing mailbox"));
}

static struct {
	char tag;
	guint32 mozflag;
	guint32 flag;
} status_flags[] = {
	{ 'F', MSG_FLAG_MARKED, CAMEL_MESSAGE_FLAGGED },
	{ 'A', MSG_FLAG_REPLIED, CAMEL_MESSAGE_ANSWERED },
	{ 'D', MSG_FLAG_EXPUNGED, CAMEL_MESSAGE_DELETED },
	{ 'R', MSG_FLAG_READ, CAMEL_MESSAGE_SEEN },
};

static guint32
decode_status(const char *status)
{
	const char *p;
	char c;
	guint32 flags = 0;
	int i;

	p = status;
	while ((c = *p++)) {
		for (i=0;i<sizeof(status_flags)/sizeof(status_flags[0]);i++)
			if (status_flags[i].tag == *p)
				flags |= status_flags[i].flag;
	}

	return flags;
}

static guint32
decode_mozilla_status(const char *tmp)
{
	unsigned long status = strtoul(tmp, NULL, 16);
	guint32 flags = 0;
	int i;

	for (i=0;i<sizeof(status_flags)/sizeof(status_flags[0]);i++)
		if (status_flags[i].mozflag & status)
			flags |= status_flags[i].flag;
	return flags;
}

static void
import_mbox_import(struct _mail_msg *mm)
{
	struct _import_mbox_msg *m = (struct _import_mbox_msg *) mm;
	CamelFolder *folder;
	CamelMimeParser *mp = NULL;
	struct stat st;
	int fd;
	CamelMessageInfo *info;

	if (g_stat(m->path, &st) == -1) {
		g_warning("cannot find source file to import '%s': %s", m->path, g_strerror(errno));
		return;
	}

	if (m->uri == NULL || m->uri[0] == 0)
		folder = mail_component_get_folder(NULL, MAIL_COMPONENT_FOLDER_INBOX);
	else
		folder = mail_tool_uri_to_folder(m->uri, CAMEL_STORE_FOLDER_CREATE, &mm->ex);

	if (folder == NULL)
		return;

	if (S_ISREG(st.st_mode)) {
		CamelOperation *oldcancel = NULL;

		fd = g_open(m->path, O_RDONLY|O_BINARY, 0);
		if (fd == -1) {
			g_warning("cannot find source file to import '%s': %s", m->path, g_strerror(errno));
			goto fail1;
		}

		mp = camel_mime_parser_new();
		camel_mime_parser_scan_from(mp, TRUE);
		if (camel_mime_parser_init_with_fd(mp, fd) == -1) {
			goto fail2;
		}

		if (m->cancel)
			oldcancel = camel_operation_register(m->cancel);

		camel_operation_start(NULL, _("Importing `%s'"), folder->full_name);
		camel_folder_freeze(folder);
		while (camel_mime_parser_step(mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) {
			CamelMimeMessage *msg;
			const char *tmp;
			int pc;
			guint32 flags = 0;

			if (st.st_size > 0)
				pc = (int)(100.0 * ((double)camel_mime_parser_tell(mp) / (double)st.st_size));
			camel_operation_progress(NULL, pc);

			msg = camel_mime_message_new();
			if (camel_mime_part_construct_from_parser((CamelMimePart *)msg, mp) == -1) {
				/* set exception? */
				camel_object_unref(msg);
				break;
			}

			info = camel_message_info_new(NULL);

			tmp = camel_medium_get_header((CamelMedium *)msg, "X-Mozilla-Status");
			if (tmp)
				flags |= decode_mozilla_status(tmp);
			tmp = camel_medium_get_header((CamelMedium *)msg, "Status");
			if (tmp)
				flags |= decode_status(tmp);
			tmp = camel_medium_get_header((CamelMedium *)msg, "X-Status");
			if (tmp)
				flags |= decode_status(tmp);

			camel_message_info_set_flags(info, flags, ~0);
			camel_folder_append_message(folder, msg, info, NULL, &mm->ex);
			camel_message_info_free(info);
			camel_object_unref(msg);

			if (camel_exception_is_set(&mm->ex))
				break;

			camel_mime_parser_step(mp, 0, 0);
		}
		camel_folder_sync(folder, FALSE, NULL);
		camel_folder_thaw(folder);
		camel_operation_end(NULL);
		/* TODO: these api's are a bit weird, registering the old is the same as deregistering */
		if (m->cancel)
			camel_operation_register(oldcancel);
	fail2:
		camel_object_unref(mp);
	}
fail1:
	camel_folder_sync(folder, FALSE, NULL);
	camel_object_unref(folder);
}

static void
import_mbox_done(struct _mail_msg *mm)
{
	struct _import_mbox_msg *m = (struct _import_mbox_msg *)mm;

	if (m->done)
		m->done(m->done_data, &mm->ex);
}

static void
import_mbox_free (struct _mail_msg *mm)
{
	struct _import_mbox_msg *m = (struct _import_mbox_msg *)mm;
	
	if (m->cancel)
		camel_operation_unref(m->cancel);
	g_free(m->uri);
	g_free(m->path);
}

static struct _mail_msg_op import_mbox_op = {
	import_mbox_describe,
	import_mbox_import,
	import_mbox_done,
	import_mbox_free,
};

int
mail_importer_import_mbox(const char *path, const char *folderuri, CamelOperation *cancel, void (*done)(void *data, CamelException *), void *data)
{
	struct _import_mbox_msg *m;
	int id;

	m = mail_msg_new(&import_mbox_op, NULL, sizeof (*m));
	m->path = g_strdup(path);
	m->uri = g_strdup(folderuri);
	m->done = done;
	m->done_data = data;
	if (cancel) {
		m->cancel = cancel;
		camel_operation_ref(cancel);
	}

	id = m->msg.seq;
	e_thread_put(mail_thread_queued, (EMsg *)m);

	return id;
}

void
mail_importer_import_mbox_sync(const char *path, const char *folderuri, CamelOperation *cancel)
{
	struct _import_mbox_msg *m;

	m = mail_msg_new(&import_mbox_op, NULL, sizeof (*m));
	m->path = g_strdup(path);
	m->uri = g_strdup(folderuri);
	if (cancel) {
		m->cancel = cancel;
		camel_operation_ref(cancel);
	}

	import_mbox_import(&m->msg);
	import_mbox_done(&m->msg);
	mail_msg_free(&m->msg);
}

struct _import_folders_data {
	MailImporterSpecial *special_folders;
	CamelOperation *cancel;

	int elmfmt:1;
};

static void
import_folders_rec(struct _import_folders_data *m, const char *filepath, const char *folderparent)
{
	GDir *dir;
	const char *d;
	struct stat st;
	char *filefull, *foldersub, *uri, *utf8_filename;
	const char *folder;

	dir = g_dir_open(filepath, 0, NULL);
 	if (dir == NULL)
		return;

	utf8_filename = g_filename_to_utf8 (filepath, -1, NULL, NULL, NULL);
	camel_operation_start(NULL, _("Scanning %s"), utf8_filename);
	g_free (utf8_filename);

	while ( (d=g_dir_read_name(dir))) {
		if (d[0] == '.')
			continue;

		filefull = g_build_filename(filepath, d, NULL);

		/* skip non files and directories, and skip directories in mozilla mode */
		if (g_stat(filefull, &st) == -1
		    || !(S_ISREG(st.st_mode)
			 || (m->elmfmt && S_ISDIR(st.st_mode)))) {
			g_free(filefull);
			continue;
		}

		folder = d;
		if (folderparent == NULL) {
			int i;

			for (i=0;m->special_folders[i].orig;i++)
				if (strcmp(m->special_folders[i].orig, folder) == 0) {
					folder = m->special_folders[i].new;
					break;
				}
			/* FIXME: need a better way to get default store location */
			uri = g_strdup_printf("mbox:%s/mail/local#%s", mail_component_peek_base_directory(NULL), folder);
		} else {
			uri = g_strdup_printf("mbox:%s/mail/local#%s/%s", mail_component_peek_base_directory(NULL), folderparent, folder);
		}

		printf("importing to uri %s\n", uri);
		mail_importer_import_mbox_sync(filefull, uri, m->cancel);
		g_free(uri);

		/* This little gem re-uses the stat buffer and filefull to automagically scan mozilla-format folders */
		if (!m->elmfmt) {
			char *tmp = g_strdup_printf("%s.sbd", filefull);

			g_free(filefull);
			filefull = tmp;
			if (g_stat(filefull, &st) == -1) {
				g_free(filefull);
				continue;
			}
		}

		if (S_ISDIR(st.st_mode)) {
			foldersub = folderparent?g_strdup_printf("%s/%s", folderparent, folder):g_strdup(folder);
			import_folders_rec(m, filefull, foldersub);
			g_free(foldersub);
		}

		g_free(filefull);
	}
	g_dir_close(dir);

	camel_operation_end(NULL);
}

/**
 * mail_importer_import_folders_sync:
 * @filepath: 
 * @: 
 * @flags: 
 * @cancel: 
 * 
 * import from a base path @filepath into the root local folder tree,
 * scanning all sub-folders.
 *
 * if @flags is MAIL_IMPORTER_MOZFMT, then subfolders are assumed to
 * be in mozilla/evolutoin 1.5 format, appending '.sbd' to the
 * directory name. Otherwise they are in elm/mutt/pine format, using
 * standard unix directories.
 **/
void
mail_importer_import_folders_sync(const char *filepath, MailImporterSpecial special_folders[], int flags, CamelOperation *cancel)
{
	struct _import_folders_data m;
	CamelOperation *oldcancel = NULL;

	m.special_folders = special_folders;
	m.elmfmt = (flags & MAIL_IMPORTER_MOZFMT) == 0;
	m.cancel = cancel;

	if (cancel)
		oldcancel = camel_operation_register(cancel);

	import_folders_rec(&m, filepath, NULL);

	if (cancel)
		camel_operation_register(oldcancel);
}