/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "rcv.h"
#include <locale.h>

/*
 * mailx -- a modified version of a University of California at Berkeley
 *	mail program
 *
 * More user commands.
 */

static int	igshow(void);
static int	igcomp(const void *l, const void *r);
static int	save1(char str[], int mark);
static int	Save1(int *msgvec, int mark);
static void	savemsglist(char *file, int *msgvec, int flag);
static int	put1(char str[], int doign);
static int	svputs(const char *line, FILE *obuf);
static int	wrputs(const char *line, FILE *obuf);
static int	retshow(void);

/* flags for savemsglist() */
#define	S_MARK		1		/* mark the message as saved */
#define	S_NOHEADER	2		/* don't write out the header */
#define	S_SAVING	4		/* doing save/copy */
#define	S_NOIGNORE	8		/* don't do ignore processing */

/*
 * If any arguments were given, go to the next applicable argument
 * following dot, otherwise, go to the next applicable message.
 * If given as first command with no arguments, print first message.
 */

int 
next(int *msgvec)
{
	register struct message *mp;
	register int *ip, *ip2;
	int list[2], mdot;

	if (*msgvec != NULL) {

		/*
		 * If some messages were supplied, find the 
		 * first applicable one following dot using
		 * wrap around.
		 */

		mdot = dot - &message[0] + 1;

		/*
		 * Find the first message in the supplied
		 * message list which follows dot.
		 */

		for (ip = msgvec; *ip != NULL; ip++)
			if (*ip > mdot)
				break;
		if (*ip == NULL)
			ip = msgvec;
		ip2 = ip;
		do {
			mp = &message[*ip2 - 1];
			if ((mp->m_flag & MDELETED) == 0) {
				dot = mp;
				goto hitit;
			}
			if (*ip2 != NULL)
				ip2++;
			if (*ip2 == NULL)
				ip2 = msgvec;
		} while (ip2 != ip);
		printf(gettext("No messages applicable\n"));
		return(1);
	}

	/*
	 * If this is the first command, select message 1.
	 * Note that this must exist for us to get here at all.
	 */

	if (!sawcom)
		goto hitit;

	/*
	 * Just find the next good message after dot, no
	 * wraparound.
	 */

	for (mp = dot+1; mp < &message[msgCount]; mp++)
		if ((mp->m_flag & (MDELETED|MSAVED)) == 0)
			break;
	if (mp >= &message[msgCount]) {
		printf(gettext("At EOF\n"));
		return(0);
	}
	dot = mp;
hitit:
	/*
	 * Print dot.
	 */

	list[0] = dot - &message[0] + 1;
	list[1] = NULL;
	return(type(list));
}

/*
 * Save a message in a file.  Mark the message as saved
 * so we can discard when the user quits.
 */
int 
save(char str[])
{
	return(save1(str, S_MARK));
}

/*
 * Copy a message to a file without affected its saved-ness
 */
int 
copycmd(char str[])
{
	return(save1(str, 0));
}

/*
 * Save/copy the indicated messages at the end of the passed file name.
 * If mark is true, mark the message "saved."
 */
static int 
save1(char str[], int mark)
{
	char *file, *cmd;
	int f, *msgvec;

	cmd = mark ? "save" : "copy";
	msgvec = (int *) salloc((msgCount + 2) * sizeof *msgvec);
	if ((file = snarf(str, &f, 0)) == NOSTR)
		file = Getf("MBOX");
	if (f==-1)
		return(1);
	if (!f) {
		*msgvec = first(0, MMNORM);
		if (*msgvec == NULL) {
			printf(gettext("No messages to %s.\n"), cmd);
			return(1);
		}
		msgvec[1] = NULL;
	}
	if (f && getmsglist(str, msgvec, 0) < 0)
		return(1);
	if ((file = expand(file)) == NOSTR)
		return(1);
	savemsglist(file, msgvec, mark | S_SAVING);
	return(0);
}

int 
Save(int *msgvec)
{
	return(Save1(msgvec, S_MARK));
}

int 
Copy(int *msgvec)
{
	return(Save1(msgvec, 0));
}

/*
 * save/copy the indicated messages at the end of a file named
 * by the sender of the first message in the msglist.
 */
