/*
 *
 *	fences.c
 *
 * Match up various fenceposts, like (), [], {}, */ /*, #if, #el, #en
 *
 * Original version probably by Dan Lawrence or Dave Conroy for MicroEMACS
 * Extensions for vile by Paul Fox
 * Rewrote to use regular expressions - T.Dickey
 *
 * $Header: /usr/build/vile/vile/RCS/fences.c,v 1.76 1999/12/24 01:08:24 tom Exp $
 *
 */

#include	"estruct.h"
#include	"edef.h"

#if OPT_CFENCE

#define	CPP_UNKNOWN -1
#define	CPP_IF       0
#define	CPP_ELIF     1
#define	CPP_ELSE     2
#define	CPP_ENDIF    3

#define BLK_UNKNOWN -1
#define BLK_BEGIN    4
#define BLK_END      5

#if OPT_TRACE
#define TRACEARG(p) p,
#else
#define TRACEARG(p) /*nothing*/
#endif

#define ok_BLK(n) ((n) >= BLK_BEGIN && (n) <= BLK_END)

#define COMPLEX_FENCE_CH  -4
#define COMMENT_FENCE_CH  -3
#define PAIRED_FENCE_CH   -2
#define UNKNOWN_FENCE_CH  -1

#define S_COL(exp) (C_NUM)(exp->startp[0] - DOT.l->l_text)
#define E_COL(exp) (C_NUM)(exp->endp[0]   - DOT.l->l_text)

#define BlkBegin b_val_rexp(curbp, VAL_FENCE_BEGIN)->reg
#define BlkEnd   b_val_rexp(curbp, VAL_FENCE_END)->reg

#define CurrentChar() \
	(is_at_end_of_line(DOT) ? '\n' : char_at(DOT))
#define InDirection(sdir) \
	((sdir == REVERSE) ? backchar(FALSE, 1) : forwchar(FALSE, 1))

#define direction_of(sdir) (((sdir) == FORWARD) ? "forward" : "backward")

#undef  min
#define min(a,b)	((a) < (b) ? (a) : (b))

#if !OPT_MAJORMODE
#define limit_iterations() /* nothing */
#endif

	/*
	 * ops are inclusive of the endpoint; account for this when saving
	 * pre_op_dot
	 */
#define start_fence_op(sdir, oldpos, oldpre) \
	TRACE(("...start_fence_op\n")); \
	oldpos = DOT; \
	oldpre = pre_op_dot; \
	if (doingopcmd && sdir == REVERSE) { \
		forwchar(TRUE,1); \
		pre_op_dot = DOT; \
		backchar(TRUE,1); \
	}

#define test_fence_op(st, oldpos, oldpre) \
	TRACE(("...test_fence_op, status=%d\n", st)); \
	if (st != TRUE) { \
		DOT = oldpos; \
		pre_op_dot = oldpre; \
	}

static int find_complex(int sdir, int *newkey);
static int complex_fence(int sdir, int key, int group, int level, int *newkey);
#if OPT_MAJORMODE
static int find_one_complex(int sdir, int level, int group, int *newkey);
static void limit_iterations(void);
#endif

#if OPT_MAJORMODE
static time_t final_time;
static long   iterations;
#endif

static int
next_line(int sdir)
{
	if (sdir == REVERSE)
		DOT.l = lback(DOT.l);
	else
		DOT.l = lforw(DOT.l);

	if (is_header_line(DOT,curbp)) {
		return FALSE;
	} else if (interrupted()) {
		kbd_alarm();
		return ABORT;
	}
	return TRUE;
}

#if OPT_TRACE
static char *typeof_complex(int code)
{
	char *s;
	switch (code) {
	case CPP_IF:		s = "CPP_IF";		break;
	case CPP_ELIF:		s = "CPP_ELIF";		break;
	case CPP_ELSE:		s = "CPP_ELSE";		break;
	case CPP_ENDIF:		s = "CPP_ENDIF";	break;
	default:		s = "CPP_UNKNOWN";	break;
	}
	return s;
}
#endif

