/*
** scansbr.c -- routines to help scan along...
**
** 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/addrsbr.h>
#include <h/fmt_scan.h>
#include <h/scansbr.h>
#include <h/tws.h>
#include <h/utils.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sysexits.h>

#define MAXSCANL 256  /* longest possible scan line */

static struct format *fmt;

static struct comp *datecomp;  /* pntr to "date" comp */
static int ncomps = 0;  /* # of interesting components */

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

char *scanl = NULL;  /* text of most recent scanline */

#define FPUTS(buf) {\
		if (fputs(buf, scnout) == EOF)\
			adios(EX_IOERR, scnmsg, "write error on");\
	}

/*
** prototypes
*/
int sc_width(void);  /* from termsbr.c */

#ifdef MULTIBYTE_SUPPORT
#define SCAN_CHARWIDTH MB_CUR_MAX
#else
#define SCAN_CHARWIDTH 1
#endif

int
scan(FILE *inb, int innum, int outnum, char *fmtstr, int width, int curflg,
	int unseen)
{
	static int slwidth;
	int compnum, i;
	enum state state;
	struct field f = {{0}};
	char *cp;
	struct comp *cptr;
	char *scnmsg = NULL;
	FILE *scnout = NULL;
	int incing = (outnum != SCN_MBOX && outnum != SCN_FOLD);
	int scanfolder = (outnum == SCN_FOLD);
	long fpos;
	struct stat st;
	int blankline;

	/* first-time only initialization */
	if (!scanl) {
		if (incing)
			umask(~m_gmprot());

		if (fmtstr) {
			if (width == 0) {
				if ((width = sc_width()) < WIDTH/2)
					width = WIDTH/2;
				else if (width > MAXSCANL)
					width = MAXSCANL;
			}
			dat[3] = slwidth = width;
			scanl = mh_xcalloc(slwidth + 2, SCAN_CHARWIDTH);  /* probably for \n and \0 */
			/* Compile format string */
			ncomps = fmt_compile(fmtstr, &fmt) + 1;
			FINDCOMP(datecomp, "date");
		} else {
			ncomps = 1;
			datecomp = NULL;
		}
	}

	if (feof(inb)) {
		return SCNEOF;
	}

	/*
	** each-message initialization
	*/
	dat[0] = innum ? innum : outnum;
	dat[1] = curflg;
	dat[4] = unseen;
	fpos = ftell(inb);

	if (incing) {
		scnmsg = m_name(outnum);
		if (*scnmsg == '?')  /* msg num out of range */
			return SCNNUM;
		if (!(scnout = fopen(scnmsg, "w")))
			adios(EX_IOERR, scnmsg, "unable to write");
	}
	/* scan - main loop */
	for (compnum = 1, state = FLD2; ; ) {
		state = m_getfld2(state, &f, inb);
		switch (state) {
		case LENERR2:
			state = FLD2;
			/* FALL */
		case FLD2:
			compnum++;
			if (incing) {
				FPUTS(f.name);
				FPUTS(":");
				FPUTS(f.value);
			}
			if (fmtstr && (cptr = wantcomp[CHASH(f.name)])) {
				/*
				** we're interested in this component,
				** but find the right one in the hash
				** collision chain ...
				*/
				do {
					if (mh_strcasecmp(f.name, cptr->c_name)!=0) {
						continue;
					}
					if (cptr->c_text) {
						free(cptr->c_text);
						cptr->c_text = NULL;
					}
					cptr->c_text = mh_xstrdup(f.value);
					cp = cptr->c_text + strlen(cptr->c_text) - 1;
					for (; cp >= cptr->c_text; cp--) {
						if (isspace(*cp)) {
							*cp = '\0';
						} else {
							break;
						}
					}
					break;
				} while ((cptr = cptr->c_next));
			}
			break;

		case BODY2:
			compnum = -1;
			if (scanfolder) {
				/* stop here if we scan a msg in a folder */
				state = FILEEOF2;
				goto finished;
			}
			/* otherwise (mbox): snarf the body */
			if (strncmp("From ", f.value, 5)==0) {
				state = FILEEOF2;
				goto finished;
			}
			if (incing) {
				FPUTS("\n");
				FPUTS(f.value);
			}
body:;
			blankline = 0;
			while ((state = m_getfld2(state, &f, inb)) == BODY2) {
				/*
				** recognize From lines without blank lines
				** before them as well.
				*/
				if (strncmp("From ", f.value, 5)==0) {
					state = FILEEOF2;
					goto finished;
				}
				/*
				** delay the printing of blank lines
				** because if it's the end of the message,
				** then we must omit the blank line,
				** as it is not part of the message but
				** part of the mbox format
				*/
				if (blankline) {
					/* print the delayed blank line */
					FPUTS("\n");
					blankline = 0;
				}
				if (strcmp(f.value, "\n")==0) {
					blankline = 1;
					continue;
				}
				if (incing) {
					FPUTS(f.value);
				}
			}
			goto finished;

		case FMTERR2:
			if (strncmp("From ", f.value, 5)==0) {
				state = FILEEOF2;
				goto finished;
			}
			state = FLD2;
			fprintf(stderr, innum ?
					"??Format error (message %d) in " :
					"??Format error in ",
					outnum ? outnum : innum);
			fprintf(stderr, "component %d\n", compnum);
			continue;

		case IOERR2:
			fprintf(stderr, innum ?
					"??Format error (message %d) in " :
					"??Format error in ",
					outnum ? outnum : innum);
			fprintf(stderr, "component %d\n", compnum);

			if (incing) {
				FPUTS("\n\nBAD MSG:\n");
				FPUTS(f.name);  /* XXX use f.field? */
				FPUTS("\n");
				state = BODY2;
				goto body;
			}
			/* fall through if we scan only */

		case FILEEOF2:
			goto finished;

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

finished:
	/* Format and output the scan line. */
	if (ferror(inb)) {
		advise("read", "unable to");
		return SCNFAT;
	}

	if (incing) {
		if ((dat[2] = ftell(scnout)) == EOF)
			adios(EX_IOERR, scnmsg, "write error on");
	} else if (!scanfolder) {
		if ((dat[2] = ftell(inb)) == EOF)
			adios(EX_IOERR, scnmsg, "write error on");
		dat[2] -= fpos;
	}

	if (fmtstr) {
		fstat(fileno(inb), &st);
		if (scanfolder) {
			dat[2] = st.st_size;
		}
		if (datecomp && !datecomp->c_text) {
			if (!datecomp->c_text) {
				if (!datecomp->c_tws)
					datecomp->c_tws = mh_xcalloc(1, sizeof(*datecomp->c_tws));
				if (!datecomp->c_tws)
					adios(EX_OSERR, NULL, "unable to allocate tws buffer");
				*datecomp->c_tws = *dlocaltime((time_t *) &st.st_mtime);
				datecomp->c_flags |= CF_DATEFAB|CF_TRUE;
			} else {
				datecomp->c_flags &= ~CF_DATEFAB;
			}
		}

		/* print the scan line */
		fmt_scan(fmt, scanl, slwidth, dat);
		fputs(scanl, stdout);
	}

	/* clean up old values */
        for (i=0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++) {
		for (cptr=wantcomp[i]; cptr; cptr=cptr->c_next) {
			if (cptr->c_text) {
				free(cptr->c_text);
				cptr->c_text = NULL;
			}
		}
	}

	if (incing && (ferror(scnout) || fclose(scnout) == EOF))
		adios(EX_IOERR, scnmsg, "write error on");

	return (state == FILEEOF2 ? SCNMSG : SCNERR);
}