static int 
Save1(int *msgvec, int mark)
{
	register char *from;
	char recfile[BUFSIZ];

#ifdef notdef
	from = striphosts(nameof(&message[*msgvec-1], 0));
#else
	from = nameof(&message[*msgvec-1]);
#endif
	getrecf(from, recfile, 1, sizeof (recfile));
	if (*recfile != '\0')
		savemsglist(safeexpand(recfile), msgvec, mark | S_SAVING);
	return(0);
}

int 
sput(char str[])
{
	return(put1(str, 0));
}

int 
Sput(char str[])
{
	return(put1(str, S_NOIGNORE));
}

/*
 * Put the indicated messages at the end of the passed file name.
 */
static int 
put1(char str[], int doign)
{
	char *file;
	int f, *msgvec;

	msgvec = (int *) salloc((msgCount + 2) * sizeof *msgvec);
	if ((file = snarf(str, &f, 0)) == NOSTR)
		file = Getf("MBOX");
	if (f==-1)
		return(1);
	if (!f) {
		*msgvec = first(0, MMNORM);
		if (*msgvec == NULL) {
			printf(gettext("No messages to put.\n"));
			return(1);
		}
		msgvec[1] = NULL;
	}
	if (f && getmsglist(str, msgvec, 0) < 0)
		return(1);
	if ((file = expand(file)) == NOSTR)
		return(1);
	savemsglist(file, msgvec, doign);
	return(0);
}

/*
 * save a message list in a file.
 * if wr set, doing "write" instead
 * of "save" or "copy" so don't put
 * out header.
 */

static	int wr_linecount;		/* count of lines written */
static	int wr_charcount;		/* char count of lines written */
static	int wr_inlines;			/* count of lines read */
static	long wr_maxlines;		/* total lines in message */
static	int wr_inhead;			/* in header of message */

static void 
savemsglist(char *file, int *msgvec, int flag)
{
	register int *ip, mesg;
	register struct message *mp;
	char *disp;
	FILE *obuf;
	struct stat statb;
	long lc, cc, t;
	int bnry, mflag;

	printf("\"%s\" ", file);
	flush();
	if (stat(file, &statb) >= 0)
		disp = "[Appended]";
	else
		disp = "[New file]";
	if ((obuf = fopen(file, "a")) == NULL) {
		perror("");
		return;
	}
	lc = cc = 0;
	bnry = 0;
	if (flag & S_SAVING)
		mflag = (int)value("alwaysignore")?(M_IGNORE|M_SAVING):M_SAVING;
	else if (flag & S_NOIGNORE)
		mflag = 0;
	else
		mflag = M_IGNORE;
	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
		mesg = *ip;
		mp = &message[mesg-1];
		if (!mp->m_text) {
			bnry = 1;
		}
		wr_linecount = 0;
		wr_charcount = 0;
		if (flag & S_NOHEADER) {
			wr_inhead = 1;
			wr_maxlines = mp->m_lines;
			wr_inlines = 0;
			t = msend(mp, obuf, 0, wrputs);
		} else {
			t = msend(mp, obuf, mflag, svputs);
		}
		if (t < 0) {
			perror(file);
			fclose(obuf);
			return;
		}
		touch(mesg);
		dot = mp;
		lc += wr_linecount;
		cc += wr_charcount;
		if (flag & S_MARK)
			mp->m_flag |= MSAVED;
	}
	fflush(obuf);
	if (fferror(obuf))
		perror(file);
	fclose(obuf);
	if (!bnry) {
		printf("%s %ld/%ld\n", disp, lc, cc);
	} else {
		printf("%s binary/%ld\n", disp, cc);
	}
}

static int
svputs(const char *line, FILE *obuf)
{
	wr_linecount++;
	wr_charcount += strlen(line);
	return(fputs(line, obuf));
}

static int
wrputs(const char *line, FILE *obuf)
{
	/*
	 * If this is a header line or
	 * the last line, don't write it out.  Since we may add a
	 * "Status" line the line count may be off by one so insist
	 * that the last line is blank before we skip it.
	 */
	wr_inlines++;
	if (wr_inhead) {
		if (strcmp(line, "\n") == 0)
			wr_inhead = 0;
		return(0);
	}
	if (wr_inlines >= wr_maxlines && strcmp(line, "\n") == 0)
		return(0);
	wr_linecount++;
	wr_charcount += strlen(line);
	return(fputs(line, obuf));
}

