#include "args.h"

#include "config.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 */

#ifndef lint
static const char *rcsid = "$Header: /usr/local/src/mm/mm-0.94/mm/RCS/seq.c,v 1.4 2005/05/31 14:43:11 beebe Exp beebe $";
#endif

/*
 * seq.c - message sequence selection routines
 *
 *
 * Notes:
 *
 * Sequence constraint minima are inclusive and maxima are exclusive.  For
 * example, a 2000 byte message received "today" is "longer (than) 2000" and
 * "since today" but not "shorter (than) 2000" or "before today".
 */

#include "mm.h"
#include "parse.h"

#define cf cf_			/* avoid warnings about shadowed global */
#define used used_		/* avoid warnings about shadowed global */

#define CS (cf->sequence)
#define PS (cf->prev_sequence)
#undef RS				/* HP-UX already defined this */
#define RS (cf->read_sequence)

#define CSfirst (sequence_first(CS))
#define CSlast (sequence_last(CS))
#define CSinverted (sequence_inverted(CS))
#define PSfirst (sequence_first(PS))
#define PSlast (sequence_last(PS))
#define PSinverted (sequence_inverted(PS))
#define RSfirst (sequence_first(RS))
#define RSlast (sequence_last(RS))
#define RSinverted (sequence_inverted(RS))

/*
 * message selection keywords
 */

static keywrd seq_keys[] = {
    { "a", KEY_INV|KEY_ABR, 2 },	/* a --> all */
    { "after", 0, (keyval) SEQ_AFTER },
    { "all", 0, (keyval) SEQ_ALL },
    { "answered", 0, (keyval) SEQ_ANSWERED },
    { "before", 0, (keyval) SEQ_BEFORE },
    { "current", 0, (keyval) SEQ_CURRENT },
    { "deleted", 0, (keyval) SEQ_DELETED },
    { "flagged", 0, (keyval) SEQ_FLAGGED },
    { "from", 0, (keyval) SEQ_FROM },
    { "inverse", 0, (keyval) SEQ_INVERSE },
    { "keyword", 0, (keyval) SEQ_KEYWORD },
    { "l", KEY_INV|KEY_ABR, (keyval) 12 }, /* 12 --> last (groan) */
    { "last", 0, (keyval) SEQ_LAST },
    { "longer", 0, (keyval) SEQ_LONGER },
    { "new", 0, (keyval) SEQ_NEW },
    { "on", 0, (keyval) SEQ_ON },
    { "previous-sequence", 0, (keyval) SEQ_PREVIOUS },
    { "recent", 0, (keyval) SEQ_RECENT },
    { "seen", 0, (keyval) SEQ_SEEN },
    { "shorter", 0, (keyval) SEQ_SHORTER },
    { "since", 0, (keyval) SEQ_SINCE },
    { "subject", 0, (keyval) SEQ_SUBJECT },
    { "text", 0, (keyval) SEQ_TEXT },
    { "to", 0, (keyval) SEQ_TO },
    { "u", KEY_ABR|KEY_INV, (keyval) 29 }, /* u -> unseen */
    { "unanswered", 0, (keyval) SEQ_UNANSWERED },
    { "undeleted", 0, (keyval) SEQ_UNDELETED },
    { "unflagged", 0, (keyval) SEQ_UNFLAGGED },
    { "unkeyword", 0,  (keyval) SEQ_UNKEYWORD },
    { "unseen", 0, (keyval) SEQ_UNSEEN }
};

void header_print ARGS((int n));

static seq_node seq_stack[SEQ_STACK_SIZE];
static int seq_stackp;
#define stackinit() seq_stackp = -1, invert_sequence = 0, use_previous = 0
#define stacktop seq_stack[seq_stackp]
#define stackdata stacktop.data
#define stackrange stackdata.range
#define stackdates stackdata.dates
#define stackpush(x) if (++seq_stackp >= sizeof seq_stack/sizeof seq_stack[0])\
			ccmd_errmsg("Too many sequence constraints");\
		     else\
			seq_stack[seq_stackp].type = x;
#define stackpop() seq_stackp--
static int invert_sequence, use_previous;

static fdb *build_seq_chn ARGS((fdb *chn, const char *def, int *chnlen,
	int realseq));
static int seq_comma ARGS((int comma_seen));
static int seq_date ARGS((int n));
static int seq_int ARGS((int n));
static int seq_interpret_fdb ARGS((fdb *used));
static int seq_keyword ARGS((int n));
static int seq_misc ARGS((int n));
static int seq_num ARGS((int n));
static int seq_range ARGS((int n));
static int seq_string ARGS((int n));
static int seq_test ARGS((int n));
static int seq_test1 ARGS((int n, seq_node *node));
static int sequence_next1 ARGS((sequence_t S, int dir));
static void parse_seq ARGS((const char *def, fdb *chn, fdb **altused));