static int
match_complex(TRACEARG(int group) LINE *lp, struct VAL *vals)
{
	static int modes[] = { CPP_IF, CPP_ELIF, CPP_ELSE, CPP_ENDIF };
	size_t j, k;
	int code = CPP_UNKNOWN;

#define any_rexp(bv,n) (bv[n].vp->r)

	for (j = 0; j < TABLESIZE(modes); j++) {
		switch (modes[j]) {
		case CPP_IF:		k = VAL_FENCE_IF;	break;
		case CPP_ELIF:		k = VAL_FENCE_ELIF;	break;
		case CPP_ELSE:		k = VAL_FENCE_ELSE;	break;
		case CPP_ENDIF:		k = VAL_FENCE_FI;	break;
		default:					continue;
		}
		if (lregexec(any_rexp(vals, k)->reg, lp, 0, llength(lp))) {
			code = modes[j];
			TRACE(("match_complex(%d) %s\n", group, typeof_complex(code)));
			break;
		}
	}

	return code;
}

/*
 * Find the match, if any, for a begin/end comment marker.  If we find a
 * match, the regular expression will overlap the given LINE/offset.
 */
static int
match_simple(void)
{
	C_NUM first = 0;
	C_NUM last = llength(DOT.l);

	TRACE(("match_simple %d:%s\n", line_no(curbp, DOT.l), lp_visible(DOT.l)));
	for (first = 0; first < last; first = S_COL(BlkBegin) + 1) {
		if (!lregexec(BlkBegin, DOT.l, first, last))
			break;
		if ((S_COL(BlkBegin) <= DOT.o)
		 && (E_COL(BlkBegin) >  DOT.o)) {
			TRACE(("...match_simple BLK_BEGIN\n"));
			return BLK_BEGIN;
		}
	}

	for (first = 0; first < last && DOT.o <= last; last = E_COL(BlkEnd) - 1) {
		if (!lregexec(BlkEnd, DOT.l, first, last))
			break;
		if ((S_COL(BlkEnd) <= DOT.o)
		 && (E_COL(BlkEnd) >  DOT.o)) {
			TRACE(("...match_simple BLK_END\n"));
			return BLK_END;
		}
		if (last >= E_COL(BlkEnd) - 1)
			break;
	}

	TRACE(("...match_simple BLK_UNKNOWN\n"));
	return BLK_UNKNOWN;
}

#define TRACE_COMPLEX TRACE(("complex_fence(%d:%d:%d) @%d ", level, group, count, line_no(curbp, DOT.l)));