/*
 * Write the indicated messages at the end of the passed
 * file name, minus header and trailing blank line.
 */

int 
swrite(char str[])
{
	register char *file;
	int f, *msgvec;

	msgvec = (int *) salloc((msgCount + 2) * sizeof *msgvec);
	if ((file = snarf(str, &f, 1)) == NOSTR)
		return(1);
	if (f==-1)
		return(1);
	if ((file = expand(file)) == NOSTR)
		return(1);
	if (!f) {
		*msgvec = first(0, MMNORM);
		if (*msgvec == NULL) {
			printf(gettext("No messages to write.\n"));
			return(1);
		}
		msgvec[1] = NULL;
	}
	if (f && getmsglist(str, msgvec, 0) < 0)
		return(1);
	savemsglist(file, msgvec, S_MARK|S_NOHEADER);
	return(0);
}

/*
 * Snarf the file from the end of the command line and
 * return a pointer to it.  If there is no file attached,
 * just return NOSTR.  Put a null in front of the file
 * name so that the message list processing won't see it,
 * unless the file name is the only thing on the line, in
 * which case, return 0 in the reference flag variable.
 */

/*
 * The following definitions are used to characterize the syntactic
 * category of the preceding character in the following parse procedure.
 * The variable pc_type assumes these values.
 */

#define	SN_DELIM	1	/* Delimiter (<blank> or line beginning) */
#define	SN_TOKEN	2	/* A part of a token */
#define	SN_QUOTE	4	/* An entire quoted string (ie, "...") */

char *
snarf(char linebuf[], int *flag, int erf)
{
	register char *p;		/* utility pointer */
	register char qc;		/* quotation character to match */
	register unsigned int  pc_type;	/* preceding character type */
	register char *tok_beg;		/* beginning of last token */
	register char *tok_end;		/* end of last token */
	char *line_beg;			/* beginning of line, after */
					/* leading whitespace */

	/* 
	 * Skip leading whitespace.
	 */
	for (line_beg = linebuf;
	     *line_beg && any(*line_beg, " \t");
	     line_beg++) {
		/* empty body */
	}
	if (!*line_beg) {
		if (erf) {
			printf(gettext("No file specified\n."));
		}
		*flag = 0;
		return(NOSTR);
	}
	/*
	 * Process line from left-to-right, 1 char at a time.
	 */
	for (pc_type = SN_DELIM, tok_beg = tok_end = NOSTR, p = line_beg;
	     *p != '\0'; ) {
		if (any(*p, " \t")) {
			/* This character is a DELIMITER */
			if (pc_type & (SN_TOKEN|SN_QUOTE)) {
				tok_end = p - 1;
			}
			pc_type = SN_DELIM;
			p++;
		} else if ((qc = *p) == '"' || qc == '\'') {
			/* This character is a QUOTE character */
			if (pc_type == SN_TOKEN) {
				/* embedded quotation symbols are simply */
				/* token characters. */
				p++;
				continue;
			}
			/* Search for the matching QUOTE character */
			for (tok_beg = p, tok_end = NOSTR, p++;
			     *p != '\0' && *p != qc;
			     p++) {
				if (*p == '\\' && *(p+1) == qc) {
					p++;
				}
			}
			if (*p == '\0') {
				printf(gettext("Syntax error: missing "
					       "%c.\n"), qc);
				*flag = -1;
				return(NOSTR);
			}
			tok_end = p;
			pc_type = SN_QUOTE;
			p++;
		} else {
			/* This character should be a TOKEN character */
			if (pc_type & (SN_DELIM|SN_TOKEN)) {
				if (pc_type & SN_DELIM) {
					tok_beg = p;
					tok_end = NOSTR;
				}
			} else {
				printf(gettext("improper quotes"
					       " at \"%s\".\n"), p);
				*flag = -1;
				return(NOSTR);
			}
			if (*p == '\\' && *++p == '\0') {
				printf(gettext("\'\\\' at "
					       "end of line.\n"));
				*flag = -1;
				return(NOSTR);
			}
			pc_type = SN_TOKEN;
			p++;
		}
	}
	if (pc_type == SN_TOKEN) {
		tok_end = p - 1;
	}
	if (tok_beg != NOSTR && tok_end != NOSTR) {
		if (tok_beg == line_beg) {
			*flag = 0;
		} else {
			tok_beg[-1] = '\0';
			*flag = 1;
		}
		tok_end[1] = '\0';
		return(tok_beg);
	} else {
		if (erf) {
			printf(gettext("No file specified\n."));
		}
		*flag = 0;
		return(NOSTR);
	}
}