static int (*seq_fns[sizeof(seq_keys)/sizeof(seq_keys[0])]) ARGS((int n)) = {
    seq_date,				/* after */
    seq_misc,				/* all */
    seq_misc,				/* answered */
    seq_date,				/* before */
    seq_misc,				/* current */
    seq_misc,				/* deleted */
    seq_misc,				/* flagged */
    seq_string,				/* from */
    seq_misc,				/* inverse */
    seq_keyword,			/* keyword */
    seq_int,				/* last */
    seq_num,				/* longer */
    seq_misc,				/* new */
    seq_date,				/* on */
    seq_misc,				/* previous-sequence */
    seq_misc,				/* recent */
    seq_misc,				/* seen */
    seq_num,				/* shorter */
    seq_date,				/* since */
    seq_string,				/* subject */
    seq_string,				/* text */
    seq_string,				/* to */
    seq_misc,				/* unanswered */
    seq_misc,				/* undeleted */
    seq_misc,				/* unflagged */
    seq_keyword,			/* unkeyworded */
    seq_misc				/* unseen */
};

static keytab seq_keytab = { sizeof (seq_keys) / sizeof (keywrd), seq_keys };

static fdb seq_fdb_key =
    { _CMKEY, 0, 0, (pdat) &seq_keytab, "message sequence, ", NULL, NULL,
      "invalid message sequence type"};
static fdb seq_fdb_n =
    { _CMNUM, CM_SDH, 0, (pdat) 10, "message number\n\
  or range of message numbers, n:m\n\
  or range of message numbers, n-m\n\
  or range of message numbers, n+m (m messages beginning with n)",
    NULL, NULL, "Invalid message sequence" };
static fdb seq_fdb_dot =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) ".",
	  "\".\" to specify the current message" };
static fdb seq_fdb_star =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) "*",
	  "\"*\" to specify the last message" };
static fdb seq_fdb_percent =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) "%", NULL };
static fdb seq_fdb_colon =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) ":",
	  "\":\" to specify a message range",
      NULL, NULL, "Invalid message range delimiter"};
static fdb seq_fdb_dash =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) "-",
	  "\"-\" to specify a message range",
      NULL, NULL, "Invalid sequence range delimiter"};
static fdb seq_fdb_hash =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) "#", NULL , NULL, NULL,
	  "Invalid message range delimiter" };
static fdb seq_fdb_plus =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) "+",
	  "\"+\" to specify a message range",
      NULL, NULL, "Invalid sequence range delimiter" };
static fdb seq_fdb_comma =
    { _CMTOK, CM_SDH|TOK_WAK, 0, (pdat) ",",
	  "\",\" and another message sequence",
      NULL, NULL, "Expected a \",\""};
static fdb seq_fdb_timeonly =
    { _CMTAD, CM_SDH|DTP_N24|DTP_NDA|DTP_NDW|DTP_NSP, nil, nil,
	  "date and time", NULL, NULL, "Invalid date and time" };
static fdb seq_fdb_dateonly =
    { _CMTAD, CM_SDH|DTP_N24|DTP_NTI|DTP_NDW|DTP_NSP, nil, nil,
	  "date and time", NULL, NULL, "Invalid date" };

#if 0					/* no longer used */
static fdb seq_fdb_tad = { _CMTAD, CM_SDH|DTP_N24|DTP_NDW|DTP_NSP };
#endif

extern brktab rembrk;		/* remote user name break table */
extern keytab timetab;
static fdb seq_fdb_daykey = { _CMKEY, CM_SDH, nil, (pdat) &timetab,
			      "day, ", NULL, NULL, "Invalid day" };

#define SEQ_FDBS &seq_fdb_n, &seq_fdb_dot, &seq_fdb_star, &seq_fdb_key, \
	&seq_fdb_percent, &seq_fdb_comma

#if 0					/* no longer used */
#define SEQ_TAD_FDBS &seq_fdb_tad, &seq_fdb_dateonly, &seq_fdb_timeonly, \
	&seq_fdb_daykey
#endif

static char msgbuf[BUFLEN + 100]; /* adequate for messages here, plus atmbuf */

/* miscellaneous easy sequence functions */
static int
#if HAVE_STDC
seq_keyword(int n)
#else /* K&R style */
seq_keyword(n)
int n;
#endif /* HAVE_STDC */
{
    stackpush(n);
    strcpy(stackdata.s,parse_old_keywords());
    parse_seq ((char *) nil, (fdb*)nil, (fdb**)nil);
    return (true);
}

static int
#if HAVE_STDC
seq_misc(int n)
#else /* K&R style */
seq_misc(n)
int n;
#endif /* HAVE_STDC */
{
    stackpush(n);
    switch (n) {
      case SEQ_PREVIOUS:
	use_previous = true;
	break;
      case SEQ_INVERSE:
	invert_sequence = !invert_sequence; /* XXX should be a seq_node */
	break;
      case SEQ_ALL:
      case SEQ_ANSWERED:
      case SEQ_CURRENT:
      case SEQ_DELETED:
      case SEQ_FLAGGED:
      case SEQ_NEW:
      case SEQ_RECENT:
      case SEQ_SEEN:
      case SEQ_UNANSWERED:
      case SEQ_UNDELETED:
      case SEQ_UNFLAGGED:
      case SEQ_UNSEEN:
	break;				/* XXX get rid of these */
      default:
	sprintf(msgbuf,"Unknown sequence constraint (%d) in seq_misc", n);
	ccmd_errmsg (msgbuf);
    }
    parse_seq ((char *) nil, (fdb*)nil, (fdb**)nil);
    return (true);
}

