/***************************************************************************
 * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne.  JOVE *
 * is provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is    *
 * included in all the files.                                              *
 ***************************************************************************/

#include "jove.h"
#include "io.h"
#include "re.h"
#include "ctype.h"

#ifdef MAC
#	include "mac.h"
#else
#	include <sys/stat.h>
#endif

#ifdef MAC
#	undef private
#	define private
#endif

#ifdef	LINT_ARGS
private Bufpos * doisearch(int, int, int);

private void
	IncSearch(int),
	replace(int, int);
private int
	isearch(int, Bufpos *),
	lookup(char *, char *, char *, char *),
	substitute(int, Line *, int, Line *, int);
#else
private Bufpos * doisearch();

private void
	IncSearch(),
	replace();
private int
	isearch(),
	lookup(),
	substitute();
#endif	/* LINT_ARGS */

#ifdef MAC
#	undef private
#	define private static
#endif

private int
substitute(query, l1, char1, l2, char2)
Line	*l1,
	*l2;
{
	Line	*lp;
	int	numdone = 0,
		offset = curchar,
		stop = NO;
	disk_line	UNDO_da = 0;
	Line		*UNDO_lp = 0;

	lsave();
	REdirection = FORWARD;

	lp = l1;
	for (lp = l1; (lp != l2->l_next) && !stop; lp = lp->l_next) {
		offset = (lp == l1) ? char1 : 0;
		while (!stop && re_lindex(lp, offset, compbuf, alternates, 0)) {
			if (lp == l2 && REeom > char2)	/* nope, leave this alone */
				break;
			DotTo(lp, REeom);
			offset = curchar;
			if (query) {
				message("Replace (Type '?' for help)? ");
reswitch:			redisplay();
				switch (CharUpcase(getchar())) {
				case '.':
					stop = YES;
					/* Fall into ... */

				case ' ':
				case 'Y':
					break;

				case BS:
				case RUBOUT:
				case 'N':
					if (linebuf[offset++] == '\0')
						goto nxtline;
					continue;

				case CTL('W'):
					re_dosub(linebuf, YES);
					numdone += 1;
					offset = curchar = REbom;
					makedirty(curline);
					/* Fall into ... */

				case CTL('R'):
				case 'R':
					RErecur();
					offset = curchar;
					lp = curline;
					continue;

				case CTL('U'):
				case 'U':
					if (UNDO_lp == 0)
						continue;
					lp = UNDO_lp;
					lp->l_dline = UNDO_da | DIRTY;
					offset = 0;
					numdone -= 1;
					continue;

				case 'P':
				case '!':
					query = 0;
					break;

				case CR:
				case LF:
				case 'Q':
					goto done;

				case CTL('L'):
					RedrawDisplay();
					goto reswitch;

				default:
					rbell();
message("Space or Y, Period, Rubout or N, C-R or R, C-W, C-U or U, P or !, Return.");
					goto reswitch;
				}
			}
			re_dosub(linebuf, NO);
			numdone += 1;
			modify();
			offset = curchar = REeom;
			makedirty(curline);
			if (query) {
				message(mesgbuf);	/* no blinking */
				redisplay();		/* show the change */
			}
			UNDO_da = curline->l_dline;
			UNDO_lp = curline;
			if (linebuf[offset] == 0)
nxtline:			break;
		}
	}
done:	return numdone;
}

/* prompt for search and replacement strings and do the substitution */
private void
replace(query, inreg)
{
	Mark	*m;
	char	*rep_ptr;
	Line	*l1 = curline,
		*l2 = curbuf->b_last;
	int	char1 = curchar,
		char2 = length(curbuf->b_last),
		numdone;

	if (inreg) {
		m = CurMark();
		l2 = m->m_line;
		char2 = m->m_char;
		(void) fixorder(&l1, &char1, &l2, &char2);
	}

	/* get search string */
	strcpy(rep_search, ask(rep_search[0] ? rep_search : (char *) 0, ProcFmt));
	REcompile(rep_search, UseRE, compbuf, alternates);
	/* Now the replacement string.  Do_ask() so the user can play with
	   the default (previous) replacement string by typing C-R in ask(),
	   OR, he can just hit Return to replace with nothing. */
	rep_ptr = do_ask("\r\n", (int (*)()) 0, rep_str, ": %f %s with ", rep_search);
	if (rep_ptr == 0)
		rep_ptr = NullStr;
	strcpy(rep_str, rep_ptr);

	if (((numdone = substitute(query, l1, char1, l2, char2)) != 0) &&
	    (inreg == NO)) {
		do_set_mark(l1, char1);
		add_mess(" ");		/* just making things pretty */
	} else
		message("");
	add_mess("(%d substitution%n)", numdone, numdone);
}

void
RegReplace()
{
	replace(0, YES);
}

void
QRepSearch()
{
	replace(1, NO);
}

void
RepSearch()
{
	replace(0, NO);
}