/*
 * Delete messages, then type the new dot.
 */

int 
deltype(int msgvec[])
{
	int list[2];
	int lastdot;

	lastdot = dot - &message[0] + 1;
	if (delm(msgvec) >= 0) {
		list[0] = dot - &message[0];
		list[0]++;
		if (list[0] > lastdot) {
			touch(list[0]);
			list[1] = NULL;
			return(type(list));
		}
		printf(gettext("At EOF\n"));
		return(0);
	}
	else {
		printf(gettext("No more messages\n"));
		return(0);
	}
}

/*
 * Delete the indicated messages.
 * Set dot to some nice place afterwards.
 */
int 
delm(int *msgvec)
{
	register struct message *mp;
	int *ip, mesg;
	int last;

	last = NULL;
	for (ip = msgvec; *ip != NULL; ip++) {
		mesg = *ip;
		touch(mesg);
		mp = &message[mesg-1];
		mp->m_flag |= MDELETED|MTOUCH;
		mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
		last = mesg;
	}
	if (last != NULL) {
		dot = &message[last-1];
		last = first(0, MDELETED);
		if (last != NULL) {
			dot = &message[last-1];
			return(0);
		}
		else {
			dot = &message[0];
			return(-1);
		}
	}

	/*
	 * Following can't happen -- it keeps lint happy
	 */

	return(-1);
}

/*
 * Undelete the indicated messages.
 */
int 
undelete(int *msgvec)
{
	register struct message *mp;
	int *ip, mesg;

	for (ip = msgvec; ip-msgvec < msgCount; ip++) {
		mesg = *ip;
		if (mesg == 0)
			return(0);
		touch(mesg);
		mp = &message[mesg-1];
		dot = mp;
		mp->m_flag &= ~MDELETED;
	}
	return(0);
}

/*
 * Add the given header fields to the retained list.
 * If no arguments, print the current list of retained fields.
 */
int 
retfield(char *list[])
{
	char field[BUFSIZ];
	register int h;
	register struct ignore *igp;
	char **ap;

	if (argcount(list) == 0)
		return(retshow());
	for (ap = list; *ap != 0; ap++) {
		istrcpy(field, sizeof (field), *ap);

		if (member(field, retain))
			continue;

		h = hash(field);
		if ((igp = (struct ignore *)
		    calloc(1, sizeof (struct ignore))) == NULL) {
			panic("Couldn't allocate memory");
		}
		if ((igp->i_field = (char *)
		    calloc(strlen(field) + 1, sizeof (char))) == NULL) {
			panic("Couldn't allocate memory");
		}
		strcpy(igp->i_field, field);
		igp->i_link = retain[h];
		retain[h] = igp;
		nretained++;
	}
	return(0);
}

/*
 * Print out all currently retained fields.
 */
static int 
retshow(void)
{
	register int h, count;
	struct ignore *igp;
	char **ap, **ring;

	count = 0;
	for (h = 0; h < HSHSIZE; h++)
		for (igp = retain[h]; igp != 0; igp = igp->i_link)
			count++;
	if (count == 0) {
		printf(gettext("No fields currently being retained.\n"));
		return(0);
	}
	ring = (char **) salloc((count + 1) * sizeof (char *));
	ap = ring;
	for (h = 0; h < HSHSIZE; h++)
		for (igp = retain[h]; igp != 0; igp = igp->i_link)
			*ap++ = igp->i_field;
	*ap = 0;
	qsort(ring, count, sizeof (char *), igcomp);
	for (ap = ring; *ap != 0; ap++)
		printf("%s\n", *ap);
	return(0);
}

/*
 * Remove a list of fields from the retain list.
 */