#undef used

static int
#if HAVE_STDC
seq_date (int n)
#else /* K&R style */
seq_date (n)
int n;
#endif /* HAVE_STDC */
{
    fdb *fdbs;

    stackpush(n);

    fdbs = fdbchn (&seq_fdb_daykey, &seq_fdb_dateonly, (fdb*) nil);
    parse (fdbs, &pv, &used);

    switch (used->_cmfnc) {
      case _CMKEY:
	stackdates.first = key2time(pv._pvint);
	break;
      case _CMTAD:
	stackdates.first = datimetogmt (&pv._pvtad);
	break;
      default:
	sprintf(msgbuf,"Bad parse function (%d) in seq_date", used->_cmfnc);
	ccmd_errmsg (msgbuf);
    }

    /* got date, now look for additional time field or something else */
    fdbs = fdbchn (&seq_fdb_timeonly, SEQ_FDBS, &cfm_fdb, (fdb *) nil);
    parse (fdbs, &pv, &used);
    if (n == SEQ_ON)
	stackdates.last = stackdates.first + (24 * 60 * 60);
    if ((n == SEQ_AFTER) && (used->_cmfnc != _CMTAD))
	stackdates.first += (24*60*60);
    switch (used->_cmfnc) {
      case _CMCFM:
	return true;
      case _CMTAD:
	stackdates.first += (((pv._pvtad._dthr*60)+pv._pvtad._dtmin)*60) +
	    pv._pvtad._dtsec;
	parse_seq ((char *) nil, (fdb*)nil, (fdb**)nil);
	return true;
      default:
	seq_interpret_fdb(used);
	return true;
    }
}

static int
#if HAVE_STDC
seq_comma (int comma_seen)
#else /* K&R style */
seq_comma (comma_seen)
int comma_seen;
#endif /* HAVE_STDC */
{
    if (!comma_seen)
	if (parse (fdbchn (&seq_fdb_comma, &cfm_fdb, nil), &pv, &used) ==
	    CMxOK)
	    switch (used->_cmfnc) {
	      case _CMCFM:
		return true;
	      case _CMTOK:
		break;
	    }
    parse_seq ("", (fdb*)nil, (fdb**)nil);
    return true;
}

static int
#if HAVE_STDC
seq_num (int n)
#else /* K&R style */
seq_num (n)
int n;
#endif /* HAVE_STDC */
{
    noise ("than");
    p_num ("number of characters");
    stackpush(n);
    stackrange.n = pv._pvint;
    parse_seq ((char *)nil, (fdb*)nil, (fdb**)nil);
    return true;
}

static int
#if HAVE_STDC
seq_int (int n)
#else /* K&R style */
seq_int (n)
int n;
#endif /* HAVE_STDC */
{
    static fdb numfdb = { _CMNUM, CM_SDH, 0, (pdat) 10, NULL, NULL, NULL,
			  "invalid number" };
    numfdb._cmhlp = "decimal number";
    numfdb._cmdef = "1";

    noise("number of messages");
    parse(&numfdb, &pv, &used);
    stackpush(n);
    stackrange.n = pv._pvint;
    parse_seq ((char *)nil, (fdb*)nil, (fdb**)nil);
    return true;
}

static int
#if HAVE_STDC
seq_string (int n)
#else /* K&R style */
seq_string (n)
int n;
#endif /* HAVE_STDC */
{
    static fdb string_fdb = { _CMFLD, CM_SDH|FLD_EMPTY|FLD_WAK, nil, nil,
			      "string", NULL, &rembrk, "Invalid String" };
    static fdb qst_fdb = { _CMQST, 0, nil, nil, nil, nil, nil,
			       "Invalid String" };
    int len;

    noise ("string");
    parse (fdbchn (&qst_fdb, &string_fdb, (fdb *) nil), &pv, &used);
    if ((len = strlen (atmbuf)) < 1)
	ccmd_errmsg ("Invalid string.  Try enclosing it in quotes");
    if (len > (sizeof stackdata.s - 1))
    {
	sprintf(msgbuf,"String too long - \"%s\"", atmbuf);
	ccmd_errmsg (msgbuf);

    }
    stackpush(n);
    strcpy (stackdata.s, atmbuf);
    parse_seq ((char *) nil, (fdb*)nil, (fdb**)nil);
    return true;
}

#undef cf

