/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol or text cross-reference
 *
 *	searching functions
 */

#ifdef __BORLANDC__
#include <alloc.h>
#include <dos.h>
#include <time.h>
#include <io.h>
#endif
#include "global.h"
#include "vp.h"

/* most of these functions have been optimized so their innermost loops have
 * only one test for the desired character by putting the char and 
 * an end-of-block marker (\0) at the end of the disk block buffer.
 * When the inner loop exits on the char, an outer loop will see if
 * the char is followed by a \0.  If so, it will read the next block
 * and restart the inner loop.
 */

char	block[BUFSIZ + 2];		/* leave room for end-of-block mark */
int	blocklen;			/* length of disk block read */
char	blockmark;			/* mark character to be searched for */
long	blocknumber;			/* block number */
char	*blockp;			/* pointer to current char in block */
long	lastfcnoffset;			/* last function name offset */
char	lastfilepath[PATHLEN + 1];	/* last file that full path was computed for */
char	*regexp;			/* regular expression */

static	char	cpattern[PATLEN + 1];	/* compressed pattern */
static	long	postingsfound;		/* retrieved number of postings */
static	POSTING	*postingp;		/* retrieved posting set pointer */
static	long	searchcount;		/* count of files searched */
static	long	starttime;		/* start time for progress messages */

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void	findsymbol(void);
void	finddef(void);
void	findallfcns(void);
void	findcalledby(void);
void	findcalledbysub(char *file);
void	findcalling(void);
void	findassignments(void);
char	*findgreppat(void);
char	*findegreppat(char *egreppat);
void	findfile(void);
void	findinclude(void);
FINDINIT findinit(void);
void	findcleanup(void);
void	findterm(void);
void	fileprogress(void);
void	initprogress(void);
void	progress(char *format, long n1, long n2);
BOOL	match(void);
BOOL	matchrest(void);
void	putpostingref(POSTING *p);
void	putref(char *file, char *function);
void	putsource(FILE *output);
void	putline(FILE *output);
void	getstring(char *s);
char	*scanpast(register char c);
char	*readblock(void);
long	dbseek(long offset);
char	*strtolower(char *s);
char	*filepath(char *file);
#else
void	fileprogress();
void	findterm();
void	findcalledbysub();
BOOL	match();
BOOL	matchrest();
void	putline();
void	putpostingref();
void	putref();
void	putsource();
char	*scanpast();
#endif

/* find the symbol in the cross-reference */

void
findsymbol()
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */
	char	symbol[PATLEN + 1];	/* symbol name */
	register char	*cp;
	register char	c;
	register char	*s;
	
	if (invertedindex == YES) {
		long	lastline = 0;
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type != INCLUDE && p->lineoffset != lastline) {
				putpostingref(p);
				lastline = p->lineoffset;
			}
		}
		return;
	}
	(void) scanpast('\t');		/* find the end of the header */
	skiprefchar();			/* skip the file marker */
	getstring(file);		/* save the file name */
	*function = '\0';
	*macro = '\0';	/* a macro can be inside a function, but not vice versa */
	
	/* find the next symbol */
	/* note: this code was expanded in-line for speed */
	/* while (scanpast('\n') != NULL) { */
	/* other macros were replaced by code using cp instead of blockp */
	cp = blockp;
	for (;;) {
		setmark('\n');
		do {	/* innermost loop optimized to only one test */
			while (*cp != '\n') {
				++cp;
			}
		} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);

		/* skip the found character */
		if (cp != NULL && *(++cp + 1) == '\0') {
			cp = readblock();
		}
		if (cp == NULL) {
			break;
		}
		/* look for a source file or function name */
		if (*cp == '\t') {
			blockp = cp;
			switch (getrefchar()) {

			case NEWFILE:		/* file name */

				/* save the name */
				skiprefchar();
				getstring(file);
			
				/* check for the end of the symbols */
				if (*file == '\0') {
					return;
				}
				fileprogress();
				/* FALLTHROUGH */

			case FCNEND:		/* function end */
				*function = '\0';
				goto notmatched;	/* don't match name */
				
			case FCNDEF:		/* function name */
				s = function;
				break;

			case DEFINE:		/* could be a macro */
				if (fileversion >= 10) {
					s = macro;
				}
				else {
					s = symbol;
				}
				break;
	
			case DEFINEEND:
				*macro = '\0';
				goto notmatched;	/* don't match name */
	
			case INCLUDE:		/* #include file */
				goto notmatched;	/* don't match name */
			default:		/* other symbol */
				s = symbol;
			}
			/* save the name */
			skiprefchar();
			getstring(s);

			/* see if this is a regular expression pattern */
			if (regexp != NULL) {
				if (caseless == YES) {
					s = strtolower(s);
				}
				if (*s != '\0' && regex(regexp, s) != NULL) {
					goto matched;
				}
			}
			/* match the symbol to the text pattern */
			else if (strequal(pattern, s)) {
				goto matched;
			}
			goto notmatched;
		}
		/* if this is a regular expression pattern */
		if (regexp != NULL) {
			c = *cp;
			if (c & 0200) {	/* digraph char? */
				c = dichar1[(c & 0177) / 8];
			}
			/* if this is a symbol */
			if (isalpha(c) || c == '_') {
				blockp = cp;
				getstring(symbol);
				s = symbol;
				if (caseless == YES) {
					s = strtolower(s);
				}
				/* match the symbol to the regular expression */
				if (regex(regexp, s) != NULL) {
					goto matched;
				}
				goto notmatched;
			}
		}
		/* match the character to the text pattern */
		else if (*cp == cpattern[0]) {
			blockp = cp;

			/* match the rest of the symbol to the text pattern */
			if (matchrest()) {
				s = NULL;
		matched:
				/* output the file, calling function or macro,
				   and source line */
				if (*macro != '\0' && s != macro) {
					putref(file, macro);
				}
				else if (s != function) {
					putref(file, function);
				}
				else {
					putref(file, "");
				}
				if (blockp == NULL) {
					return;
				}
			}
		notmatched:
			cp = blockp;
		}
	}
	blockp = cp;
}