int 
unretfield(char *list[])
{
	char **ap, field[BUFSIZ];
	register int h, count = 0;
	register struct ignore *ig1, *ig2;

	if (argcount(list) == 0) {
		for (h = 0; h < HSHSIZE; h++) {
			ig1 = retain[h];
			while (ig1) {
				free(ig1->i_field);
				ig2 = ig1->i_link;
				free((char *) ig1);
				ig1 = ig2;
				count++;
			}
			retain[h] = NULL;
		}
		if (count == 0)
			printf(gettext(
			    "No fields currently being retained.\n"));
		nretained = 0;
		return 0;
	}
	for (ap = list; *ap; ap++) {
		istrcpy(field, sizeof (field), *ap);
		h = hash(field);
		for (ig1 = retain[h]; ig1; ig2 = ig1, ig1 = ig1->i_link)
			if (strcmp(ig1->i_field, field) == 0) {
				if (ig1 == retain[h])
					retain[h] = ig1->i_link;
				else
					ig2->i_link = ig1->i_link;
				free(ig1->i_field);
				free((char *) ig1);
				nretained--;
				break;
			}
	}
	return 0;
}

/*
 * Add the given header fields to the ignored list.
 * If no arguments, print the current list of ignored fields.
 */
int 
igfield(char *list[])
{
	char field[BUFSIZ];
	register int h;
	register struct ignore *igp;
	char **ap;

	if (argcount(list) == 0)
		return(igshow());
	for (ap = list; *ap != 0; ap++) {
		if (isign(*ap, 0))
			continue;
		istrcpy(field, sizeof (field), *ap);
		h = hash(field);
		if ((igp = (struct ignore *)
		    calloc(1, sizeof (struct ignore))) == NULL) {
			panic("Couldn't allocate memory");
		}
		if ((igp->i_field = (char *)
		    calloc((unsigned)strlen(field) + 1,
		    sizeof (char))) == NULL) {
			panic("Couldn't allocate memory");
		}
		strcpy(igp->i_field, field);
		igp->i_link = ignore[h];
		ignore[h] = igp;
	}
	return(0);
}

/*
 * Print out all currently ignored fields.
 */
static int 
igshow(void)
{
	register int h, count;
	struct ignore *igp;
	char **ap, **ring;

	count = 0;
	for (h = 0; h < HSHSIZE; h++)
		for (igp = ignore[h]; igp != 0; igp = igp->i_link)
			count++;
	if (count == 0) {
		printf(gettext("No fields currently being ignored.\n"));
		return(0);
	}
	ring = (char **) salloc((count + 1) * sizeof (char *));
	ap = ring;
	for (h = 0; h < HSHSIZE; h++)
		for (igp = ignore[h]; igp != 0; igp = igp->i_link)
			*ap++ = igp->i_field;
	*ap = 0;
	qsort((char *) ring, (unsigned) count, sizeof (char *), igcomp);
	for (ap = ring; *ap != 0; ap++)
		printf("%s\n", *ap);
	return(0);
}

/*
 * Compare two names for sorting ignored field list.
 */
static int 
igcomp(const void *l, const void *r)
{
	return(strcmp(*(char **)l, *(char **)r));
}

/*
 * Remove a list of fields from the ignore list.
 */
int 
unigfield(char *list[])
{
	char **ap, field[BUFSIZ];
	register int h, count = 0;
	register struct ignore *ig1, *ig2;

	if (argcount(list) == 0) {
		for (h = 0; h < HSHSIZE; h++) {
			ig1 = ignore[h];
			while (ig1) {
				free(ig1->i_field);
				ig2 = ig1->i_link;
				free((char *) ig1);
				ig1 = ig2;
				count++;
			}
			ignore[h] = NULL;
		}
		if (count == 0)
			printf(gettext("No fields currently being ignored.\n"));
		return 0;
	}
	for (ap = list; *ap; ap++) {
		istrcpy(field, sizeof (field), *ap);
		h = hash(field);
		for (ig1 = ignore[h]; ig1; ig2 = ig1, ig1 = ig1->i_link)
			if (strcmp(ig1->i_field, field) == 0) {
				if (ig1 == ignore[h])
					ignore[h] = ig1->i_link;
				else
					ig2->i_link = ig1->i_link;
				free(ig1->i_field);
				free((char *) ig1);
				break;
			}
	}
	return 0;
}