/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-mail-mime-builder.h
 *
 *  Wed Jan 26 11:42:23 2011
 *  Copyright  2011  Christian Hilberg
 *  <hilberg@kernelconcepts.de>
 ****************************************************************************/

/*
 * 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.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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
 */

/*----------------------------------------------------------------------------*/

#include <config.h>

#include <string.h>

#include <glib/gi18n-lib.h>

#include "kolab-util-backend.h"
#include "kolab-mail-mime-builder.h"

/*----------------------------------------------------------------------------*/

#define KOLAB_MAIL_MIME_PARTS_MIN 2
#define KOLAB_MAIL_MIME_PART_DUMMY_NAME "evolution-kolab-dummy-name"

#define KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT	  \
	"Dies ist ein Kolab Groupware-Objekt. \
Um dieses Objekt anzeigen zu koennen, brauchen Sie ein E-Mail Programm, \
das das Kolab Groupware-Format versteht. \
Eine Liste solcher E-Mail-Programme finden Sie hier: \
http://www.kolab.org/kolab2-clients.html \n\
 \n\
=2D---------------------------------------------------- \n\
 \n\
This is a Kolab Groupware object. \
To view this object you will need an email client that can understand \
the Kolab Groupware format. \
For a list of such email clients please visit \
http://www.kolab.org/kolab2-clients.html \n"

#define KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT_TYPE "text/plain"

/*----------------------------------------------------------------------------*/

typedef struct _KolabMailMimeBuilderPrivate KolabMailMimeBuilderPrivate;
struct _KolabMailMimeBuilderPrivate
{
	KolabSettingsHandler *ksettings;
	gboolean is_up;
};

#define KOLAB_MAIL_MIME_BUILDER_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KOLAB_TYPE_MAIL_MIME_BUILDER, KolabMailMimeBuilderPrivate))

G_DEFINE_TYPE (KolabMailMimeBuilder, kolab_mail_mime_builder, G_TYPE_OBJECT)

/*----------------------------------------------------------------------------*/
/* object/class init */

static void
kolab_mail_mime_builder_init (KolabMailMimeBuilder *object)
{
	KolabMailMimeBuilder *self = NULL;
	KolabMailMimeBuilderPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (object));

	self = object;
	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	priv->ksettings = NULL;
	priv->is_up = FALSE;
}

static void
kolab_mail_mime_builder_dispose (GObject *object)
{
	KolabMailMimeBuilder *self = NULL;
	KolabMailMimeBuilderPrivate *priv = NULL;

	self = KOLAB_MAIL_MIME_BUILDER (object);
	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	if (priv->ksettings != NULL) /* ref'd in configure() */
		g_object_unref (priv->ksettings);

	priv->ksettings = NULL;

	G_OBJECT_CLASS (kolab_mail_mime_builder_parent_class)->dispose (object);
}

static void
kolab_mail_mime_builder_finalize (GObject *object)
{
	/* KolabMailMimeBuilder *self = NULL; */
	/* KolabMailMimeBuilderPrivate *priv = NULL; */

	/* self = KOLAB_MAIL_MIME_BUILDER (object); */
	/* priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self); */

	/* TODO: Add deinitalization code here */

	G_OBJECT_CLASS (kolab_mail_mime_builder_parent_class)->finalize (object);
}

static void
kolab_mail_mime_builder_class_init (KolabMailMimeBuilderClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass* parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabMailMimeBuilderPrivate));

	object_class->dispose = kolab_mail_mime_builder_dispose;
	object_class->finalize = kolab_mail_mime_builder_finalize;
}

/*----------------------------------------------------------------------------*/
/* object config/status */

gboolean
kolab_mail_mime_builder_configure  (KolabMailMimeBuilder *self,
                                    KolabSettingsHandler *ksettings,
                                    GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	if (priv->ksettings != NULL)
		return TRUE;

	g_object_ref (ksettings); /* unref'd in dispose() */
	priv->ksettings = ksettings;

	/* TODO implement me */

	return TRUE;
}

gboolean
kolab_mail_mime_builder_bringup (KolabMailMimeBuilder *self,
                                 GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	if (priv->is_up == TRUE)
		return TRUE;

	/* TODO implement me */

	priv->is_up = TRUE;
	return TRUE;
}