/* Lookup a tag in tag file FILE.  FILE is assumed to be sorted
   alphabetically.  The FASTTAGS code, which is implemented with
   a binary search, depends on this assumption.  If it's not true
   it is possible to comment out the fast tag code (which is clearly
   labeled) and everything else will just work. */

private int
lookup(searchbuf, filebuf, tag, file)
char	*searchbuf,
	*filebuf,
	*tag,
	*file;
{
	register int	taglen = strlen(tag);
	char	line[BUFSIZ],
		pattern[128];
	register File	*fp;
	struct stat	stbuf;
	int	fast = YES,
		success = NO;
	register off_t	lower, upper;

	sprintf(pattern, "^%s[^\t]*\t*\\([^\t]*\\)\t*[?/]\\([^?/]*\\)[?/]", tag);
	fp = open_file(file, iobuff, F_READ, !COMPLAIN, QUIET);
	if (fp == NIL)
		return 0;

	/* ********BEGIN FAST TAG CODE******** */

	if (stat(file, &stbuf) < 0)
		fast = NO;
	else {
		lower = 0;
		upper = stbuf.st_size;
		if (upper - lower < BUFSIZ)
			fast = NO;
	}
	if (fast == YES) for (;;) {
		off_t	mid;
		int	whichway,
			chars_eq;

		if (upper - lower < BUFSIZ) {
			f_seek(fp, lower);
			break;			/* stop this nonsense */
		}
		mid = (lower + upper) / 2;
		f_seek(fp, mid);
		f_toNL(fp);
		if (f_gets(fp, line, sizeof line) == EOF)
			break;
		chars_eq = numcomp(line, tag);
		if (chars_eq == taglen && iswhite(line[chars_eq]))
			goto found;
		whichway = line[chars_eq] - tag[chars_eq];
		if (whichway < 0) {		/* line is BEFORE tag */
			lower = mid;
			continue;
		} else if (whichway > 0) {	/* line is AFTER tag */
			upper = mid;
			continue;
		}
	}
	f_toNL(fp);
	/* END FAST TAG CODE */

	while (f_gets(fp, line, sizeof line) != EOF) {
		int	cmp;

		if (line[0] > *tag)
			break;
		else if ((cmp = strncmp(line, tag, taglen)) > 0)
			break;
		else if (cmp < 0)
			continue;
		/* if we get here, we've found the match */
found:		if (!LookingAt(pattern, line, 0)) {
			complain("I thought I saw it!");
			break;
		} else {
			putmatch(1, filebuf, FILESIZE);
			putmatch(2, searchbuf, 100);
			success = YES;
			break;
		}
	}
	close_file(fp);
		
	if (success == NO)
		s_mess("Can't find tag \"%s\".", tag);
	return success;
}

#ifndef MSDOS
char	TagFile[FILESIZE] = "./tags";
#else /* MSDOS */
char	TagFile[FILESIZE] = "tags";
#endif /* MSDOS */

void
find_tag(tag, localp)
char	*tag;
{
	char	filebuf[FILESIZE],
		sstr[100],
		tfbuf[FILESIZE];
	register Bufpos	*bp;
	register Buffer	*b;
	char	*tagfname;

	if (!localp) 
		tagfname = ask_file("With tag file: ", TagFile, tfbuf);
	else
		tagfname = TagFile;
	if (lookup(sstr, filebuf, tag, tagfname) == 0)
		return;
	set_mark();
	b = do_find(curwind, filebuf, 0);
	if (curbuf != b)
		SetABuf(curbuf);
	SetBuf(b);
	if ((bp = dosearch(sstr, BACKWARD, 0)) == 0 &&
	    ((bp = dosearch(sstr, FORWARD, 0)) == 0))
		message("Well, I found the file, but the tag is missing.");
	else
		SetDot(bp);
}

void
FindTag()
{
	int	localp = !is_an_arg();
	char	tag[128];

	strcpy(tag, ask((char *) 0, ProcFmt));
	find_tag(tag, localp);
}

/* Find Tag at Dot. */

void
FDotTag()
{
	int	c1 = curchar,
		c2 = c1;
	char	tagname[50];

	if (!ismword(linebuf[curchar]))
		complain("Not a tag!");
	while (c1 > 0 && ismword(linebuf[c1 - 1]))
		c1 -= 1;
	while (ismword(linebuf[c2]))
		c2 += 1;

	null_ncpy(tagname, linebuf + c1, c2 - c1);
	find_tag(tagname, !is_an_arg());
}

/* I-search returns a code saying what to do:
   STOP:	We found the match, so unwind the stack and leave
		where it is.
   DELETE:	Rubout the last command.
   BACKUP:	Back up to where the isearch was last NOT failing.

   When a character is typed it is appended to the search string, and
   then, isearch is called recursively.  When C-S or C-R is typed, isearch
   is again called recursively. */

#define STOP	1
#define DELETE	2
#define BACKUP	3
#define TOSTART	4

static char	ISbuf[128],
		*incp = 0;
