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

/* Convert a mail message into a task */

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

#include <glib/gi18n-lib.h>
#include <string.h>
#include <stdio.h>

#include <gtkhtml/gtkhtml.h>
#include <gconf/gconf-client.h>
#include <libecal/e-cal.h>
#include <libedataserver/e-account.h>
#include <libedataserverui/e-source-selector-dialog.h>

#include <camel/camel-folder.h>
#include <camel/camel-medium.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-multipart.h>
#include <camel/camel-stream.h>
#include <camel/camel-stream-mem.h>
#include <camel/camel-utf8.h>

#include <mail/em-utils.h>
#include <mail/em-format-html.h>
#include <mail/mail-config.h>
#include <e-util/e-account-utils.h>
#include <e-util/e-dialog-utils.h>
#include <calendar/common/authentication.h>
#include <misc/e-popup-action.h>
#include <shell/e-shell-view.h>
#include <shell/e-shell-window-actions.h>

#define E_SHELL_WINDOW_ACTION_CONVERT_TO_EVENT(window) \
	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-event")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \
	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \
	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo")
#define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \
	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task")

gboolean	e_plugin_ui_init		(GtkUIManager *ui_manager,
						 EShellView *shell_view);

static gchar *
clean_name(const guchar *s)
{
	GString *out = g_string_new("");
	guint32 c;
	gchar *r;

	while ((c = camel_utf8_getc ((const guchar **)&s)))
	{
		if (!g_unichar_isprint (c) || ( c < 0x7f && strchr (" /'\"`&();|<>$%{}!", c )))
			c = '_';
		g_string_append_u (out, c);
	}

	r = g_strdup (out->str);
	g_string_free (out, TRUE);

	return r;
}

static void
set_attendees (ECalComponent *comp, CamelMimeMessage *message, const gchar *organizer)
{
	GSList *attendees = NULL, *to_free = NULL;
	ECalComponentAttendee *ca;
	const CamelInternetAddress *from = NULL, *to, *cc, *bcc, *arr[4];
	gint len, i, j;

	if (message->reply_to)
		from = message->reply_to;
	else if (message->from)
		from = message->from;

	to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
	cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
	bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);

	arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc;

	for (j = 0; j < 4; j++) {
		if (!arr[j])
			continue;

		len = CAMEL_ADDRESS (arr[j])->addresses->len;
		for (i = 0; i < len; i++) {
			const gchar *name, *addr;

			if (camel_internet_address_get (arr[j], i, &name, &addr)) {
				gchar *temp;

				temp = g_strconcat ("mailto:", addr, NULL);
				if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) {
					/* do not add organizer twice */
					g_free (temp);
					continue;
				}

				ca = g_new0 (ECalComponentAttendee, 1);

				ca->value = temp;
				ca->cn = name;
				ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
				ca->status = ICAL_PARTSTAT_NEEDSACTION;
				if (j == 0) {
					/* From */
					ca->role = ICAL_ROLE_CHAIR;
				} else if (j == 2) {
					/* BCC  */
					ca->role = ICAL_ROLE_OPTPARTICIPANT;
				} else {
					/* all other */
					ca->role = ICAL_ROLE_REQPARTICIPANT;
				}

				to_free = g_slist_prepend (to_free, temp);

				attendees = g_slist_append (attendees, ca);
			}
		}
	}

	e_cal_component_set_attendee_list (comp, attendees);

	g_slist_foreach (attendees, (GFunc) g_free, NULL);
	g_slist_foreach (to_free, (GFunc) g_free, NULL);

	g_slist_free (to_free);
	g_slist_free (attendees);
}