static int
#if HAVE_STDC
seq_range (int n)
#else /* K&R style */
seq_range (n)
int n;
#endif /* HAVE_STDC */
{
    fdb *fdbs = fdbchn (&seq_fdb_colon, &seq_fdb_dash, &seq_fdb_plus,
			&seq_fdb_hash, &seq_fdb_comma, &cfm_fdb, nil);

    if (n < 1 || n > cf->count)
	ccmd_errmsg ("Number out of range");
    stackpush(SEQ_RANGE);
    stackrange.n = stackrange.m = n;
    if (parse (fdbs, &pv, &used) == CMxOK) {
	if (used == &cfm_fdb) {
	    return true;		/* return just 1 msg num */
	}
	if (used->_cmfnc == _CMTOK)
	    switch (used->_cmdat[0]) {
	      case ',':
		parse_seq ("", (fdb*)nil, (fdb**)nil);
		return true;
		break;
	      case '+':
	      case '#':
		stackrange.m += p_num ("number of messages") - 1;
		break;
	      case '-':
	      case ':':
		{
		    static fdb numfdb = { _CMNUM, CM_SDH, 0, (pdat) 10,
					      "end of range", NULL, NULL,
					      "bad range end" };
		    parse (fdbchn (&numfdb, &seq_fdb_star, &seq_fdb_percent,
				   nil), &pv, &used);
		    stackrange.m = ((used == &seq_fdb_star) ||
				    (used == &seq_fdb_percent)) ?
				      cf->count : pv._pvint;
		}
		break;
	      default:
		ccmd_errmsg ("Internal error in token evaluation");
	    }
	if (stackrange.m < stackrange.n)
	    ccmd_errmsg ("Invalid range");
/*	seq_comma (nil);*/
	parse_seq ((char *)nil, (fdb*)nil, (fdb**)nil);
	return true;
    }
    else
	return false;
}

/*
 * chain together various pieces for a sequence parse
 */
static fdb *
#if HAVE_STDC
build_seq_chn (fdb *chn, const char *def, int *chnlen, int realseq)
            				/* sequence or confirm */
#else /* K&R style */
build_seq_chn (chn, def, chnlen, realseq)
fdb *chn;
const char *def;
int *chnlen;
int realseq;				/* sequence or confirm */
#endif /* HAVE_STDC */
{
    fdb *f;

    *chnlen = 0;

    (*chnlen)++;			/* at least one element */

    /* chain the normal stuff at the end of this chain */
    for (f = chn; f->_cmlst != nil; f = f->_cmlst)
	(*chnlen)++;
    /* f now points to the last element of the chain */
    if (!realseq)			/* just stick in a confirm */
        fdbchn (f, &cfm_fdb, (fdb *) nil);
    else if (def)	 		/* non-nil def means required input */
	fdbchn (f, SEQ_FDBS, (fdb *) nil);
    else
	fdbchn (f, SEQ_FDBS, &cfm_fdb, (fdb *) nil);
    return (chn);
}

/*
 * You want parse_sequence from outside this module...
 *
 * recursively parse a message sequence.  if def is non-null, a response
 * will be required; if the string it points to isn't null, use that as a
 * default string.  Thus:
 *
 * 	parse_seq (nil, ...);		parse sequence or confirm
 * 	parse_seq ("", ...);		parse required sequence
 * 	parse_seq ("all", ...);		parse sequence, confirm -> all
 */

static void
#if HAVE_STDC
parse_seq (const char *def, fdb *chn, fdb **altused)
#else /* K&R style */
parse_seq (def, chn, altused)
const char *def;
fdb *chn;
fdb **altused;
#endif /* HAVE_STDC */
{
    fdb *fdbs, *f;
    int l = 0;
    int i;

    seq_fdb_n._cmdef = (def && *def) ? def : nil; /* XXX */

    if (!chn) {
	if (def)			/* non-nil def means required input */
	    fdbs = fdbchn (SEQ_FDBS, (fdb *) nil);
	else
	    fdbs = fdbchn (SEQ_FDBS, &cfm_fdb, (fdb *) nil);
    }
    else
	fdbs = build_seq_chn (chn, def, &l, TRUE); /* chain extras in front */

    parse (fdbs, &pv, &used);	/* parse the first token */
    seq_fdb_n._cmdef = nil;		/* XXX */
    for (i = 0, f = chn; i < l; i++, f = f->_cmlst) {
	if (used == f) {		/* one of the chain elements */
	    *altused = used;
	    return;
	}
    }

    /* otherwise, it was really a sequence parse */
    seq_interpret_fdb (used);
}

/*
 * parse a message sequence and save the result as the "current sequence"
 *
 * 	parse_sequence (nil, ...);	parse sequence or confirm
 * 	parse_seqeunce ("", ...);	parse required sequence
 * 	parse_sequence ("all", ...);	parse sequence, confirm -> all
 *
 * returns false if changes the message sequence is prohibited by the
 * current context, true otherwise.
 *
 * If an fdb chain is passed, link this to the front of our sequence.
 * After parsing, fill in used with the fdb we parsed if it was one of the
 * passed ones (pval in global pv), or NULL if we parsed a sequence.
 * This is done so optional items can be parsed before a sequence.
 * (chn should be NULL iff altused is)
 *
 */