int	SExitChar = CR;

#define cmp_char(a, b)	((a) == (b) || (CaseIgnore && (CharUpcase(a) == CharUpcase(b))))

static Bufpos *
doisearch(dir, c, failing)
register int	c,
		dir,
		failing;
{
	static Bufpos	buf;
	Bufpos	*bp;
	extern int	okay_wrap;

	if (c == CTL('S') || c == CTL('R'))
		goto dosrch;

	if (failing)
		return 0;
	DOTsave(&buf);
	if (dir == FORWARD) {
		if (cmp_char(linebuf[curchar], c)) {
			buf.p_char = curchar + 1;
			return &buf;
		}
	} else {
		if (look_at(ISbuf))
			return &buf;
	}
dosrch:	okay_wrap = YES;
	if ((bp = dosearch(ISbuf, dir, 0)) == 0)
		rbell();	/* ring the first time there's no match */
	okay_wrap = NO;
	return bp;
}

void
IncFSearch()
{
	IncSearch(FORWARD);
}

void
IncRSearch()
{
	IncSearch(BACKWARD);
}

private void
IncSearch(dir)
{
	Bufpos	save_env;

	DOTsave(&save_env);
	ISbuf[0] = 0;
	incp = ISbuf;
	if (isearch(dir, &save_env) == TOSTART)
		SetDot(&save_env);
	else {
		if (LineDist(curline, save_env.p_line) >= MarkThresh)
			do_set_mark(save_env.p_line, save_env.p_char);
	}
	setsearch(ISbuf);
}

/* Nicely recursive. */

private int
isearch(dir, bp)
Bufpos	*bp;
{
	Bufpos	pushbp;
	int	c,
		ndir,
		failing;
	char	*orig_incp;

	if (bp != 0) {		/* Move to the new position. */
		pushbp.p_line = bp->p_line;
		pushbp.p_char = bp->p_char;
		SetDot(bp);
		failing = 0;
	} else {
		DOTsave(&pushbp);
		failing = 1;
	}
	orig_incp = incp;
	ndir = dir;		/* Same direction as when we got here, unless
				   we change it with C-S or C-R. */
	for (;;) {
		SetDot(&pushbp);
		message(NullStr);
		if (failing)
			add_mess("Failing ");
		if (dir == BACKWARD)
			add_mess("reverse-");
		add_mess("I-search: %s", ISbuf);
		DrawMesg(NO);
		add_mess(NullStr);	/* tell me this is disgusting ... */
		c = getch();
		if (c == SExitChar)
			return STOP;
		if (c == AbortChar) {
			/* If we're failing, we backup until we're no longer
			   failing or we've reached the beginning; else, we
			   just about the search and go back to the start. */
			if (failing)
				return BACKUP;
			return TOSTART;
		}
		switch (c) {
		case RUBOUT:
		case BS:
			return DELETE;

		case CTL('\\'):
			c = CTL('S');

		case CTL('S'):
		case CTL('R'):
			/* If this is the first time through and we have a
			   search string left over from last time, use that
			   one now. */
			if (incp == ISbuf) {
				strcpy(ISbuf, getsearch());
				incp = &ISbuf[strlen(ISbuf)];
			}
			ndir = (c == CTL('S')) ? FORWARD : BACKWARD;
			/* If we're failing and we're not changing our
			   direction, don't recur since there's no way
			   the search can work. */
			if (failing && ndir == dir) {
				rbell();
				continue;
			}
			break;

		case '\\':
			if (incp > &ISbuf[(sizeof ISbuf) - 1]) {
				rbell();
				continue;
			}
			*incp++ = '\\';
			add_mess("\\");
			/* Fall into ... */

		case CTL('Q'):
		case CTL('^'):
			add_mess("");
			c = getch() | 0400;
			/* Fall into ... */

		default:
			if (c & 0400)
				c &= CHARMASK;
			else {
#ifdef IBMPC
				if (c == RUBOUT || c == 0xff || (c < ' ' && c != '\t')) {
#else
				if (c > RUBOUT || (c < ' ' && c != '\t')) {
#endif
					Ungetc(c);
					return STOP;
				}
			}
			if (incp > &ISbuf[(sizeof ISbuf) - 1]) {
				rbell();
				continue;
			}
			*incp++ = c;
			*incp = 0;
			break;
		}
		add_mess("%s", orig_incp);
		add_mess(" ...");	/* so we know what's going on */
		DrawMesg(NO);		/* do it now */
		switch (isearch(ndir, doisearch(ndir, c, failing))) {
		case TOSTART:
			return TOSTART;

		case STOP:
			return STOP;

		case BACKUP:
			/* If we're not failing, we just continue to to the
			   for loop; otherwise we keep returning to the 
			   previous levels until we find one that isn't
			   failing OR we reach the beginning. */
			if (failing)
				return BACKUP;
			/* Fall into ... */

		case DELETE:
			incp = orig_incp;
			*incp = 0;
			continue;
		}
	}
}
