/*
 * picksbr.c -- routines to help pick along...
 *
 * $Id: picksbr.c,v 1.7 2002/07/02 22:09:15 kenh Exp $
 *
 * 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/tws.h>
#include <h/picksbr.h>

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef TM_IN_SYS_TIME
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

static struct swit parswit[] = {
#define	PRAND                   0
    { "and", 0 },
#define	PROR                    1
    { "or", 0 },
#define	PRNOT                   2
    { "not", 0 },
#define	PRLBR                   3
    { "lbrace", 0 },
#define	PRRBR                   4
    { "rbrace", 0 },
#define	PRCC                    5
    { "cc  pattern", 0 },
#define	PRDATE                  6
    { "date  pattern", 0 },
#define	PRFROM                  7
    { "from  pattern", 0 },
#define	PRSRCH                  8
    { "search  pattern", 0 },
#define	PRSUBJ                  9
    { "subject  pattern", 0 },
#define	PRTO                   10
    { "to  pattern", 0 },
#define	PROTHR                 11
    { "-othercomponent  pattern", 15 },
#define	PRAFTR                 12
    { "after date", 0 },
#define	PRBEFR                 13
    { "before date", 0 },
#define	PRDATF                 14
    { "datefield field", 5 },
    { NULL, 0 }
};

/* DEFINITIONS FOR PATTERN MATCHING */

/*
 * We really should be using re_comp() and re_exec() here.  Unfortunately,
 * pick advertises that lowercase characters matches characters of both
 * cases.  Since re_exec() doesn't exhibit this behavior, we are stuck
 * with this version.  Furthermore, we need to be able to save and restore
 * the state of the pattern matcher in order to do things "efficiently".
 *
 * The matching power of this algorithm isn't as powerful as the re_xxx()
 * routines (no \(xxx\) and \n constructs).  Such is life.
 */

#define	CCHR	2
#define	CDOT	4
#define	CCL	6
#define	NCCL	8
#define	CDOL	10
#define	CEOF	11

#define	STAR	01

#define LBSIZE  1024
#define	ESIZE	256


static char linebuf[LBSIZE + 1];

/* the magic array for case-independence */
static char cc[] = {
	0000,0001,0002,0003,0004,0005,0006,0007,
	0010,0011,0012,0013,0014,0015,0016,0017,
	0020,0021,0022,0023,0024,0025,0026,0027,
	0030,0031,0032,0033,0034,0035,0036,0037,
	0040,0041,0042,0043,0044,0045,0046,0047,
	0050,0051,0052,0053,0054,0055,0056,0057,
	0060,0061,0062,0063,0064,0065,0066,0067,
	0070,0071,0072,0073,0074,0075,0076,0077,
	0100,0141,0142,0143,0144,0145,0146,0147,
	0150,0151,0152,0153,0154,0155,0156,0157,
	0160,0161,0162,0163,0164,0165,0166,0167,
	0170,0171,0172,0133,0134,0135,0136,0137,
	0140,0141,0142,0143,0144,0145,0146,0147,
	0150,0151,0152,0153,0154,0155,0156,0157,
	0160,0161,0162,0163,0164,0165,0166,0167,
	0170,0171,0172,0173,0174,0175,0176,0177,
};

/*
 * DEFINITIONS FOR NEXUS
 */

#define	nxtarg()	(*argp ? *argp++ : NULL)
#define	prvarg()	argp--

#define	padvise		if (!talked++) advise

struct nexus {
    int (*n_action)();

    union {
	/* for {OR,AND,NOT}action */
	struct {
	    struct nexus *un_L_child;
	    struct nexus *un_R_child;
	} st1;

	/* for GREPaction */
	struct {
	    int   un_header;
	    int   un_circf;
	    char  un_expbuf[ESIZE];
	    char *un_patbuf;
	} st2;

	/* for TWSaction */
	struct {
	    char *un_datef;
	    int   un_after;
	    struct tws un_tws;
	} st3;
    } un;
};

#define	n_L_child un.st1.un_L_child
#define	n_R_child un.st1.un_R_child

#define	n_header un.st2.un_header
#define	n_circf	 un.st2.un_circf
#define	n_expbuf un.st2.un_expbuf
#define	n_patbuf un.st2.un_patbuf

#define	n_datef	 un.st3.un_datef
#define	n_after	 un.st3.un_after
#define	n_tws	 un.st3.un_tws

static int talked;
static int pdebug = 0;

static char *datesw;
static char **argp;

static struct nexus *head;

/*
 * prototypes for date routines
 */
static struct tws *tws_parse();
static struct tws *tws_special();

/*
 * static prototypes
 */
