/* @(#)pick.c	7.2	(c) copyright 5/19/91 (Dan Heller) */

#include "mush.h"

static int before, after, search_from, search_subj, search_to, xflg, icase;
static u_long match_priority;
static char search_hdr[64];
static int mdy[3];
static int pick();
static void month_day_year();

#ifdef REGCOMP
#include <regex.h>
#endif

do_pick(n, argv, list)
register int n;
register char **argv, list[];
{
#ifndef MSGS_DYNAMIC
    char ret_list[MAXMSGS_BITS];
#else
    char* ret_list = (char*) alloca(MSGS_BITS(msg_cnt));
#endif

    if (n > 1 && !strcmp(argv[1], "-?"))
	return help(0, "pick", cmd_help);

    clear_msg_list(ret_list);
    /* if is_pipe, then the messages to search for are already set.
     * if not piped, then reverse the bits for all message numbers.
     * That is, search EACH message. only those matching will be returned.
     */
    if (isoff(glob_flags, IS_PIPE))
	bitput(ret_list, list, msg_cnt, =~); /* macro, turn on all bits */
    /* Use n temporarily as verbosity flag */
    n = (!chk_option("quiet", "pick") &&
	    isoff(glob_flags, DO_PIPE));
    if ((n = pick(argv, list, ret_list, n)) == -1)
	return -1;
    if (istool && isoff(glob_flags, DO_PIPE))
	print("%d matches:\n", n);
    for (n = 0; n < msg_cnt; n++)
	if (msg_bit(ret_list, n)) {
	    if (isoff(glob_flags, DO_PIPE))
		if (istool)
		    print_more("%d ", n+1);
		else
		    print("%s\n", compose_hdr(n));
	    set_msg_bit(list, n);
	} else
	    unset_msg_bit(list, n);
    return 0;
}

/*
 * search for messages.  Return the number of matches.  Errors such
 * as internal errors or syntax errors, return -1.
 * "head" and "tail" are specified using +<num> or -<num> as args.
 * Both can be specified and the order is significant.
 *    pick +5 -3
 * returns the last three of the first five matches.
 *    pick -3 +2
 * returns the first two of the last three matches.
 */