static void
set_description (ECalComponent *comp, CamelMimeMessage *message)
{
	CamelDataWrapper *content;
	CamelStream *mem;
	CamelContentType *type;
	CamelMimePart *mime_part = CAMEL_MIME_PART (message);
	ECalComponentText text;
	GSList sl;
	gchar *str, *convert_str = NULL;
	gsize bytes_read, bytes_written;
	gint count = 2;

	content = camel_medium_get_content_object ((CamelMedium *) message);
	if (!content)
		return;

	/*
	 * Get non-multipart content from multipart message.
	 */
	while (CAMEL_IS_MULTIPART (content) && count > 0)
	{
		mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0);
		content = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
		count--;
	}

	if (!mime_part)
		return;

	type = camel_mime_part_get_content_type (mime_part);
	if (!camel_content_type_is (type, "text", "plain"))
		return;

	mem = camel_stream_mem_new ();
	camel_data_wrapper_decode_to_stream (content, mem);

	str = g_strndup ((const gchar *)((CamelStreamMem *) mem)->buffer->data, ((CamelStreamMem *) mem)->buffer->len);
	camel_object_unref (mem);

	/* convert to UTF-8 string */
	if (str && content->mime_type->params && content->mime_type->params->value)
	{
		convert_str = g_convert (str, strlen (str),
					 "UTF-8", content->mime_type->params->value,
					 &bytes_read, &bytes_written, NULL);
	}

	if (convert_str)
		text.value = convert_str;
	else
		text.value = str;
	text.altrep = NULL;
	sl.next = NULL;
	sl.data = &text;

	e_cal_component_set_description_list (comp, &sl);

	g_free (str);
	if (convert_str)
		g_free (convert_str);
}

static gchar *
set_organizer (ECalComponent *comp)
{
	EAccount *account;
	const gchar *str, *name;
	ECalComponentOrganizer organizer = {NULL, NULL, NULL, NULL};
	gchar *res;

	account = e_get_default_account ();
	if (!account)
		return NULL;

	str = e_account_get_string (account, E_ACCOUNT_ID_ADDRESS);
	name = e_account_get_string (account, E_ACCOUNT_ID_NAME);

	if (!str)
		return NULL;

	res = g_strconcat ("mailto:", str, NULL);
	organizer.value = res;
	organizer.cn = name;
	e_cal_component_set_organizer (comp, &organizer);

	return res;
}

static void
set_attachments (ECal *client, ECalComponent *comp, CamelMimeMessage *message)
{
	gint parts, i;
	GSList *list = NULL;
	const gchar *uid;
	const gchar *store_uri;
	gchar *store_dir;
	CamelDataWrapper *content;

	content = camel_medium_get_content_object ((CamelMedium *) message);
	if (!content || !CAMEL_IS_MULTIPART (content))
		return;

	parts = camel_multipart_get_number (CAMEL_MULTIPART (content));
	if (parts < 1)
		return;

	e_cal_component_get_uid (comp, &uid);
	store_uri = e_cal_get_local_attachment_store (client);
	if (!store_uri)
		return;
	store_dir = g_filename_from_uri (store_uri, NULL, NULL);

	for (i = 1; i < parts; i++)
	{
		gchar *filename, *path, *tmp;
		const gchar *orig_filename;
		CamelMimePart *mime_part;

		mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), i);

		orig_filename = camel_mime_part_get_filename (mime_part);
		if (!orig_filename)
			continue;

		tmp = clean_name ((const guchar *)orig_filename);
		filename = g_strdup_printf ("%s-%s", uid, tmp);
		path = g_build_filename (store_dir, filename, NULL);

		if (em_utils_save_part_to_file (NULL, path, mime_part))
		{
			gchar *uri;
			uri = g_filename_to_uri (path, NULL, NULL);
			list = g_slist_append (list, g_strdup (uri));
			g_free (uri);
		}

		g_free (tmp);
		g_free (filename);
		g_free (path);
	}

	g_free (store_dir);

	e_cal_component_set_attachment_list (comp, list);
}

struct _report_error
{
	gchar *format;
	gchar *param;
};

static gboolean
do_report_error (struct _report_error *err)
{
	if (err) {
		e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param);
		g_free (err->format);
		g_free (err->param);
		g_free (err);
	}

	return FALSE;
}

static void
report_error_idle (const gchar *format, const gchar *param)
{
	struct _report_error *err = g_new (struct _report_error, 1);

	err->format = g_strdup (format);
	err->param = g_strdup (param);

	g_usleep (250);
	g_idle_add ((GSourceFunc)do_report_error, err);
}

typedef struct {
	ECal *client;
	CamelFolder *folder;
	GPtrArray *uids;
	gchar *selected_text;
	gboolean with_attendees;
}AsyncData;

