/*
** repl.c -- reply to a message
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

#include <h/mh.h>
#include <h/utils.h>
#include <h/addrsbr.h>
#include <h/fmt_scan.h>
#include <sys/file.h>  /* L_SET */
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <locale.h>
#include <sysexits.h>

static struct swit switches[] = {
#define GROUPSW  0
	{ "group", 0 },
#define NGROUPSW  1
	{ "nogroup", 2 },
#define ANNOSW  2
	{ "annotate", 0 },
#define NANNOSW  3
	{ "noannotate", 2 },
#define CCSW  4
	{ "cc all|to|cc|me", 0 },
#define NCCSW  5
	{ "nocc type", 2 },
#define EDITRSW  6
	{ "editor editor", 0 },
#define FILTSW  7
	{ "filter filterfile", 0 },
#define NFILTSW  8
	{ "nofilter", 2 },
#define FORMSW  9
	{ "form formfile", 0 },
#define MIMESW  10
	{ "mime", 0 },
#define NMIMESW  11
	{ "nomime", 2 },
#define QURYSW  12
	{ "query", 0 },
#define NQURYSW  13
	{ "noquery", 2 },
#define WHATSW  14
	{ "whatnowproc program", 0 },
#define VERSIONSW  15
	{ "Version", 0 },
#define HELPSW  16
	{ "help", 0 },
#define FILESW  17
	{ "file file", 4 },  /* interface from msh */
#define BILDSW  18
	{ "build", 5 },  /* interface from mhe */
	{ NULL, 0 }
};

char *version=VERSION;

static struct swit ccswitches[] = {
#define CTOSW  0
	{ "to", 0 },
#define CCCSW  1
	{ "cc", 0 },
#define CMESW  2
	{ "me", 0 },
#define CALSW  3
	{ "all", 0 },
	{ NULL, 0 }
};

static short ccto = -1;
static short cccc = -1;
static short ccme = -1;
static short querysw = 0;

static short groupreply = 0;  /* Is this a group reply? */

static int mime = 0;  /* include original as MIME part */
static char *form   = NULL;  /* form (components) file */
static char *filter = NULL;  /* message filter file */

static int dftype=0;

static char *badaddrs = NULL;
static char *dfhost = NULL;

static struct mailname mq;

static struct format *fmt;

static int ncomps = 0;  /* # of interesting components */

static int dat[5];  /* aux. data for format routine */

static char *addrcomps[] = {
	"from",
	"sender",
	"reply-to",
	"to",
	"cc",
	"bcc",
	"resent-from",
	"resent-sender",
	"resent-reply-to",
	"resent-to",
	"resent-cc",
	"resent-bcc",
	NULL
};

/*
** static prototypes
*/
static void docc(char *, int);
static int insert(struct mailname *);
static void replfilter(FILE *, FILE *, char *);
static void replout(FILE *, char *, struct msgs *, int,
		char *, char *);