int
#if HAVE_STDC
parse_sequence (const char *def, fdb *chn, fdb **altused)
                			/* default response */
#else /* K&R style */
parse_sequence (def, chn, altused)
const char *def;			/* default response */
fdb *chn;
fdb **altused;
#endif /* HAVE_STDC */
{
    int i, l;
    sequence_t temp;

    /*
     * MM-20 compatibility: don't allow user to change the message
     * sequence in the middle of a read or send command.  We can
     * relax this later, once we redefine the semantics of "previous-sequence".
     */

    if (altused)
	*altused = NULL;		/* no fdb yet */

    if (mode != MM_TOP_LEVEL) {
	if (chn) {			/* parse optional stuff */
	    parse (build_seq_chn (chn, def, &l, FALSE), &pv, &used);
	    if (used != &cfm_fdb)
		*altused = used;
	}
	else
	    confirm();
	return false;
    }

    noise ("messages");

    stackinit();

    parse_seq (def, chn, altused);	/* do the actual work */
    if (altused && *altused)		/* sequence not parsed */
	return true;

    /*
     * Save the current sequence as the "previous-sequence", using
     * a swap to avoid free and malloc.
     */
    temp = PS;
    PS = CS;
    CS = temp;

    clear_sequence (CS);

    for (i = 1; i <= cf->count; i++) {
	if (use_previous) {
	    if (i < PSfirst)
		i = PSfirst;
	    else if (i > PSlast)
		break;
	    if (!in_sequence (PS, i))
		continue;
	}
	if (!seq_test (i))
	    continue;
	setbit (sequence_bits(CS), i);
	if (CSfirst == 0)
	    CSfirst = i;
	CSlast = i;
    }
    if (CSlast < CSfirst)		/* sequence is empty */
	CSlast = CSfirst = 0;

    sequence_inverted(CS) = invert_sequence;
    if (use_previous && sequence_inverted(PS))
	sequence_inverted(CS) = !sequence_inverted(CS);
    if (debug)
	fprintf (stderr, "sequence: first = %d, last = %d, current = %d\n",
		 (int)CSfirst, (int)CSlast, (int)cf->current);
    return true;
}

/*
 * sequence_init - allocate and initialize sequence bitmaps
 *
 */

#define cf cf_			/* avoid warnings about shadowed global */

int
#if HAVE_STDC
sequence_init (msgvec *cf)
#else /* K&R style */
sequence_init (cf)
msgvec *cf;
#endif /* HAVE_STDC */
{
    int n = cf->count / NBBY + 1;
    if ((CS = (sequence_t) malloc (sizeof (*CS))) &&
	(sequence_bits (CS) = (unsigned char *) malloc (n)) &&
	(PS = (sequence_t) malloc (sizeof (*PS))) &&
	(sequence_bits (PS) = (unsigned char *) malloc (n)) &&
	(RS = (sequence_t) malloc (sizeof (*RS))) &&
	(sequence_bits (RS) = (unsigned char *) malloc (n))) {

	sequence_file(CS) = cf;
	sequence_file(PS) = cf;
	sequence_file(RS) = cf;

	clear_sequence (CS);
	clear_sequence (PS);
	clear_sequence (RS);
	return true;
    }
    return false;
}

void
#if HAVE_STDC
sequence_free (msgvec *cf)
#else /* K&R style */
sequence_free (cf)
msgvec *cf;
#endif /* HAVE_STDC */
{
    free_sequence(CS);
    free_sequence(PS);
    free_sequence(RS);
}

/*
 * Make sequence S1 a copy of sequence S2.
 */
void
#if HAVE_STDC
copy_sequence (sequence_t S1, sequence_t S2)
#else /* K&R style */
copy_sequence (S1, S2)
sequence_t S1, S2;
#endif /* HAVE_STDC */
{
    sequence_first (S1) = sequence_first (S2);
    sequence_last (S1) = sequence_last (S2);
    sequence_inverted (S1) = sequence_inverted (S2);
    sequence_file (S1) = sequence_file (S2);
    bcopy (sequence_bits(S2), sequence_bits(S1),
	   sequence_last (S1) / NBBY + 1);
}

/*
 * set the current message number, and return it.  The result will be
 * zero if the message sequence is empty.
 */
int
#if HAVE_STDC
sequence_start (sequence_t S)
#else /* K&R style */
sequence_start (S)
sequence_t S;
#endif /* HAVE_STDC */
{
    if (!valid_sequence(S))
	return 0;
    return (sequence_file(S)->current =
	    sequence_inverted(S) ? sequence_last(S) : sequence_first(S));
}

/* XXX make these macros later on */
int
#if HAVE_STDC
sequence_next (sequence_t S)
#else /* K&R style */
sequence_next (S)
sequence_t S;
#endif /* HAVE_STDC */
{
    return sequence_next1 (S, 1);
}

