/* $Id: search.c,v 1.35 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1998-2001 Atsushi Onoe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "cue.h"

static int
cstrstr(ccbuf_t *cbuf, ccbuf_t *word, int off)
{
	const u_char *s1, *s2;
	const char *p, *ep;
	int n;

	p = cbuf->ptr;
	ep = p + cbuf->len - word->len;
	if (off >= 0)
		p += off;
	else
		ep += off;
	for (; p <= ep; p++) {
		/* inline strncasecmp */
		s1 = (u_char *)p;
		s2 = (u_char *)word->ptr;
		for (n = word->len; ; s1++, s2++, n--) {
			if (n == 0)
				return p - cbuf->ptr;
			if (tolower(*s1) != tolower(*s2))
				break;
		}
	}
	return -1;
}

static int
cstrrstr(ccbuf_t *cbuf, ccbuf_t *word, int off)
{
	const u_char *s1, *s2;
	const char *p, *sp;
	int n;

	sp = cbuf->ptr;
	p = sp + cbuf->len - word->len;
	if (off >= 0)
		sp += off;
	else
		p += off;
	for (; p >= cbuf->ptr; p--) {
		/* inline strncasecmp */
		s1 = (u_char *)p;
		s2 = (u_char *)word->ptr;
		for (n = word->len; ; s1++, s2++, n--) {
			if (n == 0)
				return p - cbuf->ptr;
			if (tolower(*s1) != tolower(*s2))
				break;
		}
	}
	return -1;
}

void
isearch(struct state *state, struct filedb *fdb, int dir)
{
	int ch, ch2;
	char *w;
	static char word[CHARBLOCK];
	int prevpos;
	int fail = 0;
	int wrap = 0, haswrapped = 0;
	int n, i, x;
	ccbuf_t *cbuf;
	cbuf_t wbuf;
	int match, off;
	char *status, *est, *ep;
	struct window *win;

	w = NULL;
	cbuf = NULL;
	match = -1;
	status = state->status;
	ep = state->status + sizeof(state->status);
	state->mxoff = 0;
	if (fdb == NULL) {
		prevpos = state->folder->pos;
		state->myoff = -1;
		win = NULL;	/* make gcc happy */
	} else {
		if (state->hlpwin.lines)
			win = &state->hlpwin;
		else
			win = &state->msgwin;
		state->myoff = prevpos = win->off;
	}
	for (;;) {
		if (w)
			*w = '\0';
		status[0] = '\0';
		if (fail)
			strlcat(status, "Failing ", ep - status);
		if (haswrapped)
			strlcat(status, (fail ? "wrapped " : "Wrapped "),
			    ep - status);
		strlcat(status, "I-search", ep - status);
		if (fdb != NULL)
			strlcat(status, " message", ep - status);
		if (dir < 0)
			strlcat(status, " backward", ep - status);
		strlcat(status, ": ", ep - status);
		state->mlen = 0;
		est = status + strlen(status);
		if (w) {
			strlcpy(est, word, ep - est);
			if (match >= 0) {
				/* expand tab to calculate x position */
				for (i = x = 0; i < match; i++) {
					if (cbuf->ptr[i] == '\t')
						x = (x / 8 + 1) * 8;
					else
						x++;
				}
				state->mxoff = x;
			}
			if (!fail)
				state->mlen = w - word;
		}
		wrap = 0;
		disp_update(state);
		cangetch(-1);
		ch = getch();
		switch (ch) {
#ifdef USE_CANNA
		case CTRL('o'):
			ungetch(ch);
			/*FALLTHRU*/
#endif /* USE_CANNA */
		case '\033':
			snprintf(status, ep - status, "Search%s: ",
				(dir < 0 ? " backward" : ""));
			if (edit_stline(state, word, sizeof(word), NULL) != NULL) {
				w = word + strlen(word);
				break;
			}
			/*FALLTHRU*/
		case CTRL('g'):
			state->mlen = 0;
			if (fdb == NULL)
				state->folder->pos = prevpos;
			else
				win->off = prevpos;
			strlcpy(status, "Quit", ep - status);
			return;
		case CTRL('h'):
		case '\177':
			if (w && w > word) {
				if (*--w & 0x80)
					w--;
			}
			match = -1;
			state->mlen = 0;
			if (fdb == NULL)
				state->folder->pos = prevpos;
			else
				win->off = prevpos;
			break;
		case '\n':
		case '\r':
		case CTRL('a'):
			state->mlen = 0;
			status[0] = '\0';
			return;
		case CTRL('l'):
			clearok(stdscr, TRUE);
			refresh();
			clearok(stdscr, FALSE);
			continue;
		case CTRL('s'):
		case CTRL('r'):
			if (w == NULL)
				strlcat(status, word, ep - status);
			w = word + strlen(word);
			if (fail)
				wrap = 1;
			else
				fail = 1;
			dir = (ch == CTRL('s') ? 1 : -1);
			match += dir;
			break;
		default:
			if (ch >= ' ') {
				if (w == NULL)
					w = word;
				if (ch & 0x80) {
					if (!cangetch(KANJI_TIMEOUT)
					||  ((ch2 = getch()) & 0x80) == 0)
						break;
					*(u_char *)w++ = (u_char)ch;
					*(u_char *)w++ = (u_char)ch2;
				} else
					*(u_char *)w++ = (u_char)ch;
			}
			break;
		}
		if (w == NULL || w == word)
			continue;
		wbuf.ptr = word;
		wbuf.len = w - word;
		if (fail == 0 && match >= 0 && cbuf) {	/* now matching */
			if (cstrstr(cbuf, (ccbuf_t *)&wbuf, match) == 0)
				continue;
		}
		if (wrap) {
			haswrapped = 1;
			beep();
		}
		if (wrap) {
			if (dir > 0)
				n = 0;
			else {
				if (fdb == NULL)
					n = state->folder->nmsg;
				else
					n = fdb->lines;
			}
			match = -1;
		} else {
			if (fdb == NULL)
				n = state->folder->pos;
			else
				n = state->myoff;
		}
		if (match >= 0) {
			if (dir > 0)
				off = match;
			else
				off = match + wbuf.len - cbuf->len;
		} else {
			off = 0;
			if (dir < 0)
				n--;
		}
		*w = '\0';
		strlcpy(est, word, ep - est);
		strlcat(est, " [Searching ...]", ep - est);
		state->mlen = 0;
		disp_update(state);
		for (; ; n += dir) {
			if (fdb == NULL)
				cbuf = folder_read(state, n);
			else
				cbuf = fdb_read(fdb, n);
			if (cbuf == NULL) {
				fail = 1;
				break;
			}
			if (dir > 0)
				match = cstrstr(cbuf, (ccbuf_t *)&wbuf, off);
			else
				match = cstrrstr(cbuf, (ccbuf_t *)&wbuf, off);
			if (match >= 0) {
				fail = 0;
				if (fdb == NULL) {
					state->folder->pos = n;
					message_open(state, 1);
				} else {
					if (n < win->off ||
					    n >= win->off + win->lines) {
						if (n > win->lines / 2)
							win->off = n - win->lines / 2;
						else
							win->off = 0;
					}
					state->myoff = n;
				}
				break;
			}
			off = 0;
			if (cangetch(0)) {
				ch = getch();
				if (ch != (dir > 0 ? CTRL('s') : CTRL('r'))) {
					ungetch(ch);
					break;
				}
			}
		}
	}
	/*NOTREACHED*/
}

