/*
 * Copyright (c) 1984,1985,1989,1994  Mark Nudelman
 * 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 in the documentation and/or other materials provided with 
 *    the distribution.
 *
 * 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.
 */


/*
 * Routines to search a file for a pattern.
 */

#include "less.h"
#include "position.h"
#if HAVE_POSIX_REGCOMP
#include <regex.h>
#endif
#if HAVE_V8_REGCOMP
#include "regexp.h"
#endif

extern int sigs;
extern int how_search;
extern int caseless;
extern int linenums;
extern int jump_sline;
extern int bs_mode;


/*
 * Search for the n-th occurrence of a specified pattern, 
 * either forward or backward.
 * Return the number of matches not yet found in this file
 * (that is, n minus the number of matches found).
 * Return -1 if the search should be aborted.
 * Caller may continue the search in another file 
 * if less than n matches are found in this file.
 */
	public int
search(search_type, pattern, n)
	int search_type;
	char *pattern;
	int n;
{
	POSITION pos, linepos, oldpos;
	register char *p;
	register char *q;
	register int goforw;
	register int want_match;
	char *line;
	int linenum;
	int line_match;
	static int is_caseless;
#if HAVE_POSIX_REGCOMP
	static regex_t *regpattern = NULL;
#endif
#if HAVE_RE_COMP
	char *re_comp();
	PARG parg;
#endif
#if HAVE_REGCMP
	char *regcmp();
	static char *cpattern = NULL;
#endif
#if HAVE_V8_REGCOMP
	static struct regexp *regpattern = NULL;
#endif
#if NO_REGEX
	static char lpbuf[100];
	static char *last_pattern = NULL;
	static int match();
#endif

	/*
	 * Extract flags and type of search.
	 */
	goforw = (SRCH_DIR(search_type) == SRCH_FORW);
	want_match = !(search_type & SRCH_NOMATCH);

	if (pattern != NULL && *pattern != '\0' && (is_caseless = caseless))
	{
		/*
		 * Search will ignore case, unless
		 * there are any uppercase letters in the pattern.
		 */
		for (p = pattern;  *p != '\0';  p++)
			if (*p >= 'A' && *p <= 'Z')
			{
				is_caseless = 0;
				break;
			}
	}
#if HAVE_POSIX_REGCOMP
	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * A null pattern means use the previous pattern.
		 * The compiled previous pattern is in regpattern,
		 * so just use it.
		 */
		if (regpattern == NULL)
		{
			error("No previous regular expression", NULL_PARG);
			return (-1);
		}
	} else
	{
		/*
		 * Otherwise compile the given pattern.
		 */
		if (regpattern == NULL)
			regpattern = (regex_t *) ecalloc(1, sizeof(regex_t));
		else
			regfree(regpattern);
		if (regcomp(regpattern, pattern, 0))
		{
			error("Invalid pattern", NULL_PARG);
			return (-1);
		}
	}
#endif /* HAVE_POSIX_REGCOMP */
#if HAVE_RE_COMP
	/*
	 * (re_comp handles a null pattern internally, 
	 *  so there is no need to check for a null pattern here.)
	 */
	if ((parg.p_string = re_comp(pattern)) != NULL)
	{
		error("%s", &parg);
		return (-1);
	}
#endif
#if HAVE_REGCMP
	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * A null pattern means use the previous pattern.
		 * The compiled previous pattern is in cpattern, so just use it.
		 */
		if (cpattern == NULL)
		{
			error("No previous regular expression", NULL_PARG);
			return (-1);
		}
	} else
	{
		/*
		 * Otherwise compile the given pattern.
		 */
		char *s;
		if ((s = regcmp(pattern, 0)) == NULL)
		{
			error("Invalid pattern", NULL_PARG);
			return (-1);
		}
		if (cpattern != NULL)
			free(cpattern);
		cpattern = s;
	}
#endif
#if HAVE_V8_REGCOMP
	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * A null pattern means use the previous pattern.
		 * The compiled previous pattern is in regpattern, 
		 * so just use it.
		 */
		if (regpattern == NULL)
		{
			error("No previous regular expression", NULL_PARG);
			return (-1);
		}
	} else
	{
		/*
		 * Otherwise compile the given pattern.
		 */
		struct regexp *s;
		if ((s = regcomp(pattern)) == NULL)
		{
			error("Invalid pattern", NULL_PARG);
			return (-1);
		}
		if (regpattern != NULL)
			free(regpattern);
		regpattern = s;
	}
#endif
#if NO_REGEX
	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * Null pattern means use the previous pattern.
		 */
		if (last_pattern == NULL)
		{
			error("No previous regular expression", NULL_PARG);
			return (-1);
		}
		pattern = last_pattern;
	} else
	{
		strcpy(lpbuf, pattern);
		last_pattern = lpbuf;
	}