static int
pick(argv, list, ret_list, verbose)
register char **argv, list[], ret_list[];
{
    register char c;
    int matches = 0;
    char pattern[256];
    short head_first, head_cnt, tail_cnt, search = TRUE;
    int n;

    if (!msg_cnt) {
	print("No Messages.\n");
	return -1;
    }

    head_first = TRUE;
    head_cnt = tail_cnt = -1;
    match_priority = 0;
    icase = before = after = search_from = search_subj = search_to = xflg = 0;
    pattern[0] = mdy[0] = mdy[1] = search_hdr[0] = 0;
    while (*argv && *++argv && (**argv == '-' || **argv == '+'))
	if (**argv == '+' || isdigit(argv[0][1])) {
	    if (**argv == '+')
		head_cnt = atoi(&argv[0][1]);
	    else {
		tail_cnt = atoi(&argv[0][1]);
		if (head_cnt == -1)
		    head_first = FALSE;
	    }
	    if (head_cnt == 0 || tail_cnt == 0) {
		print("pick: invalid head/tail number: %s\n", &argv[0][1]);
		clear_msg_list(ret_list);
		return -1;
	    }
	} else if ((c = argv[0][1]) == 'e') {
	    if (!*++argv) {
		print("usage: -e expression...\n");
		return -1;
	    }
	    break;
	} else switch (c) {
	    /* user specifies a range */
	    case 'r': {
		int X = 2;
		/* if not a pipe, then clear all bits cuz we only want
		 * to search the message specified here...
		 * If it is a pipe, then add to the messages searched for.
		 */
		if (isoff(glob_flags, IS_PIPE))
		    clear_msg_list(list);
		/*  "-r10-15"
		 *     ^argv[1][2]  if NULL, then
		 * list detached from "r" e.g. "-r" "5-20"
		 */
		if (!argv[0][X])
		    argv++, X = 0;
		(*argv) += X;
		n = get_msg_list(argv, list);
		(*argv) -= X;
		if (n == -1)
		    return -1;
		argv += (n-1); /* we're going to increment another up top */
	    }
	    when 'a': {
		if ((n = ago_date(++argv)) == -1)
		    return -1;
		argv += n;
	    }
	    when 'd':
		if (!*++argv) {
		    print("Specify a date for -%c\n", c);
		    return -1;
		}
		if (!date1(*argv))
		    return -1;
	    when 's' : case 'f': case 't': case 'h':
		if (search_subj + search_from + search_to + *search_hdr > 1) {
		    print("Specify one of `s', `f', `t' or `h' only\n");
		    return -1;
	        }
	        if (c == 's')
		    search_subj = 1;
		else if (c == 'f')
		    search_from = 1;
		else if (c == 'h')
		    if (!*++argv)
			print("Specify header to search for.\n");
		    else
			(void) lcase_strcpy(search_hdr, *argv);
		else
		    search_to = 1;
	    when 'p' :	/* Select on priority field */
		if (!*++argv || (c = upper(**argv)) < 'A' ||
			c > MAX_PRIORITY + 'A') {
		    print("pick: invalid priority: %s\n", argv[0]);
		    clear_msg_list(ret_list);
		    return -1;
		}
		turnon(match_priority, M_PRIORITY(c - 'A' + 1));
	    when 'x' : xflg = 1;
	    when 'i' : icase = 1;
	    otherwise:
		print("pick: unknown flag: %c\n", argv[0][1]);
		clear_msg_list(ret_list);
		return -1;
	}
    if (xflg && head_cnt + tail_cnt >= 0) {
	print("Can't specify -x and head/tail options together.\n");
	return -1;
    }
    if (!mdy[1]) {
	(void) argv_to_string(pattern, argv);
	if (pattern[0] == '\0' && match_priority == 0 &&
		head_cnt + tail_cnt < 0) {
	    print("No pattern specified\n");
	    clear_msg_list(ret_list);  /* doesn't matter really */
	    return -1;
	}
    }
    search = (pattern[0] || mdy[1] || match_priority > 0);
    if (verbose) {
	if (head_cnt + tail_cnt >= 0) {
	    print("Finding the ");
	    if (head_cnt > 0) {
		if (head_first)
		    if (tail_cnt == -1)
			print_more("first %d message%s",
			    head_cnt, head_cnt > 1? "s" : "");
		    else
			print_more("last %d message%s",
			    tail_cnt, tail_cnt > 1? "s" : "");
		else /* there must be a tail_cnt and it comes first */
		    print_more("first %d message%s",
			    head_cnt, head_cnt > 1? "s" : "");
	    } else
		print_more("last %d message%s",
		    tail_cnt, tail_cnt > 1? "s" : "");
	    if (tail_cnt > 0 && head_cnt > 0)
		if (head_first)
		    print_more(" of the first %d", head_cnt);
		else
		    print_more(" of the last %d", tail_cnt);
	} else
	    print_more("Searching for %smessages",
		match_priority > 0 ? "priority " : "");
	if (!search) {
	    if (tail_cnt > 0 && head_cnt > 0)
		print_more(" messages");
	    if (ison(glob_flags, IS_PIPE))
		print_more(" from the input list");
	} else if (pattern[0]) {
	    print_more(" that %scontain \"%s\"", (xflg)? "do not ": "",
			    pattern);
	    if (search_subj)
		print_more(" in subject line");
	    else if (search_from)
		print_more(" from author names");
	    else if (search_to)
		print_more(" from the To: field");
	    else if (search_hdr[0])
		print_more(" from the message header \"%s:\"", search_hdr);
	} else if (mdy[1]) {
	    extern char *month_names[]; /* from dates.c */
	    print_more("%s dated ", xflg && !(before || after)? " not" : "");
	    if (before || after)
		if (xflg)
		    print_more("%s ", (!before)? "before": "after");
		else
		    print_more("on or %s ", (before)? "before": "after");
	    print_more("%s. %d, %d",
		      month_names[mdy[0]], mdy[1], mdy[2] + 1900);
	}
	print_more(".\n");
    }
    if (mdy[1] > 0 && icase)
	print("using date: -i flag ignored.\n");
    if (!search) {
	for (n = 0; n < msg_cnt && (!head_first || matches < head_cnt); n++)
	    if (msg_bit(list, n))
		++matches, set_msg_bit(ret_list, n);
    } else
	matches = find_pattern(head_first? head_cnt : msg_cnt,
			   pattern, list, ret_list);
    if (xflg && matches >= 0) {
	/* invert items in ret_list that also appear in list */
	bitput(list, ret_list, msg_cnt, ^=);
	/* there should be a faster way to do this count ... */
	for (matches = n = 0; n < msg_cnt; n++)
	    if (msg_bit(ret_list, n))
		++matches;
    }
    Debug("matches = %d\n", matches);
    if (!matches)
	return 0;

    /* ok, the list we've got is a list of matched messages.  If "tailing"
     * is set, reduce the number of matches to at least tail_cnt.
     */
    if (tail_cnt >= 0)
	for (n = 0; n < msg_cnt && matches > tail_cnt; n++)
	    if (msg_bit(ret_list, n)) {
		Debug("tail: dropping %d\n", n+1);
		unset_msg_bit(ret_list, n);
		matches--;
	    }

    /* if tailing came before heading, we need to do the heading now. */
    if (!head_first && head_cnt >= 0)
	for (n = 0; n < msg_cnt; n++)
	    if (msg_bit(ret_list, n))
		if (head_cnt > 0)
		    head_cnt--;
		else {
		    unset_msg_bit(ret_list, n);
		    matches--;
		}
    return matches;
}