/* find the function definition or #define */

void
finddef()
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */
	char	symbol[PATLEN + 1];	/* symbol name */
	register char	*s;

	if (invertedindex == YES) {
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case DEFINE:		/* could be a macro */
			case FCNDEF:
			case CLASSDEF:
			case ENUMDEF:
			case MEMBERDEF:
			case STRUCTDEF:
			case TYPEDEF:
			case UNIONDEF:
			case GLOBALDEF:		/* other global definition */
			case LOCALDEF:		/* other local definition */
			case PARAMETER:
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or definition */
	*function = '\0';
	*macro = '\0';	/* a macro can be inside a function, but not vice versa */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */
			
		case FCNEND:		/* function end */
			*function = '\0';
			break;
			
		case FCNDEF:		/* function name */
			s = function;
			goto def;

		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				s = macro;
			}
			else {
				s = symbol;
			}
			goto def;

		case DEFINEEND:
			*macro = '\0';
			break;

		case CLASSDEF:
		case ENUMDEF:
		case MEMBERDEF:
		case STRUCTDEF:
		case TYPEDEF:
		case UNIONDEF:
		case GLOBALDEF:		/* other global definition */
		case LOCALDEF:		/* other local definition */
		case PARAMETER:
			s = symbol;
		def:
			/* save the name */
			skiprefchar();
			getstring(s);

			/* see if this is a regular expression pattern */
			if (regexp != NULL) {
				if (caseless == YES) {
					s = strtolower(s);
				}
				if (*s != '\0' && regex(regexp, s) != NULL) {
					goto matched;
				}
			}
			/* match the symbol to the text pattern */
			else if (strequal(pattern, s)) {
		matched:
				/* output the file, calling function or macro,
				   and source line */
				if (*macro != '\0' && s != macro) {
					putref(file, macro);
				}
				else if (s != function) {
					putref(file, function);
				}
				else {
					putref(file, "");
				}
			}
		}
	}
}

/* find all function definitions (used by samuel only) */

void
findallfcns()
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */

	/* find the next file name or definition */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;

		case FCNDEF:
		case CLASSDEF:
			skiprefchar();	/* save function name */
			getstring(function);

			/* output the file, function and source line */
			putref(file, function);
			break;
		}
	}
}

/* find the functions called by this function */