static gboolean
do_mail_to_event (AsyncData *data)
{
	ECal *client = data->client;
	CamelFolder *folder = data->folder;
	GPtrArray *uids = data->uids;
	GError *err = NULL;
	gboolean readonly = FALSE;

	/* open the task client */
	if (!e_cal_open (client, FALSE, &err)) {
		report_error_idle (_("Cannot open calendar. %s"), err ? err->message : _("Unknown error."));
	} else if (!e_cal_is_read_only (client, &readonly, &err) || readonly) {
		if (err)
			report_error_idle ("Check readonly failed. %s", err->message);
		else {
			switch (e_cal_get_source_type (client)) {
			case E_CAL_SOURCE_TYPE_EVENT:
				report_error_idle (_("Selected source is read only, thus cannot create event there. Select other source, please."), NULL);
				break;
			case E_CAL_SOURCE_TYPE_TODO:
				report_error_idle (_("Selected source is read only, thus cannot create task there. Select other source, please."), NULL);
				break;
			case E_CAL_SOURCE_TYPE_JOURNAL:
				report_error_idle (_("Selected source is read only, thus cannot create memo there. Select other source, please."), NULL);
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		}
	} else {
		gint i;
		ECalSourceType source_type = e_cal_get_source_type (client);
		ECalComponentDateTime dt, dt2;
		struct icaltimetype tt, tt2;

		/* set start day of the event as today, without time - easier than looking for a calendar's time zone */
		tt = icaltime_today ();
		dt.value = &tt;
		dt.tzid = NULL;

		tt2 = tt;
		icaltime_adjust (&tt2, 1, 0, 0, 0);
		dt2.value = &tt2;
		dt2.tzid = NULL;

		for (i = 0; i < (uids ? uids->len : 0); i++) {
			CamelMimeMessage *message;
			ECalComponent *comp;
			ECalComponentText text;
			icalproperty *icalprop;
			icalcomponent *icalcomp;

			/* retrieve the message from the CamelFolder */
			message = camel_folder_get_message (folder, g_ptr_array_index (uids, i), NULL);
			if (!message) {
				continue;
			}

			comp = e_cal_component_new ();

			switch (source_type) {
			case E_CAL_SOURCE_TYPE_EVENT:
				e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
				break;
			case E_CAL_SOURCE_TYPE_TODO:
				e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
				break;
			case E_CAL_SOURCE_TYPE_JOURNAL:
				e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
				break;
			default:
				g_assert_not_reached ();
				break;
			}

			e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message));
			e_cal_component_set_dtstart (comp, &dt);

			if (source_type == E_CAL_SOURCE_TYPE_EVENT) {
				/* make it an all-day event */
				e_cal_component_set_dtend (comp, &dt2);
			}

			/* set the summary */
			text.value = camel_mime_message_get_subject (message);
			text.altrep = NULL;
			e_cal_component_set_summary (comp, &text);

			/* set all fields */
			if (data->selected_text) {
				GSList sl;

				text.value = data->selected_text;
				text.altrep = NULL;
				sl.next = NULL;
				sl.data = &text;

				e_cal_component_set_description_list (comp, &sl);
			} else
				set_description (comp, message);

			if (data->with_attendees) {
				gchar *organizer;

				/* set actual user as organizer, to be able to change event's properties */
				organizer = set_organizer (comp);
				set_attendees (comp, message, organizer);
				g_free (organizer);
			}

			/* set attachment files */
			set_attachments (client, comp, message);

			icalcomp = e_cal_component_get_icalcomponent (comp);

			icalprop = icalproperty_new_x ("1");
			icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR");
			icalcomponent_add_property (icalcomp, icalprop);

			/* save the task to the selected source */
			if (!e_cal_create_object (client, icalcomp, NULL, &err)) {
				report_error_idle (_("Could not create object. %s"), err ? err->message : _("Unknown error"));

				if (err)
					g_error_free (err);
				err = NULL;
			}

			g_object_unref (comp);
		}
	}

	/* free memory */
	g_object_unref (data->client);
	g_ptr_array_free (data->uids, TRUE);
	g_free (data->selected_text);
	g_free (data);
	data = NULL;

	if (err)
		g_error_free (err);

	return TRUE;
}