static int
complex_fence(int sdir, int key, int group, int level, int *newkey)
{
	int count  = 1;
	int that   = CPP_UNKNOWN;
	int result = -1;
	struct VAL *vals;

	TRACE(("ComplexFence(%d:%d) %d: %s %s\n",
		level,
		group,
		line_no(curbp, DOT.l),
		direction_of(sdir),
		typeof_complex(key)));
#if OPT_MAJORMODE
	if (iterations < 0) {
		return ABORT;
	} else if ((++iterations % 200) == 0) { /* roughly once/sec, slow PC */
	    if (time((time_t *)0) > final_time) {
		if (iterations >= 0) {
		    mlforce("[Too many iterations: %ld]", iterations);
		    iterations = -1;
		}
		return ABORT;
	    }
	}
#endif

	while (count > 0) {

	    MARK savedot;
	    int savecount;
	    int status = next_line(sdir);

	    if (status != TRUE)
		return status;

	    savedot = DOT;
	    savecount = count;

	    TRACE(("complex_fence(%d:%d:%d) %4d:%s\n", level, group, count, line_no(curbp, DOT.l), lp_visible(DOT.l)));

	    for_each_modegroup(curbp,result,group,vals) {
		DOT = savedot;
		count = savecount;
		if (((that = match_complex(TRACEARG(result) DOT.l, vals)) != CPP_UNKNOWN)) {
		    int	done = FALSE;

		TRACE(("for_each_modegroup:%d:%d (line %d, count %d)\n", result, group, line_no(curbp, DOT.l), count));
#if OPT_MAJORMODE
		    if (result != group) {
			int find = (sdir == FORWARD) ? CPP_IF : CPP_ENDIF;
			if (that == find) {
			    MARK save;
			    find = (sdir == FORWARD) ? CPP_ENDIF : CPP_IF;
			    do {
				TRACE_COMPLEX
				TRACE(("calling find_one_complex(%s)\n", get_submode_name(curbp, result)));
				if ((status = find_one_complex(sdir, level+1, result, &that)) != TRUE) {
				    TRACE_COMPLEX
				    TRACE(("done calling find_one_complex (%s)\n", status == ABORT ? "abort" : "fail"));
				    that = CPP_UNKNOWN;
				    if (status == ABORT)
					return status;
				    break;
				}
				TRACE_COMPLEX
				TRACE(("done calling find_one_complex(%s) (ok)\n", get_submode_name(curbp, result)));
			    } while (that != find);

			    save = DOT;
			    if (that != CPP_UNKNOWN) {
				TRACE_COMPLEX
				TRACE(("recurring to finish group '%s'\n", get_submode_name(curbp, result)));
				if ((status = complex_fence(sdir, key, group, level+1, newkey)) != FALSE) {
				    TRACE_COMPLEX
				    TRACE(("done recurring (%s)\n", status == TRUE ? "ok" : "abort"));
				    return status;
				}
				DOT = save;
			    }
			}
			/* improperly nesting? - we missed something */
			TRACE(("...complex_match mismatch\n"));
			continue;
		    }
		    *newkey = that;
#endif
		    TRACE(("...before %s, count=%d, done=%d\n",
			   typeof_complex(that),
			   count, done));

		    switch (that) {
		    case CPP_IF:
			if (sdir == FORWARD) {
			    count++;
			} else {
			    done = ((count-- == 1) &&
				    (key != that));
			    if (done)
				count = 0;
			}
			break;

		    case CPP_ELIF:
		    case CPP_ELSE:
			done = ((sdir == FORWARD) && (count == 1));
			if (done)
			    count = 0;
			break;

		    case CPP_ENDIF:
			if (sdir == FORWARD) {
			    done = (--count == 0);
			} else {
			    count++;
			}
		    }

		    TRACE(("...after %s, count=%d, done=%d, level=%d\n",
			   typeof_complex(that),
			   count, done, level));

		    savecount = count;
		    if ((count <= 0) || done) {
			(void) firstnonwhite(FALSE,1);
			goto finish;
		    }
		}
	    }
	}
finish:
	if (count == 0) {
		curwp->w_flag |= WFMOVE;
		if (doingopcmd)
			regionshape = FULLLINE;
		return TRUE;
	}
	return FALSE;
}

/*
 * Look for a complex fence beginning on the current line.  If we find it,
 * see if we can find the next part.
 */
static int
find_complex(int sdir, int *newkey)
{
	int rc = FALSE;
	int key = CPP_UNKNOWN;
	int group = -1;
	MARK	oldpos, oldpre;
	struct VAL *vals;

	/*
	 * Iterate over the complex fence groups
	 */
	TRACE(("find_complex %4d:%s\n", line_no(curbp, DOT.l), lp_visible(DOT.l)));
	limit_iterations();
	for_each_modegroup(curbp,group,0,vals) {
	    if ((key = match_complex(TRACEARG(group) DOT.l, vals)) != CPP_UNKNOWN) {
		start_fence_op(sdir, oldpos, oldpre);
		sdir = (key == CPP_ENDIF)
			? REVERSE
			: FORWARD;
		rc = complex_fence(sdir, key, group, 0, newkey);
		test_fence_op(rc, oldpos, oldpre);
#if OPT_MAJORMODE
		if (rc) {
		    if (iterations < 0)
			rc = FALSE;
		    break;
		}
#endif
	    }
	}
#if OPT_MAJORMODE
	TRACE(("...find_complex %d (iterations %ld)\n", rc, iterations));
#endif
	return rc;
}

/*
 * Look for a complex fence beginning on the current line.  If we find it,
 * see if we can find the next part.
 */
#if OPT_MAJORMODE
static int
find_one_complex(int sdir, int level, int group, int *newkey)
{
	int s = FALSE;
	int key = CPP_UNKNOWN;
	MARK	oldpos, oldpre;
	struct VAL *vals = get_submode_vals(curbp, group);

	/*
	 * Iterate over the complex fence groups
	 */
	TRACE(("find_one_complex %4d:%s\n", line_no(curbp, DOT.l), lp_visible(DOT.l)));
	if ((key = match_complex(TRACEARG(group) DOT.l, vals)) != CPP_UNKNOWN) {
	    start_fence_op(sdir, oldpos, oldpre);
	    if (level == 0)
		    sdir = (key == CPP_ENDIF)
			    ? REVERSE
			    : FORWARD;
	    s = complex_fence(sdir, key, group, level, newkey);
	    test_fence_op(s, oldpos, oldpre);
	}
	return s;
}