/*
 * find_pattern will search thru all the messages set in the check_list
 * until the list runs out or "cnt" has been exhasted.  ret_list contains
 * the list of messages which have matched the pattern.
 * return -1 for internal error or # of pattern matches.
 */
find_pattern(cnt, p, check_list, ret_list)
int cnt;
register char *p;
char check_list[], ret_list[];
{
    register int n, val, i; /* val is return value from regex or re_exec */
    int matches = 0;
    long bytes = 0;
    char buf[HDRSIZ];
    static char *err = NULL;
#ifdef REGCMP
    char *regcmp(), *regex();
#else /* REGCMP */
# ifdef REGCOMP
    static regex_t re;
    int code;
# else
    char *re_comp();
# endif
#endif /* REGCMP */

    if (p && *p == '\\')
	p++;  /* take care of escaping special cases (`-', `\') */

    /* specify what we're looking for */
    if (p && *p) {
	if (icase)
	    p = lcase_strcpy(buf, p);
#ifdef REGCMP
	if (p && (err = regcmp(p, NULL)) == NULL) {
	    print("regcmp error: %s\n", p);
	    clear_msg_list(ret_list);
	    return -1;
	}
#else /* REGCMP */
# ifdef REGCOMP
	if (p && (code = regcomp(&re, p, REG_EXTENDED)) != 0) {
	    err = NULL;
	    (void) regerror(code, &re, buf, sizeof(buf));
	    printf("regcomp error: %s\n", buf);
	    clear_msg_list(ret_list);
	    return -1;
	}
	else
	    err = "";
# else
	if (p && (err = re_comp(p)) != NULL) {
	    print("re_comp error: %s\n", err);
	    clear_msg_list(ret_list);
	    return -1;
	}
# endif
#endif /* REGCMP */
    } else if (err == NULL && mdy[1] <= 0 && match_priority == 0) {
	print("No previous regular expression\n");
	clear_msg_list(ret_list);  /* doesn't matter really */
	return -1;
    }
    /* start searching: set bytes, and message number: n */
    for (n = 0; cnt && n < msg_cnt; n++)
	if (msg_bit(check_list, n)) {
	    if (match_priority > 0) {
		if (msg[n].m_flags & match_priority)
		    ++matches, set_msg_bit(ret_list, n);
		continue;
	    }
	    if (mdy[1] > 0) {
		int msg_mdy[3];
		if (ison(glob_flags, DATE_RECV))
		    p = msg[n].m_date_recv;
		else
		    p = msg[n].m_date_sent;
		/* Ick -- fix this mdy thing asap */
		month_day_year(p, &msg_mdy[0], &msg_mdy[1], &msg_mdy[2]);
		Debug("checking %d's date: %d-%d-%d  ",
			     n+1, msg_mdy[0]+1, msg_mdy[1], msg_mdy[2]);
		/* start at year and wrap around.
		 * only when match the day (4), check for == (match)
		 */
		for (i = 2; i < 5; i++)
		    if (before && msg_mdy[i%3] < mdy[i%3]
		    ||  after  && msg_mdy[i%3] > mdy[i%3]
		    ||  i == 4 && (msg_mdy[i%3] == mdy[i%3])) {
			    Debug("matched (%s).\n",
				(i == 2)? "year" : (i == 3)? "month" : "day");
			    set_msg_bit(ret_list, n);
			    cnt--, matches++;
			    break;
		    } else if (msg_mdy[i%3] != mdy[i%3]) {
			Debug("failed.\n");
			break;
		    }
		continue;
	    }
	    /* we must have the right date -- if we're searching for a
	     * string, find it.
	     */
	    (void) msg_get(n, NULL, 0);
	    bytes = 0;
	    while (bytes < msg[n].m_size) {
		if (!search_subj && !search_from && !search_to &&
		    !*search_hdr && !(p = fgets(buf, sizeof buf, tmpf)))
		    break;
		else if (search_subj) {
		    if (!(p = header_field(n, "subject")))
			break;
		} else if (search_from) {
		    if (!(p = header_field(n, "from"))) {
			/*
			 * Check for MSG_SEPARATOR here?  Maybe not...
			 */
			register char *p2;
			(void) msg_get(n, NULL, 0);
			if (!(p2 = fgets(buf, sizeof buf, tmpf)) ||
			    !(p = index(p2, ' ')))
			    continue;
			p++;
			if (p2 = any(p, " \t"))
			    *p2 = 0;
		    }
		} else if (search_to) {
		    if (!(p = header_field(n, "to")) &&
		        !(p = header_field(n, "apparently-to")))
			break;
		} else if (*search_hdr) {
		    if (!(p = header_field(n, search_hdr)))
			break;
		}
		if (icase)
		    p = lcase_strcpy(buf, p);
#ifdef REGCMP
		val = regex(err, p, NULL) != NULL;
#else /* REGCMP */
# ifdef REGCOMP
		val = regexec(&re, p, 0, 0, 0) == 0;
# else
		val = re_exec(p);
# endif
#endif /* REGCMP */
		if (val == -1) {   /* doesn't apply in system V */
		    print("Internal error for pattern search.\n");
		    clear_msg_list(ret_list); /* it doesn't matter, really */
		    return -1;
		}
		if (val) {
		    set_msg_bit(ret_list, n);
		    cnt--, matches++;
		    break;
		}
		if (search_subj || search_from || search_to || *search_hdr)
		    break;
		else
		    bytes += strlen(p);
	    }
	}
#ifdef REGCMP
    if (err)
	free(err);
#else
# ifdef REGCOMP
    if (err) {
    	regfree(&re);
	err = NULL;
    }
# endif
#endif /* REGCMP */
    return matches;
}