int
#if HAVE_STDC
sequence_prev (sequence_t S)
#else /* K&R style */
sequence_prev (S)
sequence_t S;
#endif /* HAVE_STDC */
{
    return sequence_next1 (S, -1);
}

/*
 * advance to the next message in sequence S.
 * dir is 1 to move forward, -1 to move backards.
 */
static int
#if HAVE_STDC
sequence_next1 (sequence_t S, int dir)
#else /* K&R style */
sequence_next1 (S, dir)
sequence_t S;
int dir;
#endif /* HAVE_STDC */
{
    int n;
    if (!valid_sequence(S))
	return 0;

    n = sequence_file(S)->current;	/* get current message */

    if (sequence_inverted(S))		/* change direction if inverse seq */
	dir = -dir;

    while ((n += dir) && (n <= sequence_last(S)))
	if (in_sequence (S, n))
	    return (sequence_file(S)->current = n);
    return 0;
}


/*
 * find the next message in the sequence
 * arg is +1 to move up in the sequence, and -1 to move back
 * OBSOLETE
 */

#undef cf

int
#if HAVE_STDC
next_current (int up)			/* was next_current */
#else /* K&R style */
next_current (up)			/* was next_current */
int up;
#endif /* HAVE_STDC */
{
    int n = cf->current;

    up = (up < 0) ? -1 : 1;
    if (CSinverted)
	up = -up;

    /* XXX this could be a lot faster! */
    while ((n += up) && (n > 0) && (n <= CSlast))
	if (in_sequence (CS, n)) {
	    cf->current = n;
	    return n;
	}
    return 0;
}


int
#if HAVE_STDC
seq_next (int n)
#else /* K&R style */
seq_next (n)
int n;
#endif /* HAVE_STDC */
{
    if (CSinverted)
    {
	if (n < 1)
	    return CSlast;
	else
	{
	    while (n-- > CSlast)
	    {
		if (in_sequence (CS, n))
		    return n;
	    }
	}
    }
    else
    {
	if (n < 1)
	    return CSfirst;
	else
	{
	    while (n++ < CSlast)
	    {
		if (in_sequence (CS, n))
		    return n;
	    }
	}
    }
    return 0;
}

int
#if HAVE_STDC
seq_print (int nl)
#else /* K&R style */
seq_print (nl)
int nl;
#endif /* HAVE_STDC */
{
    int lastn, lastprinted;
    int n = sequence_start (cf->sequence);

    if (!n)
	return (0);

    printf (" %d", lastprinted = n);

    lastn = 0;				/* initialize this */
    while ((n = sequence_next (cf->sequence), n)) {
	if (n == (lastprinted + 1)) {
	    lastn = lastprinted = n;
	    continue;
	}
	if (lastn) {
	    printf (":%d", lastn);
	    lastn = 0;
	}
	printf (", %d", lastprinted = n);
    }
    if (lastn)
	printf (":%d", lastn);
    if (nl)
	printf ("\n");
    return 1;
}

#define used used_		/* avoid warnings about shadowed global */

static int
#if HAVE_STDC
seq_interpret_fdb (fdb *used)
#else /* K&R style */
seq_interpret_fdb (used)
fdb *used;
#endif /* HAVE_STDC */
{
    int n;

    switch (used->_cmfnc) {
      case _CMCFM:
	return true;
      case _CMKEY:
	n = pv._pvint;
	if (seq_fns[n])
	    (*seq_fns[n]) (n);		/* call handler function */
	else
	    parse_seq ((char *) nil, (fdb*)nil, (fdb**)nil);
	break;
      case _CMTOK:
	switch (used->_cmdat[0]) {
	  case '.':
	    if (seq_range (cf->current)) /* XXX */
		return true;
	    break;
	  case '*':
	  case '%':
	    if (seq_range (cf->count))	/* XXX */
		return true;
	    break;
	  case ',':
	    parse_seq ("", (fdb*)nil, (fdb**)nil);
	    return true;
	  default:
	    sprintf(msgbuf,"Invalid token '%s' parsed", used->_cmdat);
	    ccmd_errmsg(msgbuf);
	}
	break;
      case _CMNUM:
	if (seq_range (pv._pvint))
	    return true;
	seq_comma (true);
	break;
      default:
	sprintf(msgbuf,"Got an unexpected fdb in seq_interpret_fdb (%d)",
	       (int)used->_cmfnc);
	ccmd_errmsg (msgbuf);
    }
    return true;
}

FILE *header_pipe = NULL;

void
#if HAVE_STDC
cmd_headers(int n)
#else /* K&R style */
cmd_headers(n)
int n;
#endif /* HAVE_STDC */
{
    if (!check_cf (O_RDONLY))
	return;
    header_pipe = cmcsb._cmoj ? cmcsb._cmoj : stdout; /* don't write to NULL */
    if (parse_sequence ("current", nil, nil)) {	/* count how many headers */
	int count,k;
	for (count = 0, k = sequence_start (cf->sequence);
	     k && count < cmcsb._cmrmx;
	     k = sequence_next (cf->sequence))
	    ++count;
	if (count >= cmcsb._cmrmx)
	    header_pipe = more_pipe_open(header_pipe,crt_filter);
    }
    sequence_loop (header_print);
    if ((header_pipe == cmcsb._cmoj) ||
	(header_pipe == stdout))	/* didn't open any pipe */
	fflush(header_pipe);		/* ... since we're not closing it */
    else
	more_pipe_close(header_pipe);
    header_pipe = NULL;
}