/*
 * Provide an absolute limit on the number of iterations in a recursive search
 * for fences.  This is needed when the fences are not properly nested;
 * otherwise the complex fence logic will iterate up to linecount raised to the
 * modegroup power (worse case ;-).
 */
static void
limit_iterations(void)
{
	bsizes(curbp);
	final_time = time((time_t *)0) + b_val(curbp,VAL_FENCE_LIMIT);
	iterations = 0;
}
#endif

int
is_user_fence(int ch, int *sdirp)
{
	char *fences = b_val_ptr(curbp,VAL_FENCES);
	char *chp, och;
	if (!ch)
		return 0;
	chp = strchr(fences, ch);
	if (!chp)
		return 0;
	if ((chp - fences) & 1) {
		/* look for the left fence */
		och = chp[-1];
		if (sdirp)
			*sdirp = REVERSE;
	} else {
		/* look for the right fence */
		och = chp[1];
		if (sdirp)
			*sdirp = FORWARD;
	}
	return och;
}

static int
simple_fence(int sdir, int ch, int ofence)
{
	int count = 1;	/* Assmue that we're sitting at one end of the fence */
	int c;

	/* scan for fence */
	while (InDirection(sdir) && !interrupted()) {
		c = CurrentChar();
		if (c == ch) {
			++count;
		} else if (c == ofence) {
			if (--count <= 0)
				break;
		}
	}

	/* if count is zero, we have a match, move the sucker */
	if (count <= 0) {
		if (!doingopcmd || doingsweep)
			sweephack = TRUE;
		else if (sdir == FORWARD)
			forwchar(TRUE,1);
		curwp->w_flag |= WFMOVE;
		return TRUE;
	}
	return FALSE;
}

static int
comment_fence(int sdir)
{
	/* avoid overlapping match between begin/end patterns */
	if (sdir == FORWARD) {
		SIZE_T off = (SIZE_T)(DOT.o - S_COL(BlkBegin));
		if (BlkEnd->mlen > off)
			forwchar(TRUE, BlkEnd->mlen - off);
	}

	scanboundry(FALSE,DOT,sdir);
	if (scanner((sdir == FORWARD) ? BlkEnd : BlkBegin,
			sdir, FALSE, (int *)0)) {
		if (!doingopcmd || doingsweep)
		{
			sweephack = TRUE;
			if (sdir == FORWARD && (BlkEnd->mlen > 1))
				forwchar(TRUE, BlkEnd->mlen - 1);
		}
		else if (sdir == FORWARD && (BlkEnd->mlen > 1))
		{
			forwchar(TRUE, BlkEnd->mlen - 0);
		}
		curwp->w_flag |= WFMOVE;
		return TRUE;
	}
	return FALSE;
}