void
findcalledby()
{
	char	file[PATHLEN + 1];	/* source file name */

	if (invertedindex == YES) {
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case DEFINE:		/* could be a macro */
			case FCNDEF:
				if (dbseek(p->lineoffset) != -1 &&
				    scanpast('\t') != NULL) {	/* skip def */
					findcalledbysub(srcfiles[p->fileindex]);
				}
			}
		}
		return;
	}
	/* find the function definition(s) */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;

		case DEFINE:		/* could be a macro */
			if (fileversion < 10) {
				break;
			}
			/* FALLTHROUGH */

		case FCNDEF:
			skiprefchar();	/* match name to pattern */
			if (match()) {
				findcalledbysub(file);
			}
			break;
		}
	}
}

void
findcalledbysub(file)
char	*file;
{
	/* find the next function call or the end of this function */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		
		case DEFINE:		/* #define inside a function */
			if (fileversion >= 10) {	/* skip it */
				while (scanpast('\t') != NULL &&
				    *blockp != DEFINEEND) 
					;
			}
			break;
		
		case FCNCALL:		/* function call */

			/* output the file name */
			(void) fprintf(refsfound, "%s ", filepath(file));

			/* output the function name */
			skiprefchar();
			putline(refsfound);
			(void) putc(' ', refsfound);

			/* output the source line */
			putsource(refsfound);
			break;

		case DEFINEEND:		/* #define end */
		case FCNEND:		/* function end */
		case FCNDEF:		/* function end (pre 9.5) */
		case NEWFILE:		/* file end */
			return;
		}
	}
}

/* find the functions calling this function */

void
findcalling()
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */

	if (invertedindex == YES) {
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type == FCNCALL) {
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	*macro = '\0';	/* a macro can be inside a function, but not vice versa */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */
			
		case FCNEND:		/* function end */
			*function = '\0';
			break;
			
		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				skiprefchar();
				getstring(macro);
			}
			break;

		case DEFINEEND:
			*macro = '\0';
			break;

		case FCNDEF:		/* save calling function name */
			skiprefchar();
			getstring(function);
			break;
			
		case FCNCALL:		/* match function called to pattern */
			skiprefchar();
			if (match()) {
				
				/* output the file, calling function or macro, and source */
				if (*macro != '\0') {
					putref(file, macro);
				}
				else {
					putref(file, function);
				}
			}
		}
	}
}

/* find direct assignment to, and increment and decrement of, this variable */

void
findassignments()
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */

	if (fileversion < 13) {
		putmsg("Database built with cscope version < 13 does not have assignment information");
		(void) sleep(3);
		return;
	}
#if CTRACE
	ctroff();
#endif
	if (invertedindex == YES) {
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case ASSIGNMENT:
			case GLOBALDEF:		/* can have initializer */
			case LOCALDEF:		/* can have initializer */
			case PARAMETER:		/* initial value */
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	*macro = '\0';	/* a macro can be inside a function, but not vice versa */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */
			
		case FCNEND:		/* function end */
			*function = '\0';
			break;
			
		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				skiprefchar();
				getstring(macro);
			}
			break;

		case DEFINEEND:
			*macro = '\0';
			break;

		case FCNDEF:		/* save calling function name */
			skiprefchar();
			getstring(function);
			break;
			
		case ASSIGNMENT:	/* match assignment to pattern */
		case GLOBALDEF:		/* can have initializer */
		case LOCALDEF:		/* can have initializer */
		case PARAMETER:		/* initial value */
			skiprefchar();
			if (match()) {
				
				/* output the file, calling function or macro, and source */
				if (*macro != '\0') {
					putref(file, macro);
				}
				else {
					putref(file, function);
				}
			}
		}
	}
}

/* find the grep pattern in the source files */

char *
findgreppat()
{
	char	egreppat[2 * PATLEN];
	char	*cp, *pp;

	/* translate egrep special characters in the regular expression */
	cp = egreppat;
	for (pp = pattern; *pp != '\0'; ++pp) {
		if (strchr("+?|()", *pp) != NULL) {
			*cp++ = '\\';
		}
		*cp++ = *pp;
	}
	*cp = '\0';
	
	/* search the source files */
	return(findegreppat(egreppat));
}

/* find this regular expression in the source files */