static gboolean
text_contains_nonwhitespace (const gchar *text, gint len)
{
	const gchar *p;
	gunichar c = 0;

	if (!text || len<=0)
		return FALSE;

	p = text;

	while (p && p - text < len) {
		c = g_utf8_get_char (p);
		if (!c)
			break;

		if (!g_unichar_isspace (c))
			break;

		p = g_utf8_next_char (p);
	}

	return p - text < len - 1 && c != 0;
}

/* should be freed with g_free after done with it */
static gchar *
get_selected_text (EMailReader *reader)
{
	EMFormatHTMLDisplay *html_display;
	GtkHTML *html;
	gchar *text = NULL;
	gint len;

	html_display = e_mail_reader_get_html_display (reader);
	html = EM_FORMAT_HTML (html_display)->html;

	if (!gtk_html_command (html, "is-selection-active"))
		return NULL;

	text = gtk_html_get_selection_plain_text (html, &len);

	if (text == NULL || !text_contains_nonwhitespace (text, len)) {
		g_free (text);
		return NULL;
	}

	return text;
}

static void
mail_to_event (ECalSourceType source_type,
               gboolean with_attendees,
               EShellView *shell_view)
{
	EShellContent *shell_content;
	EMailReader *reader;
	MessageList *message_list;
	CamelFolder *folder;
	GPtrArray *selected;
	ESourceList *source_list = NULL;
	gboolean done = FALSE;
	GSList *groups, *p;
	ESource *source = NULL;
	GError *error = NULL;

	shell_content = e_shell_view_get_shell_content (shell_view);

	reader = E_MAIL_READER (shell_content);
	message_list = e_mail_reader_get_message_list (reader);
	selected = message_list_get_selected (message_list);
	folder = message_list->folder;

	if (!e_cal_get_sources (&source_list, source_type, &error)) {
		e_notice (NULL, GTK_MESSAGE_ERROR, _("Cannot get source list. %s"), error ? error->message : _("Unknown error."));

		if (error)
			g_error_free (error);

		return;
	}

	/* Check if there is only one writeable source, if so do not ask user to pick it */
	groups = e_source_list_peek_groups (source_list);
	for (p = groups; p != NULL && !done; p = p->next) {
		ESourceGroup *group = E_SOURCE_GROUP (p->data);
		GSList *sources, *q;

		sources = e_source_group_peek_sources (group);
		for (q = sources; q != NULL; q = q->next) {
			ESource *s = E_SOURCE (q->data);

			if (s && !e_source_get_readonly (s)) {
				if (source) {
					source = NULL;
					done = TRUE;
					break;
				}

				source = s;
			}
		}
	}

	if (!source) {
		GtkWidget *dialog;

		/* ask the user which tasks list to save to */
		dialog = e_source_selector_dialog_new (NULL, source_list);

		if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
			source = e_source_selector_dialog_peek_primary_selection (E_SOURCE_SELECTOR_DIALOG (dialog));

		gtk_widget_destroy (dialog);
	}

	if (source) {
		/* if a source has been selected, perform the mail2event operation */
		ECal *client = NULL;
		AsyncData *data = NULL;
		GThread *thread = NULL;

		client = auth_new_cal_from_source (source, source_type);
		if (!client) {
			gchar *uri = e_source_get_uri (source);

			e_notice (NULL, GTK_MESSAGE_ERROR, "Could not create the client: %s", uri);

			g_free (uri);
			g_object_unref (source_list);
			return;
		}

		/* Fill the elements in AsynData */
		data = g_new0 (AsyncData, 1);
		data->client = client;
		data->folder = folder;
		data->uids = selected;
		data->with_attendees = with_attendees;

		if (selected->len == 1)
			data->selected_text = get_selected_text (reader);
		else
			data->selected_text = NULL;

		thread = g_thread_create ((GThreadFunc) do_mail_to_event, data, FALSE, &error);
		if (!thread) {
			g_warning (G_STRLOC ": %s", error->message);
			g_error_free (error);
		}
	}

	g_object_unref (source_list);
}