int
main(int argc, char **argv)
{
	int anot = 0;
	char *cp, *cwd, *maildir, *file = NULL;
	char *folder = NULL, *msg = NULL;
	char *ed = NULL, drft[BUFSIZ], buf[BUFSIZ];
	char **argp, **arguments;
	struct msgs *mp = NULL;
	FILE *in;
	int buildsw = 0;

	setlocale(LC_ALL, "");
	invo_name = mhbasename(argv[0]);

	/* read user profile/context */
	context_read();

	filter = mh_xstrdup(etcpath(mhlreply));

	arguments = getarguments(invo_name, argc, argv, 1);
	argp = arguments;

	while ((cp = *argp++)) {
		if (*cp == '-') {
			switch (smatch(++cp, switches)) {
			case AMBIGSW:
				ambigsw(cp, switches);
				exit(EX_USAGE);
			case UNKWNSW:
				adios(EX_USAGE, NULL, "-%s unknown", cp);

			case HELPSW:
				snprintf(buf, sizeof(buf), "%s: [+folder] [msg] [switches]", invo_name);
				print_help(buf, switches, 1);
				exit(argc == 2 ? EX_OK : EX_USAGE);
			case VERSIONSW:
				print_version(invo_name);
				exit(argc == 2 ? EX_OK : EX_USAGE);

			case GROUPSW:
				groupreply++;
				continue;
			case NGROUPSW:
				groupreply = 0;
				continue;

			case ANNOSW:
				anot++;
				continue;
			case NANNOSW:
				anot = 0;
				continue;

			case CCSW:
				if (!(cp = *argp++) || *cp == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				docc(cp, 1);
				continue;
			case NCCSW:
				if (!(cp = *argp++) || *cp == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				docc(cp, 0);
				continue;

			case EDITRSW:
				if (!(ed = *argp++) || *ed == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				continue;

			case WHATSW:
				if (!(whatnowproc = *argp++) ||
						*whatnowproc == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				continue;

			case BILDSW:
				buildsw++;
				continue;

			case FILESW:
				if (file)
					adios(EX_USAGE, NULL, "only one file at a time!");
				if (!(cp = *argp++) || *cp == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				file = mh_xstrdup(expanddir(cp));
				continue;
			case FORMSW:
				if (!(form = *argp++) || *form == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				continue;

			case FILTSW:
				if (!(cp = *argp++) || *cp == '-')
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				filter = mh_xstrdup(etcpath(cp));
				continue;
			case NFILTSW:
				filter = NULL;
				continue;

			case MIMESW:
				mime++;
				continue;
			case NMIMESW:
				mime = 0;
				continue;

			case QURYSW:
				querysw++;
				continue;
			case NQURYSW:
				querysw = 0;
				continue;

			}
		}
		if (*cp == '+' || *cp == '@') {
			if (folder)
				adios(EX_USAGE, NULL, "only one folder at a time!");
			else
				folder = mh_xstrdup(expandfol(cp));
		} else {
			if (msg)
				adios(EX_USAGE, NULL, "only one message at a time!");
			else
				msg = cp;
		}
	}

	if (ccto == -1)
		ccto = groupreply;
	if (cccc == -1)
		cccc = groupreply;
	if (ccme == -1)
		ccme = groupreply;

	cwd = mh_xstrdup(pwd());

	if (file && (msg || folder))
		adios(EX_USAGE, NULL, "can't mix files and folders/msgs");

	strncpy(drft, buildsw ? toabsdir("reply") : m_draft(seq_beyond),
			sizeof(drft));
	/*
	** FIXME: (concerning MHE support (buildsw) only)
	** There's no check if the draft already exists. mmh has removed
	** this case by having the draft folder. I won't add code only to
	** handle this legacy issue for MHE. -- meillo@marmaro.de 2012-05
	*/

	if (file) {
		/*
		** We are replying to a file.
		*/
		anot = 0;  /* we don't want to annotate a file */
	} else {
		/*
		** We are replying to a message.
		*/
		if (!msg)
			msg = seq_cur;
		if (!folder)
			folder = getcurfol();
		maildir = toabsdir(folder);

		if (chdir(maildir) == NOTOK)
			adios(EX_OSERR, maildir, "unable to change directory to");

		/* read folder and create message structure */
		if (!(mp = folder_read(folder)))
			adios(EX_IOERR, NULL, "unable to read folder %s", folder);

		/* check for empty folder */
		if (mp->nummsg == 0)
			adios(EX_DATAERR, NULL, "no messages in %s", folder);

		/* parse the message range/sequence/name and set SELECTED */
		if (!m_convert(mp, msg))
			exit(EX_SOFTWARE);
		seq_setprev(mp);  /* set the previous-sequence */

		if (mp->numsel > 1)
			adios(EX_USAGE, NULL, "only one message at a time!");

		context_replace(curfolder, folder); /* update current folder */
		seq_setcur(mp, mp->lowsel);  /* update current message  */
		seq_save(mp);  /* synchronize sequences   */
		context_save();  /* save the context file   */
	}

	msg = file ? file : mh_xstrdup(m_name(mp->lowsel));

	if ((in = fopen(msg, "r")) == NULL)
		adios(EX_IOERR, msg, "unable to open");

	/* find form (components) file */
	if (!form) {
		if (groupreply)
			form = etcpath(replgroupcomps);
		else
			form = etcpath(replcomps);
	}

	replout(in, drft, mp, mime, form, filter);
	fclose(in);

	if (buildsw)
		exit(EX_OK);
	what_now(ed, NOUSE, drft, msg, 0, mp, anot ? "Replied" : NULL, cwd);
	return EX_OSERR;
}

static void
docc(char *cp, int ccflag)
{
	switch (smatch(cp, ccswitches)) {
	case AMBIGSW:
		ambigsw(cp, ccswitches);
		exit(EX_USAGE);
	case UNKWNSW:
		adios(EX_USAGE, NULL, "-%scc %s unknown", ccflag ? "" : "no", cp);

	case CTOSW:
		ccto = ccflag;
		break;

	case CCCSW:
		cccc = ccflag;
		break;

	case CMESW:
		ccme = ccflag;
		break;

	case CALSW:
		ccto = cccc = ccme = ccflag;
		break;
	}
}




static void
replout(FILE *inb, char *drft, struct msgs *mp,
	int mime, char *form, char *filter)
{
	enum state state;
	struct field f = {{0}};
	int i;
	struct comp *cptr;
	char **ap;
	int char_read = 0, format_len, mask;
	char *scanl;
	unsigned char *cp;
	FILE *out;

	mask = umask(~m_gmprot());
	if ((out = fopen(drft, "w")) == NULL)
		adios(EX_CANTCREAT, drft, "unable to create");

	umask(mask);

	/* get new format string */
	cp = new_fs(form, NULL);
	format_len = strlen(cp);

	/* compile format string */
	ncomps = fmt_compile(cp, &fmt) + 1;

	for (ap = addrcomps; *ap; ap++) {
		FINDCOMP(cptr, *ap);
		if (cptr)
			cptr->c_type |= CT_ADDR;
	}

	/*
	** ignore any components killed by command line switches
	*/
	if (!ccto) {
		FINDCOMP(cptr, "to");
		if (cptr)
			cptr->c_name = "";
	}
	if (!cccc) {
		FINDCOMP(cptr, "cc");
		if (cptr)
			cptr->c_name = "";
	}
	if ((cp = getenv("USER"))) {
		FINDCOMP(cptr, "user");
		if (cptr)
			cptr->c_text = mh_xstrdup(cp);
	}
	if (!ccme)
		ismymbox(NULL);

	/*
	** pick any interesting stuff out of msg "inb"
	*/
	for (state = FLD2;;) {
		state = m_getfld2(state, &f, inb);
		switch (state) {
		case FLD2:
			/*
			** if we're interested in this component, save
			** a pointer to the component text, then start
			** using our next free buffer as the component
			** temp buffer (buffer switching saves an extra
			** copy of the component text).
			*/
			if ((cptr = wantcomp[CHASH(f.name)])) {
				do {
					if (mh_strcasecmp(f.name, cptr->c_name)!=0) {
						continue;
					}
					char_read += strlen(f.value);
					if (!cptr->c_text) {
						cptr->c_text = mh_xstrdup(f.value);
						i = strlen(cptr->c_text) - 1;
						if (cptr->c_text[i] == '\n') {
							cptr->c_text[i] = '\0';
						}
					} else {
						cp = cptr->c_text;
						i = strlen(cp) - 1;
						if (cp[i] == '\n') {
							if (cptr->c_type & CT_ADDR) {
								cp[i] = '\0';
								cp = add(",\n\t", cp);
							} else {
								cp = add("\t", cp);
							}
						}
						cptr->c_text = add(f.value, cp);
					}
					break;
				} while ((cptr = cptr->c_next));
			}
			break;

		case LENERR2:
		case FMTERR2:
		case IOERR2:
		case BODY2:
		case FILEEOF2:
			goto finished;

		default:
			adios(EX_SOFTWARE, NULL, "m_getfld() returned %d", state);
		}
	}

	/*
	** format and output the header lines.
	*/
finished:

	/*
	** if there's a "Subject" component, strip any "Re:"s off it
	*/
	FINDCOMP(cptr, "subject")
	if (cptr && (cp = cptr->c_text)) {
		char *sp = cp;

		for (;;) {
			while (isspace(*cp))
				cp++;
			if(uprf(cp, "re:"))
				cp += 3;
			else
				break;
			sp = cp;
		}
		if (sp != cptr->c_text) {
			cp = cptr->c_text;
			cptr->c_text = mh_xstrdup(sp);
			mh_free0(&cp);
		}
	}
	i = format_len + char_read + 256;
	scanl = mh_xcalloc(i + 2, sizeof(char));
	dat[0] = 0;
	dat[1] = 0;
	dat[2] = 0;
	dat[3] = OUTPUTLINELEN;
	dat[4] = 0;
	fmt_scan(fmt, scanl, i, dat);
	fputs(scanl, out);
	if (badaddrs) {
		fputs("\nrepl: bad addresses:\n", out);
		fputs( badaddrs, out);
	}

	/* Check if we should filter the message */
	if (filter) {
		fflush(out);
		if (ferror(out))
			adios(EX_IOERR, drft, "error writing");

		replfilter(inb, out, filter);
	}

	fflush(out);
	if (ferror(out))
		adios(EX_IOERR, drft, "error writing");
	fclose(out);

	if (mime && mp) {
		/* add an attachment header */
		char buffer[BUFSIZ];

		snprintf(buffer, sizeof buffer, "+%s %s",
				mp->foldpath, m_name(mp->lowsel));
		if (execprogl("anno", "anno", "-append", "-nodate",
				drft, "-comp", attach_hdr, "-text", buffer,
				(char *)NULL) != 0) {
			advise(NULL, "unable to add attachment header");
		}
	}

	/* return dynamically allocated buffers */
	mh_free0(&scanl);
}

static char *buf;  /* our current working buffer */
static char *bufend;  /* end of working buffer */
static char *last_dst;  /* buf ptr at end of last call */
static unsigned int bufsiz=0;  /* current size of buf */

#define BUFINCR 512  /* how much to expand buf when if fills */

#define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }

/*
** check if there's enough room in buf for str.
** add more mem if needed
*/
#define CHECKMEM(str) \
	if ((len = strlen(str)) >= bufend - dst) {\
		int i = dst - buf;\
		int n = last_dst - buf;\
		bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
		buf = mh_xrealloc(buf, bufsiz);\
		dst = buf + i;\
		last_dst = buf + n;\
		bufend = buf + bufsiz;\
	}


/*
** fmt_scan will call this routine if the user includes the function
** "(formataddr {component})" in a format string.  "orig" is the
** original contents of the string register.  "str" is the address
** string to be formatted and concatenated onto orig.  This routine
** returns a pointer to the concatenated address string.
**
** We try to not do a lot of malloc/copy/free's (which is why we
** don't call "mh_xstrdup") but still place no upper limit on the
** length of the result string.
**
** This routine is an override for the equally named one in sbr/fmt_addr.c.
** Don't delete it!
*/
char *
formataddr(char *orig, char *str)
{
	int len;
	char baddr[BUFSIZ], error[BUFSIZ];
	int isgroup;
	char *dst;
	char *cp;
	char *sp;
	struct mailname *mp = NULL;

	/* if we don't have a buffer yet, get one */
	if (bufsiz == 0) {
		buf = mh_xcalloc(BUFINCR, sizeof(char));
		last_dst = buf;  /* XXX */
		bufsiz = BUFINCR - 6;  /* leave some slop */
		bufend = buf + bufsiz;
	}
	/*
	** If "orig" points to our buffer we can just pick up where we
	** left off.  Otherwise we have to copy orig into our buffer.
	*/
	if (orig == buf)
		dst = last_dst;
	else if (!orig || !*orig) {
		dst = buf;
		*dst = '\0';
	} else {
		dst = last_dst;  /* XXX */
		CHECKMEM(orig);
		CPY(orig);
	}

	/* concatenate all the new addresses onto 'buf' */
	for (isgroup = 0; (cp = getname(str)); ) {
		if ((mp = getm(cp, dfhost, dftype, AD_NAME, error)) == NULL) {
			snprintf(baddr, sizeof(baddr), "\t%s -- %s\n",
					cp, error);
			badaddrs = add(baddr, badaddrs);
			continue;
		}
		if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
			*dst++ = ';';
			isgroup = 0;
		}
		if (insert(mp)) {
			/* if we get here we're going to add an address */
			if (dst != buf) {
				*dst++ = ',';
				*dst++ = ' ';
			}
			if (mp->m_gname) {
				CHECKMEM(mp->m_gname);
				CPY(mp->m_gname);
				isgroup++;
			}
			sp = adrformat(mp);
			CHECKMEM(sp);
			CPY(sp);
		}
	}

	if (isgroup)
		*dst++ = ';';

	*dst = '\0';
	last_dst = dst;
	return (buf);
}


static int
insert(struct mailname *np)
{
	char buffer[BUFSIZ];
	struct mailname *mp;

	if (np->m_mbox == NULL)
		return 0;

	for (mp = &mq; mp->m_next; mp = mp->m_next) {
		if (!mh_strcasecmp(np->m_host, mp->m_next->m_host) &&
				!mh_strcasecmp(np->m_mbox, mp->m_next->m_mbox))
			return 0;
	}
	if (!ccme && ismymbox(np))
		return 0;

	if (querysw) {
		snprintf(buffer, sizeof(buffer), "Reply to %s? ",
				adrformat(np));
		if (!gans(buffer, anoyes))
			return 0;
	}
	mp->m_next = np;
	return 1;
}


/*
** Call mhl
**
** This function expects that argument out has been fflushed by the caller.
*/
static void
replfilter(FILE *in, FILE *out, char *filter)
{
	int pid, pid_show, n;
	int mailpipe[2];

	if (filter == NULL)
		return;

	if (access(filter, R_OK) == NOTOK)
		adios(EX_IOERR, filter, "unable to read");

	rewind(in);
	lseek(fileno(in), (off_t) 0, SEEK_SET);

	switch (pid = fork()) {
	case NOTOK:
		adios(EX_OSERR, "fork", "unable to");

	case OK:
		if (pipe(mailpipe) == -1) {
			adios(EX_OSERR, "pipe", "can't create pipe");
		}

		switch (pid_show = fork()) {
		case NOTOK:
			adios(EX_OSERR, "fork", "unable to");
		case OK:
			dup2(fileno(in), STDIN_FILENO);
			dup2(mailpipe[1], STDOUT_FILENO);
			close(fileno(in));
			close(fileno(out));
			close(mailpipe[0]);
			close(mailpipe[1]);
			for (n=3; n<OPEN_MAX; n++) {
				close(n);
			}
			execlp("show", "show", "-file", "-", NULL);
			adios(EX_OSERR, "exec", "unable to");
			break;
		default:
			dup2(mailpipe[0], STDIN_FILENO);
			dup2(fileno(out), STDOUT_FILENO);
			close(fileno(in));
			close(fileno(out));
			close(mailpipe[0]);
			close(mailpipe[1]);
			for (n=3; n<OPEN_MAX; n++) {
				close(n);
			}
			execlp("mhl", "mhl", "-form", filter, NULL);
			adios(EX_OSERR, "exec", "unable to");
			break;
		}

	default:
		if (pidXwait(pid, "mhl")) {
			exit(EX_SOFTWARE);
		}
		fseek(out, 0L, SEEK_END);
		break;
	}
}
