/*
** whom.c -- list recipients of message
**
** This code is Copyright (c) 2012, by the authors of mmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

#include <h/mh.h>
#include <h/addrsbr.h>
#include <h/fmt_scan.h>
#include <h/utils.h>
#include <locale.h>
#include <sysexits.h>

static struct swit switches[] = {
#define VERSIONSW 0
	{ "Version", 0 },
#define HELPSW 1
	{ "help", 0 },
#define TOCCSW 2
	{ "tocc", 0 },
#define NTOCCSW 3
	{ "notocc", 2 },
#define DCCSW 4
	{ "dcc", 0 },
#define NDCCSW 5
	{ "nodcc", 2 },
#define BCCSW 6
	{ "bcc", 0 },
#define NBCCSW 7
	{ "nobcc", 2 },
#define ALISW 8
	{ "alias", 0 },
#define NALISW 9
	{ "noalias", 2 },
	{ NULL, 0 }
};

char *version=VERSION;

#define NFILES 32

#define HRESENT (1<<1)
#define HTO (1<<2)
#define HCC (1<<3)
#define HDCC (1<<4)
#define HBCC (1<<5)

struct mailname head = {0};
struct mailname *mp = &head;

static char *cmd;

static int resent = 0;  /* consider normal or resent headers */
static int toccsw = 1;  /* list sighted recipients */
static int dccsw = 1;  /* list hidden dcc recipients */
static int bccsw = 1;  /* list hidden bcc recipients */
static int alisw = 1;  /* expand aliases on rcpt addrs */

static char *dccsep = "\t==DCC==";
static char *bccsep = "\t==BCC==";


/*
** static prototypes
*/
static int process(char *);
static void proc_hdr(char *, char *);
static int print(void);
static int printdcc(void);
static int printbcc(void);
static void printone(struct mailname *);


int
main(int argc, char **argv)
{
	int naddrs=0, n;
	char *cp;
	char buf[BUFSIZ], **argp;
	char **arguments;
	char *folder = NULL;
	FILE *in;
	struct msgs_array msgs = {0};
	struct msgs_array files = {0};
	size_t filep;

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

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

	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 [switches] file ...",
						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 TOCCSW:
				toccsw = 1;
				continue;
			case NTOCCSW:
				toccsw = 0;
				continue;

			case DCCSW:
				dccsw = 1;
				continue;
			case NDCCSW:
				dccsw = 0;
				continue;

			case BCCSW:
				bccsw = 1;
				continue;
			case NBCCSW:
				bccsw = 0;
				continue;

			case ALISW:
				alisw = 1;
				continue;
			case NALISW:
				alisw = 0;
				continue;
			}
		}
		if (*cp == '+' || *cp == '@') {
			if (folder) {
				adios(EX_USAGE, NULL, "only one folder at a time!");
			} else {
				folder = mh_xstrdup(expandfol(cp));
			}
		} else {
			app_msgarg(&msgs, cp);
		}
	}
	if (!msgs.size) {
		adios(EX_USAGE, NULL, "usage: %s [switches] file ...", invo_name);
	}
	if (!toccsw && !dccsw && !bccsw) {
		adios(EX_USAGE, NULL, "use at least one of: -tocc -dcc -bcc");
	}
	if (parse_msgs(&msgs, folder, &files) < 0) {
		exit(EX_IOERR);
	}
	for (filep = 0; filep < files.size; filep++) {
		process(files.msgs[filep]);
	}

	cmd = add("ali -list", NULL);
	if ((n=print()) && alisw) {
		if (!(in = popen(cmd, "r"))) {
			adios(EX_IOERR, "popen", "unable to");
		}
		while (fgets(buf, sizeof buf, in)) {
			fputs(buf, stdout);
		}
		pclose(in);
	}
	mh_free0(&cmd);
	naddrs += n;

	cmd = add("ali -list", NULL);
	if ((n=printdcc()) && alisw) {
		if (!(in = popen(cmd, "r"))) {
			adios(EX_IOERR, "popen", "unable to");
		}
		while (fgets(buf, sizeof buf, in)) {
			fputs(buf, stdout);
		}
		pclose(in);
	}
	mh_free0(&cmd);
	naddrs += n;

	cmd = add("ali -list", NULL);
	if ((n=printbcc()) && alisw) {
		if (!(in = popen(cmd, "r"))) {
			adios(EX_IOERR, "popen", "unable to");
		}
		while (fgets(buf, sizeof buf, in)) {
			fputs(buf, stdout);
		}
		pclose(in);
	}
	mh_free0(&cmd);
	naddrs += n;

	return naddrs ? 0 : 1;
}