#endif

	/*
	 * Figure out where to start the search.
	 */
	if (empty_screen())
	{
		/*
		 * Start at the beginning (or end) of the file.
		 * (The empty_screen() case is mainly for 
		 * command line initiated searches;
		 * for example, "+/xyz" on the command line.)
		 */
		if (goforw)
			pos = ch_zero();
		else 
		{
			pos = ch_length();
			if (pos == NULL_POSITION)
				pos = ch_zero();
		}
	} else 
	{
		if (how_search)
		{
			if (goforw)
				linenum = BOTTOM_PLUS_ONE;
			else
				linenum = TOP;
			pos = position(linenum);
		} else
		{
			linenum = adjsline(jump_sline);
			pos = position(linenum);
			if (goforw)
				pos = forw_raw_line(pos, (char **)NULL);
		}
	}

	if (pos == NULL_POSITION)
	{
		/*
		 * Can't find anyplace to start searching from.
		 */
		error("Nothing to search", NULL_PARG);
		return (-1);
	}

	linenum = find_linenum(pos);
	oldpos = pos;
	for (;;)
	{
		/*
		 * Get lines until we find a matching one or 
		 * until we hit end-of-file (or beginning-of-file 
		 * if we're going backwards).
		 */
		if (sigs)
			/*
			 * A signal aborts the search.
			 */
			return (-1);

		if (goforw)
		{
			/*
			 * Read the next line, and save the 
			 * starting position of that line in linepos.
			 */
			linepos = pos;
			pos = forw_raw_line(pos, &line);
			if (linenum != 0)
				linenum++;
		} else
		{
			/*
			 * Read the previous line and save the
			 * starting position of that line in linepos.
			 */
			pos = back_raw_line(pos, &line);
			linepos = pos;
			if (linenum != 0)
				linenum--;
		}

		if (pos == NULL_POSITION)
		{
			/*
			 * We hit EOF/BOF without a match.
			 */
			return (n);
		}

		/*
		 * If we're using line numbers, we might as well
		 * remember the information we have now (the position
		 * and line number of the current line).
		 * Don't do it for every line because it slows down
		 * the search.  Remember the line number only if
		 * we're "far" from the last place we remembered it.
		 */
		if (linenums && abs((int)(pos - oldpos)) > 1024)
		{
			add_lnum(linenum, pos);
			oldpos = pos;
		}

		if (is_caseless || bs_mode == BS_SPECIAL)
		{
			/*
			 * If this is a caseless search, convert 
			 * uppercase in the input line to lowercase.
			 * While we're at it, remove any backspaces
			 * along with the preceding char.
			 * This allows us to match text which is 
			 * underlined or overstruck.
			 */
			for (p = q = line;  *p != '\0';  p++, q++)
			{
				if (is_caseless && *p >= 'A' && *p <= 'Z')
					/* Convert uppercase to lowercase. */
					*q = *p + 'a' - 'A';
				else if (bs_mode == BS_SPECIAL && q > line && *p == '\b')
					/* Delete BS and preceding char. */
					q -= 2;
				else
					/* Otherwise, just copy. */
					*q = *p;
			}
		}

		/*
		 * Test the next line to see if we have a match.
		 * This is done in a variety of ways, depending
		 * on what pattern matching functions are available.
		 */
#if HAVE_POSIX_REGCOMP
		line_match = !regexec(regpattern, line, 0, NULL, 0);
#endif
#if HAVE_RE_COMP
		line_match = (re_exec(line) == 1);
#endif
#if HAVE_REGCMP
		line_match = (regex(cpattern, line) != NULL);
#endif
#if HAVE_V8_REGCOMP
		line_match = regexec(regpattern, line);
#endif
#if NO_REGEX
		line_match = match(pattern, line);
#endif
		/*
		 * We are successful if want_match and line_match are
		 * both true (want a match and got it),
		 * or both false (want a non-match and got it).
		 */
		if (((want_match && line_match) || (!want_match && !line_match)) &&
		      --n <= 0)
			/*
			 * Found the line.
			 */
			break;
	}

	jump_loc(linepos, jump_sline);
	return (0);
}

#if NO_REGEX
/*
 * We have no pattern matching function from the library.
 * We use this function to do simple pattern matching.
 * It supports no metacharacters like *, etc.
 */
	static int
match(pattern, buf)
	char *pattern, *buf;
{
	register char *pp, *lp;

	for ( ;  *buf != '\0';  buf++)
	{
		for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
			if (*pp == '\0' || *lp == '\0')
				break;
		if (*pp == '\0')
			return (1);
	}
	return (0);
}
#endif