static void PRaction();
static int gcompile();
static int advance();
static int cclass();
static int tcompile();

static struct nexus *parse();
static struct nexus *exp1();
static struct nexus *exp2();
static struct nexus *exp3();
static struct nexus *newnexus();

static int ORaction();
static int ANDaction();
static int NOTaction();
static int GREPaction();
static int TWSaction();


int
pcompile (char **vec, char *date)
{
    register char *cp;

    if ((cp = getenv ("MHPDEBUG")) && *cp)
	pdebug++;

    argp = vec;
    if ((datesw = date) == NULL)
	datesw = "date";
    talked = 0;

    if ((head = parse ()) == NULL)
	return (talked ? 0 : 1);

    if (*argp) {
	padvise (NULL, "%s unexpected", *argp);
	return 0;
    }

    return 1;
}


static struct nexus *
parse (void)
{
    register char  *cp;
    register struct nexus *n, *o;

    if ((n = exp1 ()) == NULL || (cp = nxtarg ()) == NULL)
	return n;

    if (*cp != '-') {
	padvise (NULL, "%s unexpected", cp);
	return NULL;
    }

    if (*++cp == '-')
	goto header;
    switch (smatch (cp, parswit)) {
	case AMBIGSW: 
	    ambigsw (cp, parswit);
	    talked++;
	    return NULL;
	case UNKWNSW: 
	    fprintf (stderr, "-%s unknown\n", cp);
	    talked++;
	    return NULL;

	case PROR: 
	    o = newnexus (ORaction);
	    o->n_L_child = n;
	    if ((o->n_R_child = parse ()))
		return o;
	    padvise (NULL, "missing disjunctive");
	    return NULL;

header: ;
	default: 
	    prvarg ();
	    return n;
    }
}

static struct nexus *
exp1 (void)
{
    register char *cp;
    register struct nexus *n, *o;

    if ((n = exp2 ()) == NULL || (cp = nxtarg ()) == NULL)
	return n;

    if (*cp != '-') {
	padvise (NULL, "%s unexpected", cp);
	return NULL;
    }

    if (*++cp == '-')
	goto header;
    switch (smatch (cp, parswit)) {
	case AMBIGSW: 
	    ambigsw (cp, parswit);
	    talked++;
	    return NULL;
	case UNKWNSW: 
	    fprintf (stderr, "-%s unknown\n", cp);
	    talked++;
	    return NULL;

	case PRAND: 
	    o = newnexus (ANDaction);
	    o->n_L_child = n;
	    if ((o->n_R_child = exp1 ()))
		return o;
	    padvise (NULL, "missing conjunctive");
	    return NULL;

header: ;
	default: 
	    prvarg ();
	    return n;
    }
}


static struct nexus *
exp2 (void)
{
    register char *cp;
    register struct nexus *n;

    if ((cp = nxtarg ()) == NULL)
	return NULL;

    if (*cp != '-') {
	prvarg ();
	return exp3 ();
    }

    if (*++cp == '-')
	goto header;
    switch (smatch (cp, parswit)) {
	case AMBIGSW: 
	    ambigsw (cp, parswit);
	    talked++;
	    return NULL;
	case UNKWNSW: 
	    fprintf (stderr, "-%s unknown\n", cp);
	    talked++;
	    return NULL;

	case PRNOT: 
	    n = newnexus (NOTaction);
	    if ((n->n_L_child = exp3 ()))
		return n;
	    padvise (NULL, "missing negation");
	    return NULL;

header: ;
	default: 
	    prvarg ();
	    return exp3 ();
    }
}