static int
process(char *file)
{
	enum state state;
	struct field f = {{0}};
	int compnum;
	FILE *in;


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

	for (compnum=1, state=FLD2;; compnum++) {
		switch (state = m_getfld2(state, &f, in)) {
		case LENERR2:
			state = FLD2;
			/* FALL */
		case FLD2:
			proc_hdr(f.name, f.value);
			continue;

		case BODY2:
		case FILEEOF2:
			break;

		case FMTERR2:
			advise(NULL, "message format error in component #%d", compnum);
			continue;

		case IOERR2:
			adios(EX_DATAERR, NULL, "message format error in component #%d",
					compnum);

		default:
			adios(EX_SOFTWARE, NULL, "getfld() returned %d", state);
		}
		break;
	}
	fclose(in);
	return 0;
}


/*
** Check if the header contains addresses we're interested in.
** If so, extract the addresses and add them to the global list.
*/
static void
proc_hdr(char *name, char *val)
{
	char *cp;
	int type = 0;

	while (*val==' ' || *val=='\t' || *val=='\n') {
		val++;
	}
	if (strncasecmp(name, "resent-", 7)==0) {
		resent = HRESENT;
	}
	if (mh_strcasecmp(name, "to")==0) {
		type = HTO;
	} else if (mh_strcasecmp(name, "cc")==0) {
		type = HCC;
	} else if (mh_strcasecmp(name, "dcc")==0) {
		type = HDCC;
	} else if (mh_strcasecmp(name, "bcc")==0) {
		type = HBCC;
	} else if (mh_strcasecmp(name, "resent-to")==0) {
		type = (HRESENT | HTO);
	} else if (mh_strcasecmp(name, "resent-cc")==0) {
		type = (HRESENT | HCC);
	} else if (mh_strcasecmp(name, "resent-dcc")==0) {
		type = (HRESENT | HDCC);
	} else if (mh_strcasecmp(name, "resent-bcc")==0) {
		type = (HRESENT | HBCC);
	}
	/* ignore non-recpient headers */
	if (!type) {
		return;
	}
	/* ignore recipient headers we are not interested in */ 
	if ((type&HTO || type&HCC) && !toccsw) {
		return;
	}
	if ((type&HDCC) && !dccsw) {
		return;
	}
	if ((type&HBCC) && !bccsw) {
		return;
	}

	while ((cp = getname(val))) {
		if (!(mp->m_next = getm(cp, NULL, 0, AD_NAME, NULL))) {
			adios(EX_DATAERR, NULL, "illegal address: %s", cp);
		}
		mp = mp->m_next;
		if (mp->m_type == BADHOST) {
			admonish(NULL, "bad address `%s'", mp->m_text);
			continue;
		}
		mp->m_type = type;
	}
}


/*
** Walk through the list of addresses and print the right ones.
*/
static int
print(void)
{
	int naddrs = 0;

	for (mp=head.m_next; mp; mp=mp->m_next) {
		/* skip unless both are resent or neither one is */
		if (resent != (mp->m_type&HRESENT)) {
			continue;
		}
		if (mp->m_type & (HTO|HCC)) {
			naddrs++;
			printone(mp);
		}
	}
	return naddrs;
}

/*
** Walk through the list of addresses and print the right ones.
*/
static int
printdcc(void)
{
	int naddrs = 0;

	for (mp=head.m_next; mp; mp=mp->m_next) {
		/* skip unless both are resent or neither one is */
		if (resent != (mp->m_type&HRESENT)) {
			continue;
		}
		if (mp->m_type & HDCC) {
			if (!naddrs && (toccsw || bccsw)) {
				puts(dccsep);
			}
			naddrs++;
			printone(mp);
		}
	}
	return naddrs;
}

/*
** Walk through the list of addresses and print the right ones.
*/
static int
printbcc(void)
{
	int naddrs = 0;

	for (mp=head.m_next; mp; mp=mp->m_next) {
		/* skip unless both are resent or neither one is */
		if (resent != (mp->m_type&HRESENT)) {
			continue;
		}
		if (mp->m_type & HBCC) {
			if (!naddrs && (toccsw || dccsw)) {
				puts(bccsep);
			}
			naddrs++;
			printone(mp);
		}
	}
	return naddrs;
}


/*
** Print one single address in appropriate form.
*/
static void
printone(struct mailname *mp)
{
	char buf[BUFSIZ];

	if (mp->m_host) {
		snprintf(buf, sizeof buf, " %s@%s", mp->m_mbox, mp->m_host);
	} else {
		snprintf(buf, sizeof buf, " %s", mp->m_mbox);
	}
	if (alisw) {
		cmd = add(buf, cmd);
	} else {
		puts(buf+1);
	}
}
