/*
 *  Copyright (C) 2000 Ximian Inc.
 *
 *  Authors: Michael Zucchi <notzed@ximian.com>
 *
 * 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.
 */

#include <string.h>
#include "camel-mime-filter.h"

/*#define MALLOC_CHECK */ /* for some malloc checking, requires mcheck enabled */

/* only suitable for glibc */
#ifdef MALLOC_CHECK
#include <mcheck.h>
#endif

struct _CamelMimeFilterPrivate {
	char *inbuf;
	size_t inlen;
};

#define PRE_HEAD (64)
#define BACK_HEAD (64)
#define _PRIVATE(o) (((CamelMimeFilter *)(o))->priv)
#define FCLASS(o) ((CamelMimeFilterClass *)(CAMEL_OBJECT_GET_CLASS(o)))

static CamelObjectClass *camel_mime_filter_parent;

static void complete (CamelMimeFilter *mf, char *in, size_t len, 
		      size_t prespace, char **out, size_t *outlen, 
		      size_t *outprespace);

static void
camel_mime_filter_class_init (CamelMimeFilterClass *klass)
{
	camel_mime_filter_parent = camel_type_get_global_classfuncs (camel_object_get_type ());

	klass->complete = complete;
}

static void
camel_mime_filter_init (CamelMimeFilter *obj)
{
	obj->outreal = NULL;
	obj->outbuf = NULL;
	obj->outsize = 0;

	obj->backbuf = NULL;
	obj->backsize = 0;
	obj->backlen = 0;

	_PRIVATE(obj) = g_malloc0(sizeof(*obj->priv));
}

static void
camel_mime_filter_finalize(CamelObject *o)
{
	CamelMimeFilter *f = (CamelMimeFilter *)o;
	struct _CamelMimeFilterPrivate *p = _PRIVATE(f);

	g_free(f->outreal);
	g_free(f->backbuf);
	g_free(p->inbuf);
	g_free(p);
}

CamelType
camel_mime_filter_get_type (void)
{
	static CamelType camel_mime_filter_type = CAMEL_INVALID_TYPE;
	
	if (camel_mime_filter_type == CAMEL_INVALID_TYPE) {
		camel_mime_filter_type = camel_type_register (CAMEL_OBJECT_TYPE, "CamelMimeFilter",
							      sizeof (CamelMimeFilter),
							      sizeof (CamelMimeFilterClass),
							      (CamelObjectClassInitFunc) camel_mime_filter_class_init,
							      NULL,
							      (CamelObjectInitFunc) camel_mime_filter_init,
							      (CamelObjectFinalizeFunc) camel_mime_filter_finalize);
	}
	
	return camel_mime_filter_type;
}

static void
complete(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace)
{
	/* default - do nothing */
}

/**
 * camel_mime_filter_new:
 *
 * Create a new CamelMimeFilter object.
 * 
 * Return value: A new CamelMimeFilter widget.
 **/
CamelMimeFilter *
camel_mime_filter_new (void)
{
	CamelMimeFilter *new = CAMEL_MIME_FILTER ( camel_object_new (camel_mime_filter_get_type ()));
	return new;
}

#ifdef MALLOC_CHECK
static void
checkmem(void *p)
{
	if (p) {
		int status = mprobe(p);

		switch (status) {
		case MCHECK_HEAD:
			printf("Memory underrun at %p\n", p);
			abort();
		case MCHECK_TAIL:
			printf("Memory overrun at %p\n", p);
			abort();
		case MCHECK_FREE:
			printf("Double free %p\n", p);
			abort();
		}
	}
}
#endif

static void filter_run(CamelMimeFilter *f,
		       char *in, size_t len, size_t prespace,
		       char **out, size_t *outlen, size_t *outprespace,
		       void (*filterfunc)(CamelMimeFilter *f,
					  char *in, size_t len, size_t prespace,
					  char **out, size_t *outlen, size_t *outprespace))
{
	struct _CamelMimeFilterPrivate *p;

#ifdef MALLOC_CHECK
	checkmem(f->outreal);
	checkmem(f->backbuf);
#endif
	/*
	  here we take a performance hit, if the input buffer doesn't
	  have the pre-space required.  We make a buffer that does ...
	*/
	if (prespace < f->backlen) {
		int newlen = len+prespace+f->backlen;
		p = _PRIVATE(f);
		if (p->inlen < newlen) {
			/* NOTE: g_realloc copies data, we dont need that (slower) */
			g_free(p->inbuf);
			p->inbuf = g_malloc(newlen+PRE_HEAD);
			p->inlen = newlen+PRE_HEAD;
		}
		/* copy to end of structure */
		memcpy(p->inbuf+p->inlen - len, in, len);
		in = p->inbuf+p->inlen - len;
		prespace = p->inlen - len;
	}

#ifdef MALLOC_CHECK
	checkmem(f->outreal);
	checkmem(f->backbuf);
#endif

	/* preload any backed up data */
	if (f->backlen > 0) {
		memcpy(in-f->backlen, f->backbuf, f->backlen);
		in -= f->backlen;
		len += f->backlen;
		prespace -= f->backlen;
		f->backlen = 0;
	}
	
	filterfunc(f, in, len, prespace, out, outlen, outprespace);

#ifdef MALLOC_CHECK
	checkmem(f->outreal);
	checkmem(f->backbuf);
#endif

}

void camel_mime_filter_filter(CamelMimeFilter *f,
			      char *in, size_t len, size_t prespace,
			      char **out, size_t *outlen, size_t *outprespace)
{
	if (FCLASS(f)->filter)
		filter_run(f, in, len, prespace, out, outlen, outprespace, FCLASS(f)->filter);
	else
		g_error("Filter function unplmenented in class");
}

void camel_mime_filter_complete(CamelMimeFilter *f,
				char *in, size_t len, size_t prespace,
				char **out, size_t *outlen, size_t *outprespace)
{
	if (FCLASS(f)->complete)
		filter_run(f, in, len, prespace, out, outlen, outprespace, FCLASS(f)->complete);
}

void camel_mime_filter_reset(CamelMimeFilter *f)
{
	if (FCLASS(f)->reset) {
		FCLASS(f)->reset(f);
	}

	/* could free some buffers, if they are really big? */
	f->backlen = 0;
}

/* sets number of bytes backed up on the input, new calls replace previous ones */
void camel_mime_filter_backup(CamelMimeFilter *f, const char *data, size_t length)
{
	if (f->backsize < length) {
		/* g_realloc copies data, unnecessary overhead */
		g_free(f->backbuf);
		f->backbuf = g_malloc(length+BACK_HEAD);
		f->backsize = length+BACK_HEAD;
	}
	f->backlen = length;
	memcpy(f->backbuf, data, length);
}

/* ensure this much size available for filter output (if required) */
void camel_mime_filter_set_size(CamelMimeFilter *f, size_t size, int keep)
{
	if (f->outsize < size) {
		int offset = f->outptr - f->outreal;
		if (keep) {
			f->outreal = g_realloc(f->outreal, size + PRE_HEAD*4);
		} else {
			g_free(f->outreal);
			f->outreal = g_malloc(size + PRE_HEAD*4);
		}
		f->outptr = f->outreal + offset;
		f->outbuf = f->outreal + PRE_HEAD*4;
		f->outsize = size;
		/* this could be offset from the end of the structure, but 
		   this should be good enough */
		f->outpre = PRE_HEAD*4;
	}
}