static void
action_mail_convert_to_event_cb (GtkAction *action,
                                 EShellView *shell_view)
{
	mail_to_event (E_CAL_SOURCE_TYPE_EVENT, FALSE, shell_view);
}

static void
action_mail_convert_to_meeting_cb (GtkAction *action,
                                   EShellView *shell_view)
{
	mail_to_event (E_CAL_SOURCE_TYPE_EVENT, TRUE, shell_view);
}

static void
action_mail_convert_to_memo_cb (GtkAction *action,
                                EShellView *shell_view)
{
	mail_to_event (E_CAL_SOURCE_TYPE_JOURNAL, FALSE, shell_view);
}

static void
action_mail_convert_to_task_cb (GtkAction *action,
                                EShellView *shell_view)
{
	mail_to_event (E_CAL_SOURCE_TYPE_TODO, FALSE, shell_view);
}

static GtkActionEntry entries[] = {

	{ "mail-convert-to-event",
	  "appointment-new",
	  N_("Convert to an _Event"),
	  NULL,
	  N_("Convert the selected messages to an event"),
	  G_CALLBACK (action_mail_convert_to_event_cb) },

	{ "mail-convert-to-meeting",
	  "stock_new-meeting",
	  N_("Convert to a _Meeting"),
	  NULL,
	  N_("Convert the selected messages to a meeting"),
	  G_CALLBACK (action_mail_convert_to_meeting_cb) },

	{ "mail-convert-to-memo",
	  "stock_insert-note",
	  N_("Convert to a Mem_o"),
	  NULL,
	  N_("Convert the selected messages to a memo"),
	  G_CALLBACK (action_mail_convert_to_memo_cb) },

	{ "mail-convert-to-task",
	  "stock_todo",
	  N_("Convert to a _Task"),
	  NULL,
	  N_("Convert the selected messages to a task"),
	  G_CALLBACK (action_mail_convert_to_task_cb) }
};

static EPopupActionEntry popup_entries[] = {

	{ "mail-popup-convert-to-event",
	  NULL,
	  "mail-convert-to-event" },

	{ "mail-popup-convert-to-meeting",
	  NULL,
	  "mail-convert-to-meeting" },

	{ "mail-popup-convert-to-memo",
	  NULL,
	  "mail-convert-to-memo" },

	{ "mail-popup-convert-to-task",
	  NULL,
	  "mail-convert-to-task" }
};

static void
update_actions_cb (EShellView *shell_view)
{
	EShellContent *shell_content;
	EShellWindow *shell_window;
	GtkAction *action;
	gboolean sensitive;
	guint32 state;

	shell_content = e_shell_view_get_shell_content (shell_view);
	shell_window = e_shell_view_get_shell_window (shell_view);

	state = e_mail_reader_check_state (E_MAIL_READER (shell_content));

	sensitive =
		(state & E_MAIL_READER_SELECTION_SINGLE) ||
		(state & E_MAIL_READER_SELECTION_MULTIPLE);

	action = E_SHELL_WINDOW_ACTION_CONVERT_TO_EVENT (shell_window);
	gtk_action_set_sensitive (action, sensitive);

	action = E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING (shell_window);
	gtk_action_set_sensitive (action, sensitive);

	action = E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO (shell_window);
	gtk_action_set_sensitive (action, sensitive);

	action = E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK (shell_window);
	gtk_action_set_sensitive (action, sensitive);
}

gboolean
e_plugin_ui_init (GtkUIManager *ui_manager,
                  EShellView *shell_view)
{
	EShellWindow *shell_window;
	GtkActionGroup *action_group;

	shell_window = e_shell_view_get_shell_window (shell_view);
	action_group = e_shell_window_get_action_group (shell_window, "mail");

	gtk_action_group_add_actions (
		action_group, entries,
		G_N_ELEMENTS (entries), shell_view);
	e_action_group_add_popup_actions (
		action_group, popup_entries,
		G_N_ELEMENTS (popup_entries));

	g_signal_connect (
		shell_view, "update-actions",
		G_CALLBACK (update_actions_cb), NULL);

	return TRUE;
}