#ifdef CURSES
/*
 * search for a pattern in composed message headers -- also see next function
 * flags ==  0   forward search (prompt).
 * flags == -1   continue search (no prompt).
 * flags ==  1   backward search (prompt).
 */
search(flags)
register int flags;
{
    register char   *p;
    char   	    pattern[128];
    register int    this_msg = current_msg, val = 0;
    static char     *err = (char *) -1, direction;
    SIGRET	    (*oldint)(), (*oldquit)();
#ifdef REGCMP
    char *regex(), *regcmp();
#else /* REGCMP */
# ifdef REGCOMP
    regex_t re;
    int code;
# else
    char *re_comp();
# endif
#endif /* REGCMP */

    if (msg_cnt <= 1) {
	print("Not enough messages to invoke a search.\n");
	return 0;
    }
    pattern[0] = '\0';
    if (flags == -1)
	print("continue %s search...", direction? "forward" : "backward");
    else
	print("%s search: ", flags? "backward" : "forward");
    if (flags > -1)
	if (Getstr(pattern, COLS-18, 0) < 0)
	    return 0;
	else
	    direction = !flags;
#ifdef REGCMP
    if (err != (char *)-1 && *pattern) {
	xfree(err);
	err = NULL;
    }
    else if (err == (char *)-1 && !*pattern) {
	print("No previous regular expression.");
	return 0;
    }
    if (*pattern && (err = regcmp(pattern, NULL)) == NULL) {
	print("Error in regcmp in %s", pattern);
	return 0;
    }
#else /* REGCMP */
# ifdef REGCOMP
    if (err != (char *)-1 && *pattern) {
	regfree(&re);
	err = NULL;
    }
    else if (err == (char *)-1 && !*pattern) {
	print("No previous regular expression.");
	return 0;
    }

    if (*pattern && regcomp(&re, pattern, REG_EXTENDED) != 0) {
	(void) regerror(code, &re, pattern, sizeof(pattern));
	printf("regcomp error: %s\n", pattern);
	err = NULL;
	return 0;
    }
    else
	err = "";
# else
    if (*pattern && (err = re_comp(pattern)) != NULL) {
	print(err);
	return 0;
    }
# endif
#endif /* REGCMP */
    move(LINES-1, 0), refresh();
    on_intr();

    do  {
	if (direction)
	    current_msg = (current_msg+1) % msg_cnt;
	else
	    if (--current_msg < 0)
		current_msg = msg_cnt-1;
	p = compose_hdr(current_msg);
#ifdef REGCMP
	val = regex(err, p, NULL) != NULL; /* convert value to a boolean */
#else /* REGCMP */
# ifdef REGCOMP
	val = regexec(&re, p, 0, 0, 0) == 0;
# else
	val = re_exec(p);
# endif
#endif /* REGCMP */
	if (val == -1)     /* doesn't apply in system V */
	    print("Internal error for pattern search.\n");
    } while (!val && current_msg != this_msg && isoff(glob_flags, WAS_INTR));

#ifndef REGCMP
# ifndef REGCOMP
    if (err)
	free(err);
# endif /* REGCOMP */
#endif /* REGCMP */

    if (ison(glob_flags, WAS_INTR)) {
	print("Pattern search interrupted.");
	current_msg = this_msg;
    } else if (val == 0)
	print("Pattern not found.");

    off_intr();
    return val;
}
#endif /* CURSES */