void
#if HAVE_STDC
header_print(int n)
#else /* K&R style */
header_print(n)
int n;
#endif /* HAVE_STDC */
{
    static int header_count = 0;

    switch (n) {
      case 0:
	header_count = 0;
	break;
      case -1:
	if (debug)
	    printf ("Matching headers = %d\n", header_count);
	break;
      default:
	header_count++;
	header_summary (n, header_pipe, 0); /* 0 => "header command" */
    }
}

/*
 * Apply a function to each message in the current message sequence.
 *
 * In addition to valid message numbers, the function is also called
 * with zero to indicate the beginning of a sequence, and -1 to indicate
 * that the sequence has been exhausted.
 */

void
#if HAVE_STDC
sequence_loop (void (*fn) ARGS((int n)))
#else /* K&R style */
sequence_loop (fn)
void (*fn) ARGS((int n));
#endif /* HAVE_STDC */
{
    int i;

    (*fn) (0);				/* initialize the function */

    if (mode == MM_TOP_LEVEL) {
	sequence_t S = CS;		/* use current sequence */

	if (valid_sequence (S)) {
	    if (sequence_inverted (S))	/* run backwards */
		for (i = sequence_last (S); i >= sequence_first (S); i--) {
		    if (in_sequence (S, i)) {
			cf->current = i;
			(*fn) (i);
		    }
		}
	    else			/* run forwards */
		for (i = sequence_first(S); i <= sequence_last(S); i++)
		    if (in_sequence (S, i)) {
			cf->current = i;
			(*fn) (i);
		    }
	}
    }
    else
	if (cf->current)
	    (*fn) (cf->current);	/* just use current message */

    (*fn) (-1);				/* tell function we're done */
}

/*
 * Test whether message n is part of the user's selection.
 * This is pretty awful, but it'll do until we finish defining
 * the message sequence grammar (at which point we'll need an
 * and/or tree anyway).
 */

static int
#if HAVE_STDC
seq_test (int n)			/* message number */
#else /* K&R style */
seq_test (n)
int n;					/* message number */
#endif /* HAVE_STDC */
{
    int i, inrange = false, haverange = false;

    /*
     * We test ranges first as a (lame) optimization.
     * Obviously this could be a lot faster.
     */

    for (i = 0; i <= seq_stackp; i++)
	if (seq_stack[i].type == SEQ_RANGE) {
	    haverange = true;
	    if (seq_test1 (n, &seq_stack[i]) == false)
		continue;
	    inrange = true;
	    break;
	}

    if (haverange && !inrange)
	return false;

    for (i = 0; i <= seq_stackp; i++)
	if (seq_stack[i].type != SEQ_RANGE)
	    if (seq_test1 (n, &seq_stack[i]) == false)
		return false;

    return true;
}

static int
#if HAVE_STDC
seq_test1(int n, seq_node *node)
#else /* K&R style */
seq_test1(n, node)
int n;
seq_node *node;
#endif /* HAVE_STDC */
{
    message *m = &cf->msgs[n];
    switch (node->type) {
      case SEQ_SINCE:
      case SEQ_AFTER:
	return (m->date >= node->data.dates.first);
      case SEQ_BEFORE:
	return (m->date < node->data.dates.first);
      case SEQ_INVERSE:
      case SEQ_ALL:
	return true;		/* XXX should not get here */
      case SEQ_ANSWERED:
	return (m->flags & M_ANSWERED);
      case SEQ_CURRENT:
	return (cf->current == n);
      case SEQ_DELETED:
	return (m->flags & M_DELETED);
      case SEQ_FLAGGED:
	return (m->flags & M_FLAGGED);
      case SEQ_FROM:
	if (search_header ("from", node->data.s, m))
	    return true;
	return false;
      case SEQ_KEYWORD:
	return(lookup_keyword(node->data.s, m->keywords));
      case SEQ_LAST:
	return (cf->count < n + node->data.range.n);
      case SEQ_SHORTER:
	return (m->size < node->data.range.n);
      case SEQ_LONGER:
	return (m->size >= node->data.range.n);
      case SEQ_NEW:
	return (!(m->flags & M_SEEN) && (m->flags & M_RECENT));
      case SEQ_ON:
	return (m->date >= node->data.dates.first &&
		m->date <= node->data.dates.last);
      case SEQ_PREVIOUS:
	return true;			/* XXX caller has to check... */
      case SEQ_RECENT:
	return (m->flags & M_RECENT);
      case SEQ_SEEN:
	return (m->flags & M_SEEN);
      case SEQ_SUBJECT:
	if (search_header ("subject", node->data.s, m))
	    return true;
	return false;
      case SEQ_TEXT:
	if (search_text (node->data.s, m))
	    return true;
	return false;
      case SEQ_TO:
	if ((search_header ("to", node->data.s, m)) ||
	    (search_header ("cc", node->data.s, m)) ||
	    (search_header ("bcc", node->data.s, m)))
	    return true;
	return false;
      case SEQ_UNANSWERED:
	return (!(m->flags & M_ANSWERED));
      case SEQ_UNDELETED:
	return (!(m->flags & M_DELETED));
      case SEQ_UNFLAGGED:
	return (!(m->flags & M_FLAGGED));
      case SEQ_UNKEYWORD:
	return(!lookup_keyword(node->data.s, m->keywords));
      case SEQ_UNSEEN:
	return (!(m->flags & M_SEEN));
      case SEQ_RANGE:
	return ((n >= node->data.range.n) &&
		(n <= node->data.range.m));
    }
    ccmd_errmsg ("Unknown sequence constraint type encountered");
    return (0);
}