gboolean
kolab_mail_mime_builder_shutdown (KolabMailMimeBuilder *self,
                                  GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	if (priv->is_up == FALSE)
		return TRUE;

	/* TODO implement me */

	priv->is_up = FALSE;
	return TRUE;
}

/*----------------------------------------------------------------------------*/

Kolab_conv_mail*
kolab_mail_mime_builder_conv_new_from_camel (KolabMailMimeBuilder *self,
                                             const CamelMimeMessage *message,
                                             GCancellable *cancellable,
                                             GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;
	Kolab_conv_mail *kconvmail = NULL;
	guint kconvmail_len = 0;
	CamelDataWrapper *data_wrapper = NULL;
	CamelMultipart *multipart = NULL;
	guint nparts = 0;
	guint ii = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_assert (CAMEL_IS_MIME_MESSAGE (message));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (message));
	if (data_wrapper == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_CAMEL,
		             _("Cannot unpack Camel mime message"));
		return NULL;
	}

	if (! CAMEL_IS_MULTIPART (data_wrapper)) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_CAMEL,
		             _("Mail message is not a multipart message, ignoring"));
		/* need to unref data wrapper? */
		return NULL;
	}

	multipart = CAMEL_MULTIPART (data_wrapper);

	nparts = camel_multipart_get_number (multipart);
	g_debug ("%s: number of parts: %i", __func__, nparts);

	if (nparts < KOLAB_MAIL_MIME_PARTS_MIN) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_KOLAB,
		             _("Mail message does not have the expected minimum of %i mime parts, ignoring"),
		             KOLAB_MAIL_MIME_PARTS_MIN);
		return NULL;
	}

	/* create new Kolab_conv_mail */
	kconvmail_len = nparts - 1;
	kconvmail = g_new0 (Kolab_conv_mail, 1);
	kconvmail->length = kconvmail_len;
	kconvmail->mail_parts = g_new0 (Kolab_conv_mail_part, kconvmail_len);

	/* we're ignoring the zeroeth mime part (is just info text) */
	for (ii = 1; ii < nparts; ii++) {
		Kolab_conv_mail_part *kconvmailpart = NULL;
		CamelMimePart *mimepart = NULL;
		CamelContentType *content_type = NULL;
		CamelDataWrapper *mpart_data_wrapper = NULL;
		CamelStreamMem *memstream = NULL;
		GByteArray *buffer = NULL;
		const gchar *name = NULL;
		gchar *content_type_str = NULL;
		gssize stream_bytes = 0;
		mimepart = camel_multipart_get_part (multipart, ii);

		content_type = camel_mime_part_get_content_type (mimepart);
		content_type_str = camel_content_type_simple (content_type);
		name = camel_mime_part_get_filename (mimepart);
		mpart_data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (mimepart));

		kconvmailpart = &(kconvmail->mail_parts[ii - 1]);

		/* content checks */
		/* TODO more detail in warning messages */

		if (content_type_str == NULL) {
			g_set_error (&tmp_err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_CAMEL,
			             _("Mail message mime part has no content type set, skipping"));
			goto mime_part_cleanup;
		}
		if (name == NULL) {
			g_warning ("%s: NULL content name, adding dummy",
			           __func__);
			name = KOLAB_MAIL_MIME_PART_DUMMY_NAME;
		}
		if (mpart_data_wrapper == NULL) {
			g_set_error (&tmp_err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_CAMEL,
			             _("Could not get Camel data wrapper for mime part, skipping"));
			goto mime_part_cleanup;
		}

		memstream = CAMEL_STREAM_MEM (camel_stream_mem_new ());
		buffer = g_byte_array_new ();
		camel_stream_mem_set_byte_array (memstream, buffer);
		stream_bytes = camel_data_wrapper_decode_to_stream_sync (mpart_data_wrapper,
		                                                         CAMEL_STREAM (memstream),
		                                                         cancellable,
		                                                         &tmp_err);
		if (stream_bytes < 0)
			goto mime_part_cleanup;

		kconvmailpart->name = g_strdup (name);
		kconvmailpart->mime_type = g_strdup (content_type_str);
		kconvmailpart->length = buffer->len;

		/* steal the GByteArray data */
		kconvmailpart->data = (gchar*) g_byte_array_free (buffer,
		                                                  FALSE);
		buffer = NULL;

		g_debug ("%s: part (%i) type (%s) name (%s) size (%i)",
		         __func__, ii, content_type_str, name, kconvmailpart->length);

		goto mime_part_next;

	mime_part_cleanup:
		kconvmailpart->name = NULL;
		kconvmailpart->mime_type = NULL;
		kconvmailpart->length = 0;
		kconvmailpart->data = NULL;
	mime_part_next:
		if (content_type_str != NULL)
			g_free (content_type_str);
		if (memstream != NULL)
			g_object_unref (memstream);
		if (buffer != NULL)
			g_byte_array_free (buffer, TRUE);

		/* FIXME free mimepart ? */
		/* FIXME free content_type ? */

		if (tmp_err != NULL)
			break;
	}

	/* need to unref data wrapper? */

	/* if anything went wrong, free all mem */
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		g_free (kconvmail->mail_parts);
		g_free (kconvmail);
		return NULL;
	}

	return kconvmail;
}