/*
 * Get just the month, day, and year from a date.
 * This is a temporary measure until the date compares in pick()
 * can be overhauled.  It really should be in dates.c, but ...
 */
static
void
month_day_year(date, month, day, year)
char *date;
int *month, *day, *year;
{
    long gmt;
    char unused[4], zone[8];
    struct tm *t;
    extern long getzoff();

    (void) sscanf(date, "%ld%3c%s", &gmt, unused, zone);
    gmt += getzoff(zone);
    t = gmtime(&gmt);
    *month = t->tm_mon;
    *day = t->tm_mday;
    *year = t->tm_year;
}

/*
 * parse a user given date string and set mdy[] array with correct
 * values.  Return 0 on failure.
 */
date1(p)
register char *p;
{
    register char *p2;
    long	  t;
    int 	  i;
    struct tm 	  *today;

    if (*p == '-' || *p == '+') {
	before = !(after = *p == '+');
	skipspaces(1);
    }
    if (!isdigit(*p) && *p != '/') {
	print("syntax error on date: \"%s\"\n", p);
	return 0;
    }
    (void) time (&t);
    today = localtime(&t);
    for (i = 0; i < 3; i++)
	if (!p || !*p || *p == '/') {
	    switch(i) {   /* default to today's date */
		case 0: mdy[0] = today->tm_mon;
		when 1: mdy[1] = today->tm_mday;
		when 2: mdy[2] = today->tm_year;
	    }
	    if (p && *p)
		p++;
	} else {
	    p2 = (*p)? index(p+1, '/') : NULL;
	    mdy[i] = atoi(p); /* atoi will stop at the '/' */
	    if (i == 0 && (--(mdy[0]) < 0 || mdy[0] > 11)) {
		print("Invalid month: %s\n", p);
		return 0;
	    } else if (i == 1 && (mdy[1] < 1 || mdy[1] > 31)) {
		print("Invalid day: %s\n", p);
		return 0;
	    }
	    if (p = p2) /* set p to p2 and check to see if it's valid */
		p++;
	}
    return 1;
}