char *
findegreppat(egreppat)
char	*egreppat;
{
	register int	i;
	char	*egreperror;
	char	msg[MSGLEN + 1];
	BOOL	allfiles = YES;	/* assume a search of all files */

	/**********************************************************************
	 * In large systems, searching all the files for an egrep pattern can
	 * take a long time (hour!).  Often such a search can be limited by
	 * the user to a relatively small subset of the files and order-of-magnitude
	 * performance improvements can be realized.  If the user specified '-a'
	 * on the command line, then the subset of files can be expressed as a
	 * regular expression.  This is stored in subdirpat[].  If the user
	 * STILL wants to search all files then either a carriage return or
	 * a "." (which is a regular expression that matches all files) can be
	 * specified.
	 **********************************************************************/
	if(askegrep == YES) {	/* -a option specified */
		
		/* Check for all-inclusive file pattern */
		if((subdirpat[0] != '\0') && strcmp(subdirpat,".")) {
			
			allfiles = NO;	/* subset of all files */
			if((regexp = regcmp(subdirpat,(char *)NULL)) == NULL) {
				static char errmsg[128];
				
				sprintf(errmsg,"error in file pattern '%s' (regcmp(3X) error)",
					subdirpat);
				return(errmsg);
			}
		}
	}

	/* compile the pattern */
	if ((egreperror = egrepinit(egreppat)) == NULL) {

		/* search the files */
		for (i = 0; i < nsrcfiles; ++i) {
			register char *file = filepath(srcfiles[i]);
			/* Skip the file if it doesn't match the pattern */
			if((allfiles == NO) && (regex(regexp,file) == NULL)) {
				continue;	/* skip this file */
			}
#ifdef __BORLANDC__
			(void) sprintf(msg,"Searching %s ", file);
			putmsg2(msg);
#else
			fileprogress();
#endif
			if (egrep(file, refsfound, "%s <unknown> %ld ") < 0) {
				(void) sprintf(msg, "Cannot open file %s",
				    file);
				putmsg2(msg);
			}
		}
	}
	return(egreperror);
}

/* find matching file names */

void
findfile()
{
	register int	i;
	register char	*s;
	
	for (i = 0; i < nsrcfiles; ++i) {
		s = srcfiles[i];
		if (caseless == YES) {
#ifndef __BORLANDC__
			s = strtolower(s);
#endif
		}
		if (regex(regexp, s) != NULL) {
			(void) fprintf(refsfound, "%s <unknown> 1 <unknown>\n", 
				filepath(srcfiles[i]));
		}
	}
}

/* find files #including this file */

void
findinclude()
{
	char	file[PATHLEN + 1];	/* source file name */

	if (invertedindex == YES) {
		POSTING	*p;
		
		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type == INCLUDE) {
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
			
		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;
			
		case INCLUDE:		/* match function called to pattern */
			skiprefchar();
			skiprefchar();	/* skip global or local #include marker */
			if (match()) {
				
				/* output the file and source line */
				putref(file, "");
			}
		}
	}
}

/* initialize */

FINDINIT
findinit()
{
	char	buf[PATLEN + 3];
	BOOL	isregexp = NO;
	register int	i;
	register char	*s;
	register unsigned c;

	/* remove trailing white space */
	for (s = pattern + strlen(pattern) - 1; isspace(*s); --s) {
		*s = '\0';
	}
	/* allow a partial match for a file name */
	if (field == FILENAME || field == INCLUDES) {
#ifdef __BORLANDC__
		strtoupper(pattern);	/* filenames are all upper case in MS-DOS */
#endif
		/* allow types.h to match #include <sys/types.h> */
		if (invertedindex == YES && field == INCLUDES &&
		    strncmp(pattern, ".*", 2) != 0) {
			(void) sprintf(pattern, ".*%s", strcpy(buf, pattern));
		}
		if ((regexp = regcmp(pattern, (char *) NULL)) == NULL) {
			return(REGCMPERROR);
		}
		return(NOERROR);
	}
	/* see if the pattern is a regular expression */
	if (strpbrk(pattern, "^.[{*+$") != NULL) {
		isregexp = YES;
	}
	else {
		/* check for a valid C symbol */
		s = pattern;
		if (!isalpha(*s) && *s != '_') {
			return(NOTSYMBOL);
		}
		while (*++s != '\0') {
			if (!isalnum(*s) && *s != '_') {
				return(NOTSYMBOL);
			}
		}
		/* look for use of the -T option (truncate symbol to 8
		   characters) on a database not built with -T */
		if (truncateto8 == YES && isuptodate == YES &&
		    dbtruncated == NO && s - pattern >= 8) {
			(void) strcpy(pattern + 8, ".*");
			isregexp = YES;
		}
	}
	/* if this is a regular expression or letter case is to be ignored */
	/* or there is an inverted index */
	if (isregexp == YES || caseless == YES || invertedindex == YES) {

		/* remove a leading ^ */
		s = pattern;
		if (*s == '^') {
			(void) strcpy(newpat, s + 1);
			(void) strcpy(s, newpat);
		}
		/* remove a trailing $ */
		i = strlen(s) - 1;
		if (s[i] == '$') {
			s[i] = '\0';
		}
		/* if requested, try to truncate a C symbol pattern */
		if (truncateto8 == YES && strpbrk(s, "[{*+") == NULL) {
			s[8] = '\0';
		}
		/* must be an exact match */
		/* note: regcmp doesn't recognize ^*keypad$ as an syntax error
		   unless it is given as a single arg */
		(void) sprintf(buf, "^%s$", s);
		if ((regexp = regcmp(buf, (char *) NULL)) == NULL) {
			return(REGCMPERROR);
		}
	}
	else {
		/* if requested, truncate a C symbol pattern */
		if (truncateto8 == YES && field <= CALLING) {
			pattern[8] = '\0';
		}
		/* compress the string pattern for matching */
		s = cpattern;
		for (i = 0; (c = pattern[i]) != '\0'; ++i) {
			if (dicode1[c] && dicode2[(unsigned) pattern[i + 1]]) {
				c = (0200 - 2) + dicode1[c] + dicode2[(unsigned) pattern[i + 1]];
				++i;
			}
			*s++ = c;
		}
		*s = '\0';
	}
	return(NOERROR);
}