static int
getfence(
int ch,		/* fence type to match against */
int sdir)	/* direction to scan if we're not on a fence to begin with */
{
	MARK	oldpos; 		/* original pointer */
	MARK	oldpre;
	int ofence = 0;
	int s, i;
	int key = CPP_UNKNOWN;
	int fch;

	/* save position */
	oldpos = DOT;

	TRACE(("getfence, starting at %d.%d\n", line_no(curbp, DOT.l), DOT.o));

	if (ch == UNKNOWN_FENCE_CH) {
		if ((i = firstchar(DOT.l)) < 0)	/* offset of first nonblank */
			return FALSE;		/* line is entirely blank */

		if (DOT.o <= i
		 && ((s = find_complex(sdir, &ch)) != FALSE)) {
			return s;
		} else if ((key = match_simple()) != BLK_UNKNOWN) {
			ch = COMMENT_FENCE_CH;
			sdir = (key == BLK_BEGIN) ? FORWARD : REVERSE;
		} else if (sdir == FORWARD) {
			if (oldpos.o < llength(oldpos.l)) {
				do {
					ch = char_at(oldpos);
				} while(!is_user_fence(ch, (int *)0) &&
					++oldpos.o < llength(oldpos.l));
			}
			if (is_at_end_of_line(oldpos)) {
				return FALSE;
			}
		} else {
			if (oldpos.o >= 0) {
				do {
					ch = char_at(oldpos);
				} while(!is_user_fence(ch, (int *)0) &&
					--oldpos.o >= 0);
			}

			if (oldpos.o < 0) {
				return FALSE;
			}
		}

		/* we've at least found a fence -- move us that far */
		DOT.o = oldpos.o;
	}

	fch = ch;

	if (ch >= 0) {
		ofence = is_user_fence(ch, &sdir);
		if (ofence)
			fch = PAIRED_FENCE_CH;
	}

	if (fch >= 0) {
		if ((key = match_simple()) == BLK_UNKNOWN)
			return(FALSE);
	}

	start_fence_op(sdir, oldpos, oldpre);

	if (ok_BLK(key)) {
		s = comment_fence(sdir);
	} else if (ch == '/') {
		s = comment_fence(sdir);
	} else {
		s = simple_fence(sdir, ch, ofence);
	}

	test_fence_op(s, oldpos, oldpre);
	TRACE(("...getfence, end at %d.%d (status=%d)\n", line_no(curbp, DOT.l), DOT.o, s));
	return(s);
}

/*	the cursor is moved to a matching fence */
int
matchfence(int f, int n)
{
	int s = getfence(UNKNOWN_FENCE_CH, (!f || n > 0) ? FORWARD:REVERSE);
	if (s == FALSE)
		kbd_alarm();
	return s;
}

int
matchfenceback(int f, int n)
{
	int s = getfence(UNKNOWN_FENCE_CH, (!f || n > 0) ? REVERSE:FORWARD);
	if (s == FALSE)
		kbd_alarm();
	return s;
}

/* get the indent of the line containing the matching brace/paren. */
int
fmatchindent(int c)
{
	int ind;

	MK = DOT;

	if (getfence(c, REVERSE) == FALSE) {
		(void)gomark(FALSE,1);
		return previndent((int *)0);
	}

	ind = indentlen(DOT.l);

	(void)gomark(FALSE,1);

	return ind;
}



/*	Close fences are matched against their partners, and if
	on screen the cursor briefly lights there		*/
void
fmatch(int rch)
{
	MARK	oldpos; 	/* original position */
	register LINE *toplp;	/* top line in current window */
	register int count;	/* current fence level count */
	register char c;	/* current character in scan */
	int dir, lch;
	int backcharfailed = FALSE;

	/* get the matching left-fence char, if it exists */
	lch = is_user_fence(rch, &dir);
	if (lch == 0 || dir != REVERSE)
		return;

	/* first get the display update out there */
	(void)update(FALSE);

	/* save the original cursor position */
	oldpos = DOT;

	/* find the top line and set up for scan */
	toplp = lback(curwp->w_line.l);
	count = 1;
	backchar(TRUE, 2);

	/* scan back until we find it, or reach past the top of the window */
	while (count > 0 && DOT.l != toplp) {
		c = CurrentChar();
		if (c == rch)
			++count;
		if (c == lch)
			--count;
		if (backchar(FALSE, 1) != TRUE) {
			backcharfailed = TRUE;
			break;
		}
	}

	/* if count is zero, we have a match, display the sucker */
	if (count == 0) {
		if (!backcharfailed)
			forwchar(FALSE, 1);
		if (update(SORTOFTRUE) == TRUE) {
#ifdef DISP_NTWIN
			/*
			 * For all flavors of the editor except winvile,
			 * update() has made the cursor visible.  Winvile,
			 * otoh, uses its own custom logic to enable and
			 * disable the cursor.	At the moment, the cursor
			 * is invisible.  Turn it on.
			 */
			(void) winvile_cursor_state(TRUE, FALSE);
#endif
			/* 
			 * The idea is to leave the cursor there for about a
			 * quarter of a second.
			 */

			catnap(300, FALSE);
		}
	}

	/* restore the current position */
	DOT = oldpos;
}

#endif /* OPT_CFENCE */