/*
 * Parse arguments specifying days/months/years "ago" (relative to today).
 * Legal syntax: -ago [+-][args]
 *    where "args" is defined to be:
 *    [0-9]+[ ]*[dD][a-Z]*[ ,]*[0-9]+[mM][a-Z]*[ ,]*[0-9]+[ ]*[yY][a-Z]*
 *    1 or more digits, 0 or more spaces, d or D followed by 0 or more chars,
 *    0 or more whitespaces or commas, repeat for months and years...
 * Examples:
 *    1 day, 2 months, 0 years
 *    2 weeks 1 year
 *    10d, 5m
 *    3w
 *    1d 1Y
 *
 * Return number of args parsed; -1 on error.
 */
ago_date(argv)
char **argv;
{
#define SECS_PER_DAY   (60 * 60 * 24)
#define SECS_PER_WEEK  (SECS_PER_DAY * 7)
#define SECS_PER_MONTH ((int)(SECS_PER_DAY * 30.5))
#define SECS_PER_YEAR  (SECS_PER_DAY * 365)
    register char *p;
    char	   buf[256];
    int		   n = 0, value;
    long	   t;
    struct tm 	  *today;

    (void) argv_to_string(buf, argv);
    p = buf;
    (void) time (&t); /* get current time in seconds and subtract new values */
    if (*p == '-')
	before = TRUE;
    else if (*p == '+')
	after = TRUE;
    skipspaces(before || after);
    while (*p) {
	if (!isdigit(*p)) {
	    p -= 2;
	    break; /* really a syntax error, but it could be other pick args */
	}
	p = my_atoi(p, &value); /* get 1 or more digits */
	skipspaces(0); /* 0 or more spaces */
	switch (lower(*p)) {   /* d, m, or y */
	    case 'd' : t -= value * SECS_PER_DAY;
	    when 'w' : t -= value * SECS_PER_WEEK;
	    when 'm' : t -= value * SECS_PER_MONTH;
	    when 'y' : t -= value * SECS_PER_YEAR;
	    otherwise: return -1;
	}
	for (p++; Lower(*p) >= 'a' && *p <= 'z'; p++)
	    ; /* skip the rest of this token */
	while (*p == ',' || isspace(*p))
	    ++p; /* 0 or more whitespaces or commas */
    }
    today = localtime(&t);
    mdy[0] = today->tm_mon;
    mdy[1] = today->tm_mday;
    mdy[2] = today->tm_year;

    /* Count the number of args parsed */
    for (n = 0; p > buf && *argv; n++)
	p -= (strlen(*argv++)+1);
    Debug("parsed %d args\n", n);
    return n;
}