void
findcleanup()
{
	/* discard any regular expression */
	if (regexp != NULL) {
		free(regexp);
		regexp = NULL;
	}
}

/* find this term, which can be a regular expression */

void
findterm()
{
	register char	*s;
	register int	len;
	char	prefix[PATLEN + 1];
	char	term[PATLEN + 1];

	npostings = 0;		/* will be non-zero after database built */
	lastfcnoffset = 0;	/* clear the last function name found */
	boolclear();		/* clear the posting set */

	/* get the string prefix (if any) of the regular expression */
	(void) strcpy(prefix, pattern);
	if ((s = strpbrk(prefix, ".[{*+")) != NULL) {
		*s = '\0';
	}
	/* if letter case is to be ignored */
	if (caseless == YES) {
		
		/* convert the prefix to upper case because it is lexically
		   less than lower case */
		s = prefix;
		while (*s != '\0') {
			*s = toupper(*s);
			++s;
		}
	}
	/* find the term lexically >= the prefix */
	(void) invfind(&invcontrol, prefix);
	if (caseless == YES) {	/* restore lower case */
		(void) strcpy(prefix, strtolower(prefix));
	}
	/* a null prefix matches the null term in the inverted index,
	   so move to the first real term */
	if (*prefix == '\0') {
		(void) invforward(&invcontrol);
	}
	len = strlen(prefix);
	do {
		(void) invterm(&invcontrol, term);	/* get the term */
		s = term;
		if (caseless == YES) {
			s = strtolower(s);	/* make it lower case */
		}
		/* if it matches */
		if (regex(regexp, s) != NULL) {
	
			/* add it's postings to the set */
			if ((postingp = boolfile(&invcontrol, &npostings, OR)) == NULL) {
				break;
			}
		}
		/* if there is a prefix */
		else if (len > 0) {
			
			/* if ignoring letter case and the term is out of the
			   range of possible matches */
			if (caseless == YES) {
				if (strncmp(term, prefix, len) > 0) {
					break;	/* stop searching */
				}
			}
			/* if using letter case and the prefix doesn't match */
			else if (strncmp(term, prefix, len) != 0) {
				break;	/* stop searching */
			}
		}
		/* display progress about every three seconds */
		if (++searchcount % 50 == 0) {
			progress("%ld of %ld symbols matched", searchcount, totalterms);
		}
	} while (invforward(&invcontrol));	/* while didn't wrap around */
	
	/* initialize the progress message for retrieving the references */
	initprogress();
	postingsfound = npostings;
}

/* display the file search progress about every three seconds */

void
fileprogress()
{
	if (++searchcount % 10 == 0) {
		progress("%ld of %ld files searched", searchcount, (long) nsrcfiles);
	}
}

