/*	$Id: example.c,v 1.4 2010/03/09 12:29:27 kristaps Exp $ */
/*
 * Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "mmail.h"

struct	mmbuf {
	char	*buf;
	size_t	 bufsz;
};

static	const char	*to = "\"Charlie Rööt\" <root@localhost>";
static	const char	*bod = "bÄ“das kļuvuÅ¡as par ikdienišķu svÄrku";

static	void	  usage(void);
static	void	  mmp_err(enum mmerr, const struct mmp *, void *);
static	int	  mmp_warn(enum mmerr, const struct mmp *, void *);
static	int	  mm_stdout(void *, const char *, ...);
static	int	  mm_filter(void *, const char *, size_t,
			const char **, size_t *);


/*
 * Simple example: reads in an e-mail file, parses/validates it, adds
 * some arbitrary (encoded) headers and body, and does some output
 * filtering for the sake of example.
 */
int
main(int argc, char *argv[])
{
	int		 fd, rc;
	struct mmail	*mm;
	ssize_t		 ssz, i;
	char		 buf[BUFSIZ];
	struct mmp	*mp;
	enum mmerr	 merr;
	struct mmwriter  w;
	struct mmbuf	 mb;
	extern int	 optind;
	extern char	*__progname;

	if (-1 != getopt(argc, argv, "")) {
		usage();
		return(EXIT_FAILURE);
	}

	rc = 1;
	argc -= optind;
	argv += optind;

	if (strcmp(*argv, "-"))
		fd = open(*argv, O_RDONLY, 0);
	else
		fd = STDIN_FILENO;

	if (-1 == fd) {
		perror(*argv);
		return(EXIT_FAILURE);
	}

	/* We use UTF-8 encoding! */

	mp = mmp_alloc("UTF-8", mmp_err, mmp_warn, *argv);
	if (NULL == mp) {
		perror("malloc");
		return(EXIT_FAILURE);
	}

	memset(&mb, 0, sizeof(struct mmbuf));
	memset(&w, 0, sizeof(struct mmwriter));
	w.print = mm_stdout;
	w.filter = mm_filter;
	w.arg = &mb;

	/* Read the file in blocks. */

	for ( ;; ) {
		ssz = read(fd, buf, BUFSIZ);
		if (-1 == ssz) {
			perror(*argv);
			goto out;
		} else if (0 == ssz)
			break;

		for (i = 0; i < ssz; i++) {
			if (mmp_char(mp, buf[(int)i]))
				continue;
			goto out;
		}
	}

	if ( ! mmp_end(mp))
		goto out;

	if (STDIN_FILENO != fd && -1 == close(fd)) {
		perror(*argv);
		fd = -1;
		goto out;
	}

	fd = -1;

	/* Add a binary "To" address header. */

	mm = mmp_mmail(mp);
	assert(mm);
	merr = mmail_addhead(mm, MMHEAD_TO, to, strlen(to));
	if (MMAIL_OK != merr) {
		fprintf(stderr, "%s: %s\n", __progname,
				mmail_errstring(merr));
		goto out;
	}

	merr = mmail_addbody(mm, bod, strlen(bod));
	if (MMAIL_OK != merr) {
		fprintf(stderr, "%s: %s\n", __progname,
				mmail_errstring(merr));
		goto out;
	}

	/* Serialise to stdout! */

	if ( ! mmail_write(mm, &w))
		goto out;

	rc = 1;
out:
	if (-1 != fd)
		close(fd);
	if (mp)
		mmp_free(mp);
	if (mb.buf)
		free(mb.buf);

	return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
}


static void
usage(void)
{
	extern char	*__progname;

	fprintf(stderr, "usage: %s file\n", __progname);
}


static int
mmp_warn(enum mmerr t, const struct mmp *p, void *arg)
{
	char		*fn;

	assert(arg);
	fn = (char *)arg;

	fprintf(stderr, "%s:%zu:%zu: warning: %s\n", fn, mmp_line(p),
			mmp_col(p), mmail_errstring(t));

	return(1);
}


static void
mmp_err(enum mmerr t, const struct mmp *p, void *arg)
{
	char		*fn;

	assert(arg);
	fn = (char *)arg;

	fprintf(stderr, "%s:%zu:%zu: error: %s\n", fn, mmp_line(p),
			mmp_col(p), mmail_errstring(t));
}


/* ARGSUSED */
static int
mm_stdout(void *arg, const char *fmt, ...)
{
	va_list	 	 ap;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	return(1);
}


/*
 * Example use of a filter.  If an input line is blank (length zero),
 * it's passed over.  If it's not, it's copied into a temporary buffer,
 * with space between consecutive '@' values omitted including the at
 * symbols.  Thus, "How are you @DOING@" results in "How are you ".  The
 * temporary buffer is expanded on-demand and freed in the main calling
 * body.
 */
static int
mm_filter(void *arg, const char *line, size_t linesz,
		const char **cp, size_t *csz)
{
	struct mmbuf	*buf;
	size_t		 sz;
	const char	*p, *pp;
	char		*ppp;

	assert(arg);
	buf = (struct mmbuf *)arg;

	if (0 == linesz)
		return(1);

	if (linesz > buf->bufsz) {
		ppp = realloc(buf->buf, linesz);
		if (NULL == ppp) {
			perror("realloc");
			return(0);
		}
		buf->buf = ppp;
		buf->bufsz = linesz;
	}

	sz = 0;
	p = line;
	while (linesz && NULL != (pp = memchr(p, '@', linesz))) {
		assert(pp >= p);
		linesz -= /* LINTED */
			(pp - p);
		/* LINTED */
		memcpy(buf->buf + sz, p, pp - p);
		/* LINTED */
		sz += (pp - p);

		p = pp;
		if (0 == --linesz)
			break;
		p++;
		pp = memchr(p, '@', linesz);
		if (NULL == pp) {
			p--;
			linesz++;
			break;
		}

		/* Just drop the interior contents. */

		linesz -= /* LINTED */
			(pp - p);
		p = pp;

		linesz--;
		p++;
	}

	if (linesz) {
		/* LINTED */
		memcpy(buf->buf + sz, p, linesz);
		sz += linesz;
	}

	*cp = buf->buf;
	*csz = sz;
	return(1);
}