/*
** m_convert.c -- parse a message range or sequence and set SELECTED
**
** 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.
*/


/* FIXME: This code needs rework! Rewrite as a parser? */

#include <h/mh.h>
#include <ctype.h>

/*
** error codes for sequence
** and message range processing
*/
#define BADMSG (-2)
#define BADRNG (-3)
#define BADNEW (-4)
#define BADNUM (-5)
#define BADLST (-6)

#define FIRST 1
#define LAST 2

#define getbeyond(mp) ((mp)->hghmsg + 1)

static int convdir;  /* convert direction */
static char *delimp;  /* delimiter pointer */

/*
** static prototypes
*/
static int m_conv(struct msgs *, char *, int);
static int attr(struct msgs *, char *);

int
m_convert(struct msgs *mp, char *name)
{
	int first, last, found, range, err;
	unsigned char *bp;
	char *cp;

	/* check if user defined sequence */
	err = attr(mp, cp = name);
	if (err == -1)
		return 0;
	else if (err < 0)
		goto badmsg;
	else if (err > 0) {
		/* it had been a user-defined sequence: we're finished */
		return 1;
	}
	/*
	** else err == 0, so continue
	** here we know: it is no user-defined seq
	*/

	/*
	** Handle the special beyond sequence, which is valid only if
	** ALLOW_BEYOND is set, and can appear only on its own.
	** Also, it is available in any folder.
	*/
	if ((mp->msgflags & ALLOW_BEYOND) && strcmp(cp, seq_beyond)==0) {
		set_selected(mp, getbeyond(mp));
		return 1;
	}

	/*
	** Handle the special all sequence: replace `a' with `f-l'
	*/
	if (strcmp(cp, seq_all)==0) {
		cp = concat(seq_first, "-", seq_last, NULL);
	}

	if ((err = first = m_conv(mp, cp, FIRST)) <= 0)
		goto badmsg;

	cp = delimp;
	switch (*cp) {
	case '-':
		/* global range */
		cp++;
		if ((err = last = m_conv(mp, cp, LAST)) <= 0) {
badmsg:
			switch (err) {
			case BADMSG:
				advise(NULL, "no %s message", cp);
				break;

			case BADNUM:
				advise(NULL, "message %s doesn't exist", cp);
				break;

			case BADRNG:
				advise(NULL, "message %s out of range 1-%d",
						cp, mp->hghmsg);
				break;

			case BADLST:
badlist:
				advise(NULL, "bad message list %s", name);
				break;

			case BADNEW:
				advise(NULL, "folder full, no %s message",
						name);
				break;

			default:
				advise(NULL, "no messages match specification");
			}
			return 0;
		}

		if (last < first)
			goto badlist;
		if (*delimp)
			goto badelim;
		if (first > mp->hghmsg || last < mp->lowmsg) {
rangerr:
			advise(NULL, "no messages in range %s", name);
			return 0;
		}

		/* tighten the range to search */
		if (last > mp->hghmsg)
			last = mp->hghmsg;
		if (first < mp->lowmsg)
			first = mp->lowmsg;
		break;

	case ':':
		/* anchored range */
		cp++;
		if (*cp == '-') {
			convdir = -1;
			cp++;
		} else if (*cp == '+') {
			convdir = 1;
			cp++;
		}
		if ((range = atoi(bp = cp)) == 0)
			goto badlist;
		while (isdigit(*bp))
			bp++;
		if (*bp)
			goto badelim;
		if ((convdir > 0 && first > mp->hghmsg)
				|| (convdir < 0 && first < mp->lowmsg))
			goto rangerr;

		/* tighten the range to search */
		if (first < mp->lowmsg)
			first = mp->lowmsg;
		if (first > mp->hghmsg)
			first = mp->hghmsg;

		for (last = first; last >= mp->lowmsg && last <= mp->hghmsg;
				last += convdir)
			if (does_exist(mp, last) && (--range <= 0))
				break;
		if (last < mp->lowmsg)
			last = mp->lowmsg;
		if (last > mp->hghmsg)
			last = mp->hghmsg;
		if (last < first) {
			range = last;
			last = first;
			first = range;
		}
		break;

	case '\0':
		/* single name */
		/*
		** AFAICS, the only cases to reach here are:
		**     f, l, p, n, c, b
		** But I'm not sure.  --meillo
		*/
		/*
		** Single Message
		**
		** Check if message is in-range and exists.
		*/
		if (first > mp->hghmsg || first < mp->lowmsg ||
				!does_exist(mp, first)) {
			if (strcmp(name, seq_cur)==0)
				advise(NULL, "no current message");
			else
				/* this case seems to never be reached */
				advise(NULL, "message %d doesn't exist",
						first);
			return 0;
		}
		last = first;  /* range of 1 */
		break;

	default:
badelim:
		advise(NULL, "illegal argument delimiter: `%c'(0%o)",
				*delimp, *delimp);
		return 0;
		break;
	}

	/* Cycle through the range and select the messages that exist. */
	for (found=0; first <= last; first++) {
		if (does_exist(mp, first)) {
			set_selected(mp, first);
			found++;
		}
	}
	if (!found)
		goto rangerr;

	return 1;
}