static CamelMimePart*
kolab_mail_mime_builder_new_zeroth_mime_part (KolabMailMimeBuilder *self)
{
	KolabMailMimeBuilderPrivate *priv = NULL;
	CamelMimePart *zeroth_part = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	zeroth_part = camel_mime_part_new ();

	camel_mime_part_set_content (zeroth_part,
	                             KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT,
	                             strlen (KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT),
	                             KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT_TYPE);
	camel_mime_part_set_content_type (zeroth_part,
	                                  KOLAB_MAIL_MIME_PART_ZEROTH_CONTENT_TYPE);
	camel_mime_part_set_encoding (zeroth_part,
	                              CAMEL_TRANSFER_ENCODING_7BIT);

	return zeroth_part;
}

CamelMimeMessage*
kolab_mail_mime_builder_camel_new_from_conv (KolabMailMimeBuilder *self,
                                             const Kolab_conv_mail *kconvmail,
                                             GCancellable *cancellable,
                                             GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;
	CamelMimeMessage *message = NULL;
	CamelMultipart *multipart = NULL;
	CamelMimePart *zeroth_part = NULL;
	gchar *msg_mimetype = NULL;
	guint ii = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_assert (kconvmail != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* sanity checks */
	if (kconvmail->length == 0)
		return NULL;
	if (kconvmail->mail_parts == NULL)
		return NULL;

	/* create new zeroth part */
	zeroth_part = kolab_mail_mime_builder_new_zeroth_mime_part (self);

	/* create multipart, add zeroth part */
	multipart = camel_multipart_new ();
	camel_multipart_set_boundary (multipart, NULL);
	camel_multipart_add_part (multipart, zeroth_part);

	/* iterate over kconvmail parts */
	for (ii = 0; ii < kconvmail->length; ii++) {
		Kolab_conv_mail_part *kconvmailpart = NULL;
		CamelMimePart *mimepart = NULL;
		CamelStreamMem *memstream = NULL;
		CamelDataWrapper *data_wrapper = NULL;
		gssize stream_bytes = 0;
		gint rval = 0;
		gboolean ok = TRUE;

		kconvmailpart = &(kconvmail->mail_parts[ii]);

		/* by any chance ... */
		if (kconvmailpart == NULL) {
			g_warning ("%s: NULL kconvmailpart", __func__);
			ok = FALSE;
		}
		if (ok && kconvmailpart->name == NULL) {
			g_warning ("%s: NULL kconvmailpart->name", __func__);
			ok = FALSE;
		}
		if (ok && kconvmailpart->mime_type == NULL) {
			g_warning ("%s: NULL kconvmailpart->mime_type", __func__);
			ok = FALSE;
		}
		if (ok && kconvmailpart->length == 0) {
			g_warning ("%s: zero kconvmailpart->length", __func__);
			ok = FALSE;
		}
		if (ok && kconvmailpart->data == NULL) {
			g_warning ("%s: NULL kconvmailpart->data", __func__);
			ok = FALSE;
		}

		if (! ok) {
			/* should never happen */
			g_set_error (&tmp_err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_INTERNAL,
			             _("Internal inconsistency detected: Invalid mail part after Kolab data conversion, no Camel message created"));
			goto mime_part_skip;
		}

		memstream = CAMEL_STREAM_MEM (camel_stream_mem_new ());
		stream_bytes = camel_stream_write (CAMEL_STREAM (memstream),
		                                   kconvmailpart->data,
		                                   kconvmailpart->length,
		                                   cancellable,
		                                   &tmp_err);
		if (stream_bytes < 0)
			goto mime_part_skip;

		data_wrapper = camel_data_wrapper_new ();
		rval = camel_data_wrapper_construct_from_stream_sync (data_wrapper,
		                                                      CAMEL_STREAM (memstream),
		                                                      cancellable,
		                                                      &tmp_err);
		if ((rval < 0) || (! CAMEL_IS_DATA_WRAPPER (data_wrapper)))
			goto mime_part_skip;

		/* create new mimepart */
		mimepart = camel_mime_part_new ();
		camel_medium_set_content (CAMEL_MEDIUM (mimepart),
		                          data_wrapper);

		camel_mime_part_set_filename (mimepart,
		                              kconvmailpart->name);

		camel_mime_part_set_content_type (mimepart,
		                                  kconvmailpart->mime_type);

		if (ii == 0) {
			camel_mime_part_set_encoding (mimepart,
			                              CAMEL_TRANSFER_ENCODING_7BIT);
			if (msg_mimetype != NULL)
				g_free (msg_mimetype);
			msg_mimetype = g_strdup (kconvmailpart->mime_type);
		} else {
			camel_mime_part_set_encoding (mimepart,
			                              CAMEL_TRANSFER_ENCODING_8BIT);
		}

		camel_multipart_add_part (multipart, mimepart);

	mime_part_skip:
		if (memstream != NULL)
			g_object_unref (memstream);
		if (tmp_err != NULL)
			break;
	}

	if (tmp_err != NULL) {
		/* clean up everything */
		if (msg_mimetype != NULL)
			g_free (msg_mimetype);
		g_object_unref (multipart);
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	message = camel_mime_message_new ();
	camel_medium_set_content (CAMEL_MEDIUM (message),
	                          CAMEL_DATA_WRAPPER (multipart));
	camel_mime_message_encode_8bit_parts (message);
	if (msg_mimetype != NULL) {
		camel_medium_set_header (CAMEL_MEDIUM (message),
		                         "X-Kolab-Type",
		                         msg_mimetype);
		g_free (msg_mimetype);
	}

	return message;
}

gboolean
kolab_mail_mime_builder_camel_set_header (KolabMailMimeBuilder *self,
                                          CamelMimeMessage *message,
                                          const KolabMailMimeBuilderHeaderInfo *headerinfo,
                                          CamelMimeMessage *orig_message,
                                          GError **err)
{
	KolabMailMimeBuilderPrivate *priv = NULL;
	CamelInternetAddress *cia = NULL;

	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (self));
	g_assert (CAMEL_IS_MIME_MESSAGE (message));
	g_assert (headerinfo != NULL);
	if (orig_message != NULL)
		g_assert (CAMEL_IS_MIME_MESSAGE (orig_message));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_MIME_BUILDER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	if (orig_message != NULL) {
		camel_mime_message_set_from (message,
		                             camel_mime_message_get_from (orig_message));
		camel_mime_message_set_message_id (message,
		                                   camel_mime_message_get_message_id (orig_message));
		camel_mime_message_set_reply_to (message,
		                                 camel_mime_message_get_reply_to (orig_message));
		camel_mime_message_set_subject (message,
		                                camel_mime_message_get_subject (orig_message));
		/* FIXME need to copy more here ? */
	}

	camel_mime_message_set_subject (message, headerinfo->kolab_uid);

	cia = camel_internet_address_new ();
	camel_internet_address_add (cia, headerinfo->from_name, headerinfo->from_addr);
	camel_mime_message_set_from (message, cia);
	g_object_unref (cia);

	/* TODO more here if KolabMailMimeBuilderHeaderInfo is extended */

	return TRUE;
}

/*----------------------------------------------------------------------------*/