#ifdef SEQDEBUG
char *FNS[] = {
    "no function",
    "cmcfm",
    "cmkey",
    "cmnum",
    "cmqst",
    "cmnoi",
    "cmtxt",
    "cmfld",
    "cmswi",
    "cmtok",
    "cmtad",
    "cmfil",
    "cmusr",
    "cmgrp",
    "cmpara"
};

void
#if HAVE_STDC
printfdb(fdb *fdbs)
#else /* K&R style */
printfdb(fdbs)
fdb *fdbs;
#endif /* HAVE_STDC */
{
    if (fdbs == nil) {
	printf ("nil\n");
	return;
    }
    printf ("%s", FNS[fdbs->_cmfnc]);
    if (fdbs->_cmhlp)
	printf (" help='%s'", fdbs->_cmhlp);
    if (fdbs->_cmdef)
	printf (" def='%s'", fdbs->_cmdef);
    printf ("\n");
    printfdb(fdbs->_cmlst);
}

#if 0					/* no longer used */
void seq_print ARGS((seq_node *node, int last));

void
#if HAVE_STDC
seq_print (seq_node *node, int last)
#else /* K&R style */
seq_print (node,last)
seq_node *node;
int last;
#endif /* HAVE_STDC */
{
    int i;

    for (i = 0; i <= last; i++) {
	switch (node[i].type) {
	  case SEQ_AFTER:
	    printf ("after %s", daytime (node[i].data.dates.first)); break;
	  case SEQ_ALL:
	    printf ("all"); break;
	  case SEQ_ANSWERED:
	    printf ("answered"); break;
	  case SEQ_BEFORE:
	    printf ("before %s", daytime (node[i].data.dates.first)); break;
	  case SEQ_CURRENT:
	    printf ("current"); break;
	  case SEQ_DELETED:
	    printf ("deleted"); break;
	  case SEQ_FLAGGED:
	    printf ("flagged"); break;
	  case SEQ_FROM:
	    printf ("from \"%s\"", node[i].data.s); break;
	  case SEQ_INVERSE:
	    printf ("inverse"); break;
	  case SEQ_KEYWORD:
	    printf ("keyword"); break;
	  case SEQ_LAST:
	    printf ("last"); break;
	  case SEQ_LONGER:
	    printf ("longer than %d chars", node[i].data.range.n); break;
	  case SEQ_NEW:
	    printf ("new (recent and unseen)"); break;
	  case SEQ_ON:
	    printf ("between %s", daytime (node[i].data.dates.first));
	    printf (" and %s", daytime (node[i].data.dates.last)); break;
	  case SEQ_PREVIOUS:
	    printf ("previous sequence"); break;
	  case SEQ_RECENT:
	    printf ("recent"); break;
	  case SEQ_UNSEEN:
	    printf ("seen");
	  case SEQ_SHORTER:
	    printf ("shorter than %d chars", node[i].data.range.n); break;
	  case SEQ_SINCE:
	    printf ("since %s", daytime (node[i].data.dates.first)); break;
	  case SEQ_SUBJECT:
	    printf ("subject \"%s\"", node[i].data.s); break;
	  case SEQ_TEXT:
	    printf ("text \"%s\"", node[i].data.s); break;
	  case SEQ_TO:
	    printf ("to \"%s\"", node[i].data.s); break;
	  case SEQ_UNANSWERED:
	    printf ("not answered"); break;
	  case SEQ_UNDELETED:
	    printf ("not deleted"); break;
	  case SEQ_UNFLAGGED:
	    printf ("not flagged"); break;
	  case SEQ_UNSEEN:
	    printf ("not seen");
	  case SEQ_RANGE:
	    if (node[i].data.range.n != node[i].data.range.m)
		printf ("%d:%d", node[i].data.range.n, node[i].data.range.m);
	    else
		printf ("%d", node[i].data.range.n);
	}
	printf ("\n");
    }
}
#endif
#endif /*SEQDEBUG*/