/*
** Convert the various message names to their numeric values.
**
** 42  (any integer)
** p
** n
** f
** l
** c
*/
static int
m_conv(struct msgs *mp, char *str, int call)
{
	int i;
	unsigned char *cp, *bp;
	unsigned char buf[16];  /* for reserved sequence name */

	convdir = 1;
	cp = bp = str;

	/* Handle an integer */
	if (isdigit(*cp)) {
		while (isdigit(*bp))
			bp++;
		delimp = bp;
		i = atoi(cp);

		if (i <= mp->hghmsg)
			return i;
		else if (*delimp || call == LAST)
			return mp->hghmsg + 1;
		else if (mp->msgflags & ALLOW_BEYOND)
			return BADRNG;
		else
			return BADNUM;
	}

	for (bp = buf; isalpha(*cp) && (bp - buf < (int)sizeof(buf) - 1); ) {
		*bp++ = *cp++;
	}
	*bp++ = '\0';
	delimp = cp;

	/* Which one of the reserved names is it? */
	if (strcmp(buf, seq_first)==0) {
		return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
				mp->lowmsg : BADMSG);

	} else if (strcmp(buf, seq_last)==0) {
		convdir = -1;
		return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
				mp->hghmsg : BADMSG);

	} else if (strcmp(buf, seq_cur)==0) {
		return (mp->curmsg > 0 ? mp->curmsg : BADMSG);

	} else if (strcmp(buf, seq_prev)==0) {
		convdir = -1;
		for (i = (mp->curmsg <= mp->hghmsg) ?
				mp->curmsg - 1 : mp->hghmsg;
				i >= mp->lowmsg; i--) {
			if (does_exist(mp, i))
				return i;
		}
		return BADMSG;

	} else if (strcmp(buf, seq_next)==0) {
		for (i = (mp->curmsg >= mp->lowmsg) ?
				mp->curmsg + 1 : mp->lowmsg;
				i <= mp->hghmsg; i++) {
			if (does_exist(mp, i))
				return i;
		}
		return BADMSG;

	} else {
		return BADLST;
	}
}

/*
** Handle user defined sequences.
** They can take the following forms:
**
** seq
** seq:p
** seq:n
** seq:f
** seq:l
** seq:+42
** seq:-42
** seq:42
**
** (`42' being an arbitrary integer)
*/
static int
attr(struct msgs *mp, char *cp)
{
	unsigned char *dp;
	char *bp = NULL;
	int i, j;
	int found;
	int inverted = 0;
	int range = 0;  /* no range */
	int first = 0;

	/* hack for "c-..." */
	if (strcmp(cp, seq_cur)==0)
		return 0;
	/* "c:..." -- this code need to be rewritten... */
	if (strncmp(seq_cur, cp, strlen(seq_cur))==0 &&
			cp[strlen(seq_cur)] == ':') {
		return 0;
	}

	/* Check for sequence negation */
	if (!(dp = context_find(nsequence))) {
		dp = seq_neg;  /* use default */
	}
	if (*dp && strncmp(cp, dp, strlen(dp))==0) {
		inverted = 1;
		cp += strlen(dp);
	}

	convdir = 1;  /* convert direction */

	for (dp = cp; *dp && isalnum(*dp); dp++)
		continue;

	if (*dp == ':') {
		bp = dp++;
		range = 1;

		/*
		** seq:p  (or)
		** seq:n  (or)
		** seq:f  (or)
		** seq:l
		*/
		if (isalpha(*dp)) {
			if (strcmp(dp, seq_prev)==0) {
				convdir = -1;
				first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
					? mp->curmsg - 1 : mp->hghmsg;
			} else if (strcmp(dp, seq_next)==0) {
				convdir = 1;
				first = (mp->curmsg >= mp->lowmsg)
					? mp->curmsg + 1 : mp->lowmsg;
			} else if (strcmp(dp, seq_first)==0) {
				convdir = 1;
			} else if (strcmp(dp, seq_last)==0) {
				convdir = -1;
			} else
				return BADLST;
		} else {
			/*
			** seq:42  (or)
			** seq:+42 (or)
			** seq:-42
			*/
			if (*dp == '+')
				dp++;
			else if (*dp == '-') {
				dp++;
				convdir = -1;
			}
			if ((range = atoi(dp)) == 0)
				return BADLST;
			while (isdigit(*dp))
				dp++;
			if (*dp)
				return BADLST;
		}

		*bp = '\0';  /* temporarily terminate sequence name */
	}

	i = seq_getnum(mp, cp);  /* get index of sequence */

	if (bp)
		*bp = ':';  /* restore sequence name */
	if (i == -1)
		return 0;

	found = 0;  /* count the number we select for this argument */

	for (j = first ? first : (convdir > 0) ? mp->lowmsg : mp->hghmsg;
			j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) {
		if (does_exist(mp, j)
				&& inverted ? !in_sequence(mp, i, j) :
				in_sequence(mp, i, j)) {
			set_selected(mp, j);
			found++;

			/*
			** If we have a range, then break out
			** once we've found enough.
			*/
			if (range && found >= range)
				break;
		}
	}

	if (found)
		return found;

	if (first)
		return BADMSG;
	advise(NULL, "sequence %s %s", cp, inverted ? "full" : "empty");
	return -1;
}