static struct nexus *
exp3 (void)
{
    int i;
    register char *cp, *dp;
    char buffer[BUFSIZ], temp[64];
    register struct nexus *n;

    if ((cp = nxtarg ()) == NULL)
	return NULL;

    if (*cp != '-') {
	padvise (NULL, "%s unexpected", cp);
	return NULL;
    }

    if (*++cp == '-') {
	dp = ++cp;
	goto header;
    }
    switch (i = smatch (cp, parswit)) {
	case AMBIGSW: 
	    ambigsw (cp, parswit);
	    talked++;
	    return NULL;
	case UNKWNSW: 
	    fprintf (stderr, "-%s unknown\n", cp);
	    talked++;
	    return NULL;

	case PRLBR: 
	    if ((n = parse ()) == NULL) {
		padvise (NULL, "missing group");
		return NULL;
	    }
	    if ((cp = nxtarg ()) == NULL) {
		padvise (NULL, "missing -rbrace");
		return NULL;
	    }
	    if (*cp++ == '-' && smatch (cp, parswit) == PRRBR)
		return n;
	    padvise (NULL, "%s unexpected", --cp);
	    return NULL;

	default: 
	    prvarg ();
	    return NULL;

	case PRCC: 
	case PRDATE: 
	case PRFROM: 
	case PRTO: 
	case PRSUBJ: 
	    strncpy(temp, parswit[i].sw, sizeof(temp));
	    temp[sizeof(temp) - 1] = '\0';
	    dp = *brkstring (temp, " ", NULL);
    header: ;
	    if (!(cp = nxtarg ())) {/* allow -xyz arguments */
		padvise (NULL, "missing argument to %s", argp[-2]);
		return NULL;
	    }
	    n = newnexus (GREPaction);
	    n->n_header = 1;
	    snprintf (buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
	    dp = buffer;
	    goto pattern;

	case PRSRCH: 
	    n = newnexus (GREPaction);
	    n->n_header = 0;
	    if (!(cp = nxtarg ())) {/* allow -xyz arguments */
		padvise (NULL, "missing argument to %s", argp[-2]);
		return NULL;
	    }
	    dp = cp;
    pattern: ;
	    if (!gcompile (n, dp)) {
		padvise (NULL, "pattern error in %s %s", argp[-2], cp);
		return NULL;
	    }
	    n->n_patbuf = getcpy (dp);
	    return n;

	case PROTHR: 
	    padvise (NULL, "internal error!");
	    return NULL;

	case PRDATF: 
	    if (!(datesw = nxtarg ()) || *datesw == '-') {
		padvise (NULL, "missing argument to %s", argp[-2]);
		return NULL;
	    }
	    return exp3 ();

	case PRAFTR: 
	case PRBEFR: 
	    if (!(cp = nxtarg ())) {/* allow -xyz arguments */
		padvise (NULL, "missing argument to %s", argp[-2]);
		return NULL;
	    }
	    n = newnexus (TWSaction);
	    n->n_datef = datesw;
	    if (!tcompile (cp, &n->n_tws, n->n_after = i == PRAFTR)) {
		padvise (NULL, "unable to parse %s %s", argp[-2], cp);
		return NULL;
	    }
	    return n;
    }
}


static struct nexus *
newnexus (int (*action)())
{
    register struct nexus *p;

    if ((p = (struct nexus *) calloc ((size_t) 1, sizeof *p)) == NULL)
	adios (NULL, "unable to allocate component storage");

    p->n_action = action;
    return p;
}


#define	args(a)	a, fp, msgnum, start, stop
#define	params	args (n)
#define	plist	\
	    register struct nexus  *n; \
	    register FILE *fp; \
	    int	msgnum; \
	    long    start, \
		    stop;

int
pmatches (FILE *fp, int msgnum, long start, long stop)
{
    if (!head)
	return 1;

    if (!talked++ && pdebug)
	PRaction (head, 0);

    return (*head->n_action) (args (head));
}


static void
PRaction (struct nexus *n, int level)
{
    register int i;

    for (i = 0; i < level; i++)
	fprintf (stderr, "| ");

    if (n->n_action == ORaction) {
	fprintf (stderr, "OR\n");
	PRaction (n->n_L_child, level + 1);
	PRaction (n->n_R_child, level + 1);
	return;
    }
    if (n->n_action == ANDaction) {
	fprintf (stderr, "AND\n");
	PRaction (n->n_L_child, level + 1);
	PRaction (n->n_R_child, level + 1);
	return;
    }
    if (n->n_action == NOTaction) {
	fprintf (stderr, "NOT\n");
	PRaction (n->n_L_child, level + 1);
	return;
    }
    if (n->n_action == GREPaction) {
	fprintf (stderr, "PATTERN(%s) %s\n",
		n->n_header ? "header" : "body", n->n_patbuf);
	return;
    }
    if (n->n_action == TWSaction) {
	fprintf (stderr, "TEMPORAL(%s) %s: %s\n",
		n->n_after ? "after" : "before", n->n_datef,
		dasctime (&n->n_tws, TW_NULL));
	return;
    }
    fprintf (stderr, "UNKNOWN(0x%x)\n", (unsigned int) (*n->n_action));
}


static int
ORaction (params)
plist
{
    if ((*n->n_L_child->n_action) (args (n->n_L_child)))
	return 1;
    return (*n->n_R_child->n_action) (args (n->n_R_child));
}


static int
ANDaction (params)
plist
{
    if (!(*n->n_L_child->n_action) (args (n->n_L_child)))
	return 0;
    return (*n->n_R_child->n_action) (args (n->n_R_child));
}


static int
NOTaction (params)
plist
{
    return (!(*n->n_L_child->n_action) (args (n->n_L_child)));
}


static int
gcompile (struct nexus *n, char *astr)
{
    register int c;
    int cclcnt;
    register char *ep, *dp, *sp, *lastep;

    dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
    sp = astr;
    if (*sp == '^') {
	n->n_circf = 1;
	sp++;
    }
    else
	n->n_circf = 0;
    for (;;) {
	if (ep >= dp)
	    goto cerror;
	if ((c = *sp++) != '*')
	    lastep = ep;
	switch (c) {
	    case '\0': 
		*ep++ = CEOF;
		return 1;

	    case '.': 
		*ep++ = CDOT;
		continue;

	    case '*': 
		if (lastep == 0)
		    goto defchar;
		*lastep |= STAR;
		continue;

	    case '$': 
		if (*sp != '\0')
		    goto defchar;
		*ep++ = CDOL;
		continue;

	    case '[': 
		*ep++ = CCL;
		*ep++ = 0;
		cclcnt = 1;
		if ((c = *sp++) == '^') {
		    c = *sp++;
		    ep[-2] = NCCL;
		}
		do {
		    *ep++ = c;
		    cclcnt++;
		    if (c == '\0' || ep >= dp)
			goto cerror;
		} while ((c = *sp++) != ']');
		lastep[1] = cclcnt;
		continue;

	    case '\\': 
		if ((c = *sp++) == '\0')
		    goto cerror;
	defchar: 
	    default: 
		*ep++ = CCHR;
		*ep++ = c;
	}
    }

cerror: ;
    return 0;
}


static int
GREPaction (params)
plist
{
    int c, body, lf;
    long pos = start;
    register char *p1, *p2, *ebp, *cbp;
    char ibuf[BUFSIZ];

    fseek (fp, start, SEEK_SET);
    body = 0;
    ebp = cbp = ibuf;
    for (;;) {
	if (body && n->n_header)
	    return 0;
	p1 = linebuf;
	p2 = cbp;
	lf = 0;
	for (;;) {
	    if (p2 >= ebp) {
		if (fgets (ibuf, sizeof ibuf, fp) == NULL
			|| (stop && pos >= stop)) {
		    if (lf)
			break;
		    return 0;
		}
		pos += (long) strlen (ibuf);
		p2 = ibuf;
		ebp = ibuf + strlen (ibuf);
	    }
	    c = *p2++;
	    if (lf && c != '\n') {
		if (c != ' ' && c != '\t') {
		    --p2;
		    break;
		}
		else
		    lf = 0;
	    }
	    if (c == '\n') {
		if (body)
		    break;
		else {
		    if (lf) {
			body++;
			break;
		    }
		    lf++;
		    c = ' ';
		}
	    }
	    if (c && p1 < &linebuf[LBSIZE - 1])
		*p1++ = c;
	}

	*p1++ = 0;
	cbp = p2;
	p1 = linebuf;
	p2 = n->n_expbuf;

	if (n->n_circf) {
	    if (advance (p1, p2))
		return 1;
	    continue;
	}

	if (*p2 == CCHR) {
	    c = p2[1];
	    do {
		if (*p1 == c || cc[(unsigned char)*p1] == c)
		    if (advance (p1, p2))
			return 1;
	    } while (*p1++);
	    continue;
	}

	do {
	    if (advance (p1, p2))
		return 1;
	} while (*p1++);
    }
}


static int
advance (char *alp, char *aep)
{
    register char *lp, *ep, *curlp;

    lp = alp;
    ep = aep;
    for (;;)
	switch (*ep++) {
	    case CCHR: 
		if (*ep++ == *lp++ || ep[-1] == cc[(unsigned char)lp[-1]])
		    continue;
		return 0;

	    case CDOT: 
		if (*lp++)
		    continue;
		return 0;

	    case CDOL: 
		if (*lp == 0)
		    continue;
		return 0;

	    case CEOF: 
		return 1;

	    case CCL: 
		if (cclass (ep, *lp++, 1)) {
		    ep += *ep;
		    continue;
		}
		return 0;

	    case NCCL: 
		if (cclass (ep, *lp++, 0)) {
		    ep += *ep;
		    continue;
		}
		return 0;

	    case CDOT | STAR: 
		curlp = lp;
		while (*lp++)
		    continue;
		goto star;

	    case CCHR | STAR: 
		curlp = lp;
		while (*lp++ == *ep || cc[(unsigned char)lp[-1]] == *ep)
		    continue;
		ep++;
		goto star;

	    case CCL | STAR: 
	    case NCCL | STAR: 
		curlp = lp;
		while (cclass (ep, *lp++, ep[-1] == (CCL | STAR)))
		    continue;
		ep += *ep;
		goto star;

	star: 
		do {
		    lp--;
		    if (advance (lp, ep))
			return (1);
		} while (lp > curlp);
		return 0;

	    default: 
		admonish (NULL, "advance() botch -- you lose big");
		return 0;
	}
}


static int
cclass (char *aset, int ac, int af)
{
    register int    n;
    register char   c,
                   *set;

    set = aset;
    if ((c = ac) == 0)
	return (0);

    n = *set++;
    while (--n)
	if (*set++ == c)
	    return (af);

    return (!af);
}


static int
tcompile (char *ap, struct tws *tb, int isafter)
{
    register struct tws *tw;

    if ((tw = tws_parse (ap, isafter)) == NULL)
	return 0;

    twscopy (tb, tw);
    return 1;
}


static struct tws *
tws_parse (char *ap, int isafter)
{
    char buffer[BUFSIZ];
    register struct tws *tw, *ts;

    if ((tw = tws_special (ap)) != NULL) {
	tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
	tw->tw_hour = isafter ? 23 : 0;
	return tw;
    }
    if ((tw = dparsetime (ap)) != NULL)
	return tw;

    if ((ts = dlocaltimenow ()) == NULL)
	return NULL;

    snprintf (buffer, sizeof(buffer), "%s %s", ap, dtwszone (ts));
    if ((tw = dparsetime (buffer)) != NULL)
	return tw;

    snprintf (buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
	    ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone (ts));
    if ((tw = dparsetime (buffer)) != NULL)
	return tw;

    snprintf (buffer, sizeof(buffer), "%02d %s %04d %s",
	    ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
    if ((tw = dparsetime (buffer)) != NULL)
	return tw;

    snprintf (buffer, sizeof(buffer), "%02d %s %04d %s %s",
	    ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
	    ap, dtwszone (ts));
    if ((tw = dparsetime (buffer)) != NULL)
	return tw;

    return NULL;
}


static struct tws *
tws_special (char *ap)
{
    int i;
    time_t clock;
    register struct tws *tw;

    time (&clock);
    if (!strcasecmp (ap, "today"))
	return dlocaltime (&clock);
    if (!strcasecmp (ap, "yesterday")) {
	clock -= (long) (60 * 60 * 24);
	return dlocaltime (&clock);
    }
    if (!strcasecmp (ap, "tomorrow")) {
	clock += (long) (60 * 60 * 24);
	return dlocaltime (&clock);
    }

    for (i = 0; tw_ldotw[i]; i++)
	if (!strcasecmp (ap, tw_ldotw[i]))
	    break;
    if (tw_ldotw[i]) {
	if ((tw = dlocaltime (&clock)) == NULL)
	    return NULL;
	if ((i -= tw->tw_wday) > 0)
	    i -= 7;
    }
    else
	if (*ap != '-')
	    return NULL;
	else			/* -ddd days ago */
	    i = atoi (ap);	/* we should error check this */

    clock += (long) ((60 * 60 * 24) * i);
    return dlocaltime (&clock);
}


static int
TWSaction (params)
plist
{
    int state;
    register char *bp;
    char buf[BUFSIZ], name[NAMESZ];
    register struct tws *tw;

    fseek (fp, start, SEEK_SET);
    for (state = FLD, bp = NULL;;) {
	switch (state = m_getfld (state, name, buf, sizeof buf, fp)) {
	    case FLD: 
	    case FLDEOF: 
	    case FLDPLUS: 
		if (bp != NULL)
		    free (bp), bp = NULL;
		bp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof buf, fp);
		    bp = add (buf, bp);
		}
		if (!strcasecmp (name, n->n_datef))
		    break;
		if (state != FLDEOF)
		    continue;

	    case BODY: 
	    case BODYEOF: 
	    case FILEEOF: 
	    case LENERR: 
	    case FMTERR: 
		if (state == LENERR || state == FMTERR)
		    advise (NULL, "format error in message %d", msgnum);
		if (bp != NULL)
		    free (bp);
		return 0;

	    default: 
		adios (NULL, "internal error -- you lose");
	}
	break;
    }

    if ((tw = dparsetime (bp)) == NULL)
	advise (NULL, "unable to parse %s field in message %d, matching...",
		n->n_datef, msgnum), state = 1;
    else
	state = n->n_after ? (twsort (tw, &n->n_tws) > 0)
	    : (twsort (tw, &n->n_tws) < 0);

    if (bp != NULL)
	free (bp);
    return state;
}


syntax highlighted by Code2HTML, v. 0.9.1