void
search_mark_folder(struct state *state, int dir)
{
	char buf[LINE_WIDTH];
	struct msginfo *msg;
	struct folder *folder;
	ccbuf_t *cbuf;
	cbuf_t wbuf;
	char *est, *ep;
	int found;
	int n;

	est = state->status;
	ep = state->status + sizeof(state->status);
	strlcpy(est, "Search", ep - est);
	if (dir < 0)
		strlcat(est, " backward", ep - est);
	strlcat(est, ": ", ep - est);
	est += strlen(est);
	buf[0] = '\0';
	if (edit_stline(state, buf, sizeof(buf), NULL) == NULL) {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return;
	}
	if (buf[0] == '\0')
		return;
	wbuf.ptr = buf;
	wbuf.len = strlen(buf);
	folder = state->folder;
	n = folder->pos;
	if (dir < 0)
		n--;
	found = 0;
	strlcpy(est, buf, ep - est);
	est += wbuf.len;
	strlcpy(est, " [Searching ...]", ep - est);
	disp_update(state);
	while ((cbuf = folder_read(state, n)) != NULL) {
		msg = &folder->msg[n];
		if (msg->mark == 0 && cstrstr(cbuf, (ccbuf_t *)&wbuf, 0) >= 0) {
			msg->mark = MARK_MARK;
			folder->nmark++;
			folder->pos = n;
			found++;
		}
		n += dir;
	}
	if (found)
		strlcpy(est, " [Done]", ep - est);
	else
		strlcpy(est, " [Search failed]", ep - est);
}

