/*
 * camel-filter-output-stream.c
 *
 * This library 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.
 *
 * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * SECTION: camel-filter-output-stream
 * @short_description: Filtered output stream
 * @include: camel/camel.h
 * @see_also: #GOutputStream, #CamelMimeFilter
 *
 * #CamelFilterOutputStream is similar to #GConverterOutputStream, except it
 * operates on a #CamelMimeFilter instead of a #GConverter.
 *
 * This class is meant to be a temporary solution until all of Camel's MIME
 * filters are ported to the #GConverter interface.
 **/

#include "camel-filter-output-stream.h"

#include <string.h>

#define READ_PAD (128)		/* bytes padded before buffer */
#define READ_SIZE (4096)

struct _CamelFilterOutputStreamPrivate {
	CamelMimeFilter *filter;
};

enum {
	PROP_0,
	PROP_FILTER
};

G_DEFINE_TYPE_WITH_PRIVATE (
	CamelFilterOutputStream,
	camel_filter_output_stream,
	G_TYPE_FILTER_OUTPUT_STREAM)

static void
filter_output_stream_set_filter (CamelFilterOutputStream *filter_stream,
                                 CamelMimeFilter *filter)
{
	g_return_if_fail (CAMEL_IS_MIME_FILTER (filter));
	g_return_if_fail (filter_stream->priv->filter == NULL);

	filter_stream->priv->filter = g_object_ref (filter);
}

static void
filter_output_stream_set_property (GObject *object,
                                   guint property_id,
                                   const GValue *value,
                                   GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_FILTER:
			filter_output_stream_set_filter (
				CAMEL_FILTER_OUTPUT_STREAM (object),
				g_value_get_object (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
filter_output_stream_get_property (GObject *object,
                                   guint property_id,
                                   GValue *value,
                                   GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_FILTER:
			g_value_set_object (
				value,
				camel_filter_output_stream_get_filter (
				CAMEL_FILTER_OUTPUT_STREAM (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
filter_output_stream_dispose (GObject *object)
{
	CamelFilterOutputStreamPrivate *priv;

	priv = CAMEL_FILTER_OUTPUT_STREAM (object)->priv;

	/* XXX GOutputStream calls flush() one last time during
	 *     dispose(), so chain up before clearing our filter. */
	G_OBJECT_CLASS (camel_filter_output_stream_parent_class)->
		dispose (object);

	g_clear_object (&priv->filter);
}

static gssize
filter_output_stream_write (GOutputStream *stream,
                            gconstpointer buffer,
                            gsize count,
                            GCancellable *cancellable,
                            GError **error)
{
	CamelMimeFilter *filter;
	GOutputStream *base_stream;
	gchar real_buffer[READ_SIZE + READ_PAD];
	const gchar *input_buffer = buffer;
	gsize bytes_left = count;

	filter = camel_filter_output_stream_get_filter (
		CAMEL_FILTER_OUTPUT_STREAM (stream));
	base_stream = g_filter_output_stream_get_base_stream (
		G_FILTER_OUTPUT_STREAM (stream));

	while (bytes_left > 0) {
		gsize length;
		gsize presize;
		gchar *bufptr;
		gboolean success;

		bufptr = real_buffer + READ_PAD;
		length = MIN (READ_SIZE, bytes_left);
		memcpy (bufptr, input_buffer, length);
		input_buffer += length;
		bytes_left -= length;

		presize = READ_PAD;

		camel_mime_filter_filter (
			filter, bufptr, length, presize,
			&bufptr, &length, &presize);

		/* XXX The bytes_written argument can be NULL,
		 *     even though the API docs don't say so. */
		success = g_output_stream_write_all (
			base_stream, bufptr, length,
			NULL, cancellable, error);
		if (!success)
			return -1;
	}

	return count;
}

static gboolean
filter_output_stream_flush (GOutputStream *stream,
                            GCancellable *cancellable,
                            GError **error)
{
	CamelMimeFilter *filter;
	GOutputStream *base_stream;
	gchar *bufptr = (gchar *) "";
	gsize length = 0;
	gsize presize = 0;
	gboolean success = TRUE;

	filter = camel_filter_output_stream_get_filter (
		CAMEL_FILTER_OUTPUT_STREAM (stream));
	base_stream = g_filter_output_stream_get_base_stream (
		G_FILTER_OUTPUT_STREAM (stream));

	camel_mime_filter_complete (
		filter, bufptr, length, presize,
		&bufptr, &length, &presize);

	if (length > 0) {
		/* XXX The bytes_written argument can be NULL,
		 *     even though the API docs don't say so. */
		success = g_output_stream_write_all (
			base_stream, bufptr, length,
			NULL, cancellable, error);
	}

	if (success) {
		success = g_output_stream_flush (
			base_stream, cancellable, error);
	}

	return success;
}

static void
camel_filter_output_stream_class_init (CamelFilterOutputStreamClass *class)
{
	GObjectClass *object_class;
	GOutputStreamClass *stream_class;

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = filter_output_stream_set_property;
	object_class->get_property = filter_output_stream_get_property;
	object_class->dispose = filter_output_stream_dispose;

	stream_class = G_OUTPUT_STREAM_CLASS (class);
	stream_class->write_fn = filter_output_stream_write;
	stream_class->flush = filter_output_stream_flush;

	g_object_class_install_property (
		object_class,
		PROP_FILTER,
		g_param_spec_object (
			"filter",
			"Filter",
			"The MIME filter object",
			CAMEL_TYPE_MIME_FILTER,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));
}

static void
camel_filter_output_stream_init (CamelFilterOutputStream *filter_stream)
{
	filter_stream->priv = camel_filter_output_stream_get_instance_private (filter_stream);
}

/**
 * camel_filter_output_stream_new:
 * @base_stream: a #GOutputStream
 * @filter: a #CamelMimeFilter
 *
 * Creates a new filtered output stream for the @base_stream.
 *
 * Returns: a new #GOutputStream
 *
 * Since: 3.12
 **/
GOutputStream *
camel_filter_output_stream_new (GOutputStream *base_stream,
                                CamelMimeFilter *filter)
{
	g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL);
	g_return_val_if_fail (CAMEL_IS_MIME_FILTER (filter), NULL);

	return g_object_new (
		CAMEL_TYPE_FILTER_OUTPUT_STREAM,
		"base-stream", base_stream,
		"filter", filter, NULL);
}

/**
 * camel_filter_output_stream_get_filter:
 * @filter_stream: a #CamelFilterOutputStream
 *
 * Gets the #CamelMimeFilter that is used by @filter_stream.
 *
 * Returns: (transfer none): a #CamelMimeFilter
 *
 * Since: 3.12
 **/
CamelMimeFilter *
camel_filter_output_stream_get_filter (CamelFilterOutputStream *filter_stream)
{
	g_return_val_if_fail (
		CAMEL_IS_FILTER_OUTPUT_STREAM (filter_stream), NULL);

	return filter_stream->priv->filter;
}