/* initialize the progress message */

void
initprogress()
{
	searchcount = 0;
	starttime = time(NULL);
}

/* display the progress every three seconds */

void
progress(format, n1, n2)
char	*format;
long	n1, n2;
{
	char	msg[MSGLEN + 1];
	long	now;

	/* print after 2 seconds so the average is nearer 3 seconds */
	if (linemode == NO && (now = time(NULL)) - starttime >= 2) {
		starttime = now;
		(void) sprintf(msg, format, n1, n2);
		putmsg(msg);
	}
}

/* match the pattern to the string */

BOOL
match()
{
	char	string[PATLEN + 1];
	char	*s;

	/* see if this is a regular expression pattern */
	if (regexp != NULL) {
		getstring(string);
		if (*string == '\0') {
			return(NO);
		}
		s = string;
		if (caseless == YES) {
			s = strtolower(s);
		}
		return(regex(regexp, s) ? YES : NO);
	}
	/* it is a string pattern */
	return((BOOL) (*blockp == cpattern[0] && matchrest()));
}

/* match the rest of the pattern to the name */

BOOL
matchrest()
{
	int	i = 1;
	
	skiprefchar();
	do {
		while (*blockp == cpattern[i]) {
			++blockp;
			++i;
		}
	} while (*(blockp + 1) == '\0' && readblock() != NULL);
	
	if (*blockp == '\n' && cpattern[i] == '\0') {
		return(YES);
	}
	return(NO);
}

/* peek at the next posting for this term */

POSTING *
peekposting()
{
	return(postingp);
}

/* get the next posting for this term */

POSTING *
getposting()
{
	if (npostings-- <= 0) {
		return(NULL);
	}
	/* display progress about every three seconds */
	if (++searchcount % 100 == 0) {
		progress("%ld of %ld possible references retrieved", searchcount,
		    postingsfound);
	}
	return(postingp++);
}

/* put the posting reference into the file */

void
putpostingref(p)
POSTING	*p;
{
	static	char	function[PATLEN + 1];	/* function name */

	if (p->fcnoffset == 0) {
		*function = '\0';
	}
	else if (p->fcnoffset != lastfcnoffset) {
		if (dbseek(p->fcnoffset) != -1) {
			getstring(function);
			lastfcnoffset = p->fcnoffset;
		}
	}
	if (dbseek(p->lineoffset) != -1) {
		putref(srcfiles[p->fileindex], function);
	}
}

/* put the reference into the file */

void
putref(file, function)
char	*file;
char	*function;
{
	FILE	*output;

#ifdef MULTILINGUAL
	/* Set scanner data for accessing compressed keywords */
	setscanner(file);

#endif
	/* put global references first */
	if (*function == '\0') {
		function = "<global>";
		output = refsfound;
	}
	else {
		output = nonglobalrefs;
	}
	if (fprintf(output, "%s %s ", filepath(file), function) == EOF) {
		cannotwrite(temp1);
		/* NOTREACHED */
	}
	putsource(output);
}

/* put the source line into the file */

void
putsource(output)
FILE	*output;
{
	register char	*cp, nextc = '\0';
	
	if (fileversion <= 5) {
		(void) scanpast(' ');
		putline(output);
		(void) putc('\n', output);
		return;
	}
	/* scan back to the beginning of the source line */
	cp = blockp;
	while (*cp != '\n' || nextc != '\n') {
		nextc = *cp;
		if (--cp < block) {
			
			/* read the previous block */
			(void) dbseek((blocknumber - 1) * BUFSIZ);
			cp = &block[BUFSIZ - 1];
		}
	}
	/* there must be a double newline followed by a line number */
	blockp = cp;
	setmark(' ');	/* so getrefchar doesn't skip the last block char */
	if (*blockp != '\n' || getrefchar() != '\n' ||
	    !isdigit(getrefchar()) && fileversion >= 12) {
		putmsg("Internal error: cannot get source line from database");
		myexit(1);
	}
	/* until a double newline is found */
	do {
		/* skip a symbol type */
		if (*blockp == '\t') {
			skiprefchar();
			skiprefchar();
		}
		/* output a piece of the source line */
		putline(output);
	} while (blockp != NULL && getrefchar() != '\n');
	(void) putc('\n', output);
}

/* put the rest of the cross-reference line into the file */