void
rguess_mark_folder(struct state *state, int dir)
{
	struct folder *folder;
	struct msginfo *msg;
	char *refile[MAX_REFILE];
	char *est, *ep;
	int found;
	int i, n;

	folder = state->folder;
	if (folder->nmark) {
		for (i = 0, msg = folder->msg; i < folder->nmsg; i++, msg++) {
			if (msg->mark == MARK_MARK) {
				strlcpy(state->status,
				    "There is already marked messages",
				    sizeof(state->status));
				return;
			}
		}
	}
	n = folder->pos;
	msg = &folder->msg[n];
	memcpy(refile, msg->refile, sizeof(refile));
	found = 0;
	est = state->status;
	ep = state->status + sizeof(state->status);
	strlcpy(state->status, "Mark refile (", /*)*/ sizeof(state->status));
	est += strlen(est);
	for (i = 0; i < MAX_REFILE; i++) {
		if (refile[i] == NULL)
			break;
		if (i != 0)
			*est++ = ',';
		strlcpy(est, refile[i], ep - est);
		est += strlen(est);
	}
	*est++ = /*(*/ ')';
	strlcpy(est, " [Searching ...]", ep - est);
	disp_update(state);
	for (found = 0; folder_read(state, n) != NULL; n += dir) {
		msg = &folder->msg[n];
		if (msg->mark)
			continue;
		for (i = 0; i < MAX_REFILE; i++) {
			if (refile[i] == msg->refile[i]) {
				if (refile[i] == NULL) {
					i = MAX_REFILE;
					break;
				}
				continue;
			}
			if (refile[i] == NULL || msg->refile[i] == NULL)
				break;
			if (strcmp(refile[i], msg->refile[i]) != 0)
				break;
		}
		if (i == MAX_REFILE) {
			msg->mark = MARK_MARK;
			folder->nmark++;
			found++;
		}
	}
	if (found <= 1)
		strlcpy(est, ": No more message", ep - est);
	else
		snprintf(est, ep - est, ": %d messages found", found);
}

void
pick_mark_folder(struct state *state, int dir)
{
	char buf[LINE_WIDTH];
	struct msginfo *msg;
	struct folder *folder;
	struct filedb *fdb;
	struct header *hdr;
	cbuf_t wbuf;
	char *p, *est, *ep;
	u_char *u;
	int tocc, found;
	int klen;
	int i, n;

	est = state->status;
	ep = state->status + sizeof(state->status);
	strlcat(est, "Pick", ep - est);
	if (dir < 0)
		strlcat(est, " backward", ep - est);
	strlcat(est, ": ", ep - est);
	est += strlen(est);
	buf[0] = '\0';
	if (edit_stline(state, buf, sizeof(buf), NULL) == NULL) {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return;
	}
	if (buf[0] == '\0')
		return;
	strlcpy(est, buf, ep - est);
	est += strlen(est);
	if ((p = strchr(buf, ':')) == NULL) {
		strlcpy(est, ": No keyword", ep - est);
		return;
	}
	p++;
	klen = p - buf;
	while (*p == ' ' || *p == ':')
		p++;
	wbuf.ptr = p;
	wbuf.len = strlen(p);
	folder = state->folder;
	n = folder->pos;
	if (dir < 0)
		n--;
	found = 0;
	strlcpy(est, " [Searching ...]", ep - est);
	if (klen == 6 && strncasecmp(buf, "To/Cc:", klen) == 0) {
		tocc = 1;
		klen = 3;
	} else
		tocc = 0;
	disp_update(state);

	for (; folder_read(state, n) != NULL; n += dir) {
		msg = &folder->msg[n];
		if (msg->mark != 0)
			continue;
		folder->pos = n;
		message_open(state, 0);
		if ((fdb = state->message) == NULL)
			break;
		for (i = 0, hdr = fdb->hdr; i < fdb->hdrs; i++, hdr++) {
			if (CL(&hdr->dbuf) < klen)
				continue;
			u = (u_char *)CP(&hdr->dbuf);
			if (tocc) {
				if (u[2] != ':')
					continue;
				if ((tolower(*u)!='t' || tolower(u[1])!='o') &&
				    (tolower(*u)!='c' || tolower(u[1])!='c'))
					continue;
			} else {
				if (tolower(*u) != tolower(*(u_char *)buf) ||
				    strncasecmp((char *)u, buf, klen) != 0)
					continue;
			}
			if (cstrstr((ccbuf_t *)&hdr->dbuf, (ccbuf_t *)&wbuf, klen) >= 0) {
				msg->mark = MARK_MARK;
				folder->nmark++;
				found++;
				break;
			}
		}
	}
	if (found)
		snprintf(est, ep - est, ": %d messages found", found);
	else
		strlcpy(est, ": Search failed", ep - est);
}