void
putline(output)
FILE	*output;
{
	register char	*cp;
	register unsigned c;
	register struct	keystruct *p;
	
	setmark('\n');
	cp = blockp;
	do {
		while ((c = *cp) != '\n') {
			
			/* check for a compressed digraph */
			if (c & 0200) {
				c &= 0177;
				(void) putc(dichar1[c / 8], output);
				(void) putc(dichar2[c & 7], output);
			}
			/* check for a compressed keyword */
			else if (c < ' ') {
#ifdef MULTILINGUAL
				p = keyword(c);
#else
				p = &keyword[c];
#endif
				(void) fputs(p->text, output);
				if (p->delim != '\0') {
					(void) putc(' ', output);
				}
				if (p->delim == '(') {
					(void) putc('(', output);
				}
			}
			else {
				(void) putc((int) c, output);
			}
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
}

/* put the rest of the cross-reference line into the string */

void
getstring(s)
register char	*s;
{
	register char	*cp;
	register unsigned c;
	
	setmark('\n');
	cp = blockp;
	do {
		while ((c = *cp) != '\n') {
			if (c & 0200) {
				c &= 0177;
				*s++ = dichar1[c / 8];
				*s++ = dichar2[c & 7];
			}
			else {
				*s++ = c;
			}
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
	*s = '\0';
}

/* scan past the next occurence of this character in the cross-reference */

#if defined( __STDC__ ) || defined( __BORLANDC__ )
char *scanpast(register char c)
{
#else
char	*
scanpast(c)
register char	c;
{
#endif
	register char	*cp;
	
	setmark(c);
	cp = blockp;
	do {	/* innermost loop optimized to only one test */
		while (*cp != c) {
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
	if (cp != NULL) {
		skiprefchar();	/* skip the found character */
	}
	return(blockp);
}

/* read a block of the cross-reference */

char	*
readblock()
{
	/* read the next block */
	blocklen = read(symrefs, block, BUFSIZ);
	blockp = block;
	
	/* add the search character and end-of-block mark */
	block[blocklen] = blockmark;
	block[blocklen + 1] = '\0';
	
	/* return NULL on end-of-file */
	if (blocklen == 0) {
		blockp = NULL;
	}
	else {
		++blocknumber;
	}
	return(blockp);
}

/* seek to the database offset */

long
dbseek(offset)
long offset;
{
	long	n;
	int	rc = 0;
	
	if ((n = offset / BUFSIZ) != blocknumber) {
#if cygwin || darwin
		if ((rc = lseek(symrefs, (off_t)(n * BUFSIZ), 0)) == -1) {
#else
		if ((rc = lseek(symrefs, n * BUFSIZ, 0)) == -1) {
#endif
			myperror("Lseek failed");
			(void) sleep(3);
			return(rc);
		}
		(void) readblock();
		blocknumber = n;
	}
	blockp = block + offset % BUFSIZ;
	return(rc);
}

/* convert the string to lower case */

char	*
strtolower(s)
register char	*s;
{
	static char buf[PATLEN + 1];
	register char *lp = buf;
	
	while (*s != '\0') {
		
		/* note: s in not incremented in this line because the BSD
		   compatibility tolower macro evaluates its argument twice */
		*lp++ = tolower(*s);
		++s;
	}
	*lp = '\0';
	return(buf);
}

/* if needed, convert a relative path to a full path */

char *
filepath(file)
char	*file;
{
	static	char	path[PATHLEN + 1];
	int	i;
	
	if (*file != '/'
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	    && *file != '\\' && file[1] != ':'
#endif
	    ) {

		/* if this is the same file as last time, return the same path */
		if (strequal(file, lastfilepath)) {
			return(path);
		}
		(void) strcpy(lastfilepath, file);

		/* if requested, prepend a path to a relative file path */
		if (prependpath != NULL) {
			(void) sprintf(path, "%s/%s", prependpath, file);
			return(path);
		}
		/* if the database was built with a view path, return a full path so
		   "cscope -d -f" does not have to be called from the build directory
		   with the same view path */
		if (dbvpndirs > 1) {
			for (i = 0; i < dbvpndirs; i++) {
				(void) sprintf(path, "%s/%s", dbvpdirs[i], file);
				if (access(compath(path), READ) != -1) {
					return(path);
				}
			}
		}
		(void) strcpy(path, file);	/* for lastfilepath check */
	}
	return(file);
}
