/*
 * This file is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.
 * Users may copy, modify or distribute this file at will.
 * 
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 * 
 * This file is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 * 
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS FILE
 * OR ANY PART THEREOF.
 * 
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even
 * if Sun has been advised of the possibility of such damages.
 * 
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 *
 * Modifications to the original Sun Microsystems, Inc. source code
 * made by the Grasshopper Group are in the Public Domain.
 *
 * Extensions to this file by Eric Messick of the Grasshopper Group.
 *
 * Grasshopper Group
 * 212 Clayton St
 * San Francisco, CA 94117
 *
 */

#ifndef lint
static	char sccsid[] =
	"@(#)tcap_parse.c 9.5 88/01/19 Copyright 1985 Sun Micro";
static	char RCSid[] =
	"@(#)$Header: tcap_parse.c,v 2.3 88/10/04 06:00:03 gnu Release $";
#endif


/*
 * Copyright (c) 1985 by Sun Microsystems, Inc.
 */

/*-
	tcap_parse.c:  Parse termcap output based on termcap entry.

	tcap_parse.c, Mon Mar 24 11:25:44 1986

		David Rosenthal,
		Sun Microsystems
 */
    /* XXX - remember longest outstanding partial match? */
    /* XXX - overlapping partial matches? */

#include	<stdio.h>
#include	<ctype.h>
#include	<sys/types.h>
#ifdef REF
#include	<ref/config.h>
#endif
#include	"termcap.h"

extern char *malloc();
#ifndef bcopy
extern void bcopy();
#endif

static	int TerminalIsBraindamaged = 0;
/*  Import these from tcap_ops.c */
extern struct tcap T[];
extern int Ts;
extern struct tcap *CheckCR;
extern struct tcap *CheckNL;
extern struct tcap *CheckTAB;
extern struct tcap *CheckBS;
extern int PageFull;
extern int PageMode;

static char *unpad();
static int tc_init_stacks();

/*
 * Initialize the display system from the TERMCAP entry.
 * We parse the entry and build the tcap structures
 * describing the operations supported by this type of
 * terminal.  These descriptions are then used by tc_display()
 * in interpreting the data stream generated by the
 * application
 */
int
tc_initialize(term)
    char *term;
{
#ifdef SUNTGETENT
    static void tc_fix_tcap_ent();
#endif
    static char tcapbuf[1024], tcaparea[1024];
    char       *areap = tcaparea;
    extern int tgetent(), tgetnum(), tgetflag();
    extern char *tgetstr();
    register struct tcap *tp;

    if (tgetent(tcapbuf, term) != 1) {		/* unknown terminal type? */
	fprintf(stderr, "tgetent failed\n");
	return (1);
	}
#ifdef SUNTGETENT
    tc_fix_tcap_ent(tcapbuf);
#endif
    set_environment_var("TERMCAP", tcapbuf);
    for (tp = T; tp < T+Ts; tp++) {
	switch (tp->t_type) {
	case string:
	    tp->t_text = unpad(tgetstr(tp->t_key, &areap));
	    if (tp->t_text == NULL)
		tp->t_text = tp->t_deftext;
	    if (tp->t_text) {
		    tp->t_size = strlen(tp->t_text);
		    if (isprint(tp->t_text[0]))
			    TerminalIsBraindamaged = 1;
	    } else
		    tp->t_size = 0;
	    break;
	case num:
	    tp->t_x = tgetnum(tp->t_key);
	    break;
	case bool:
	    tp->t_x = tgetflag(tp->t_key);
	    break;
	}
	/* invoke any initialize routine */
	if (tp->t_in && (*tp->t_in)(tp)) {
	    fprintf(stderr, "termcap init failed for %s\n", tp->t_key);
	    return (1);
	    }
    }
    tc_init_ops();
    return tc_init_stacks();
}

#ifdef HAVE_TERMCAP
static char *
unpad(s)
    register char *s;
{

    if (s) {
	register pad = 0;

	while (isdigit(*s))
	    pad++, s++;
	if (pad && *s == '*')
	    s++;
    }
    return (s);
}

#else	/* !HAVE_TERMCAP, ie next code is for TERMINFO */

/*
 * Remove the substring of the form "$<x^>" where x = number, and ^ = characters
 * in the set [* /]. This is the terminfo way of specifying delays or padding.
 */
static char *
unpad(s)
    register char *s;
{

    if (s) {
	register char *spt = s;
	register char *spt1, *spt2;
	char *strchr();

	while (spt1 = strchr(spt, '$')) {
		if (*(spt1 + 1) == '<') {	/* found the '$<' pair */
			if (spt2 = strchr(spt1, '>')) {
				strcpy(spt1, ++spt2);	/* found end '>' */
				spt = spt1;		/* copy tail of */
				continue;		/* string over  */
							/* '$<..', look */
							/* for more */
			} else
				break;		/* no match for '$<' so quit */
		} else {
			spt = spt1 + 1;		/* found '$' but no '<', */
			continue;		/* look for more */
		}
			
	}
    }
    return (s);
}
#endif	/* !HAVE_TERMCAP */


#ifdef SUNTGETENT
#define TCAPBUFSIZE 1024
#define SPECIALSIZE 2

static struct {
    char	*last;
    char	 str[3];
}   tcapSpecials[SPECIALSIZE] = {
    NULL,	"co",
    NULL,	"li"
};

/*
 * Stomp on the first "co" and "li" entries in the termcap entry
 * to avoid braindamage in the Sun version of the termcap library.
 * Apparently the Sun version of tgetent() looks at the terminal
 * state and uses this to prepend extra line+column spec's that
 * reflect the terminal's current state.  This is not what we want,
 * so our only recourse is to undo the this braindamage here.
 */
static void
tc_fix_tcap_ent(buf)
    char *buf;
{
    char *bp = buf;
#ifndef SYSVREF
    char *index();
#else
#define index(s, c)	(char *)strchr(s, c)
#endif
    int   i;

    /* for each item in buf ... */
    for (bp = index(bp, ':'); bp && *(bp+1); bp = index(bp, ':')) {
	++bp;
	/* for each special tcap code ... */
	for (i = 0; i < SPECIALSIZE; i++) {
	    if (strncmp(tcapSpecials[i].str, bp, 2) == 0) {
		if (tcapSpecials[i].last)
		    strncpy(tcapSpecials[i].last, "xx", 2);
		tcapSpecials[i].last = bp;
		break;
	    }
	}
    }
}
#endif /* SUNTGETENT */

/*
 * Matching is performed with a push-down automata implemented
 * with dual stacks.  An initial stack is loaded with all the
 * potential matches from the termcap structure.  Matching then
 * takes place by popping each potential match off the ``current
 * stack'' and, if a successful match for the current character
 * occurs, pushing the match on the ``other stack''.  When the
 * ``current stack'' is empty (all elements have been examined),
 * the stacks are swapped and the process restarted.  This continues
 * until a completed match or the stack of potential matches has
 * been exhausted.
 */
static	struct	tcap **curstack, **cursp;	/* ``potential match'' stack */
static	struct tcap **otherstack, **othersp;	/* ``match this pass'' stack */
static	struct tcap **resetstack;		/* prototype curstack */
static	int stacksize;				/* # of potential matches */
static	int MatchInProgress;			/* for fast check */

#define	PushMatch(tp)	(*--othersp = tp)
#define	PopMatch()	(*cursp++)
#define	PopMatched()	(*othersp++)
#define	SwapStacks() { \
	struct tcap **t; \
	t = curstack, curstack = otherstack, otherstack = t; \
	cursp = othersp, othersp = otherstack + stacksize; \
	MatchInProgress = 1; \
}
#define	ResetMatchStack() { \
	bcopy((char *)resetstack, (char *)curstack, \
		stacksize*sizeof (struct tcap *)); \
	cursp = curstack; \
	MatchInProgress = 0; \
}
#define	FlushStack(sp, stack) { \
	while (sp < stack+stacksize) { \
	    tp = *sp++; \
	    tp->t_index = 0; \
	    tp->t_param = 0 ; \
	    tp->t_matched = 0; \
	    tp->t_2nd = 0; \
	} \
}
#define	FlushMatchStack()	FlushStack(cursp, curstack)
#define	FlushMatchedStack()	FlushStack(othersp, otherstack);
#define	MatchStackEmpty()	(cursp >= curstack+stacksize)
#define	MatchedStackEmpty()	(othersp >= otherstack+stacksize)

/*
 * Reset the pattern matching stack and load
 * it with all the potential matching entries.
 */
static int
tc_init_stacks()
{
	register struct tcap *tp;

	for (tp = T; tp < T+Ts; tp++)
	    if (tp->t_text != NULL)
		stacksize++;
	curstack = (struct tcap **)malloc((unsigned)
			(3*sizeof(struct tcap *) * stacksize));
	if (!curstack)
	    return 1;
	otherstack = curstack+stacksize;
	resetstack = otherstack+stacksize;
	othersp = resetstack+stacksize;
	for (tp = T; tp < T+Ts; tp++)
	    if (tp->t_text != NULL)
		    PushMatch(tp);
	othersp = otherstack+stacksize;
	ResetMatchStack();
	return 0;
}

extern struct tcap interruptedOp;

/*
 * Interpret data from the application.  We match data against
 * the ``escape sequences'' expected for this termcap description
 * and, if successful, invoke the routines used to emulate the
 * capabilities on the window.
 */
tc_display(cp, n)
    u_char *cp;
    register int n;
{
    register int c, j;
    register struct tcap *tp;
    static char dbuf[256], *dp = dbuf;
    int restart, lim;

    /*
     * If we're blocked with a page full, indicate
     * nothing was sent to the screen.  We should
     * never be called when already blocked, but
     * just in case, turn scrolling on again so we
     * don't lost any data.
     */
    if (PageFull) {
	if (interruptedOp.t_key == 0)
	    return (n);
	scrollreset(0);			/* XXX */
    }	
    /*
     * If we have previous output, process it first.
     * Check on completion to see if we filled the screen.
     */
    if (interruptedOp.t_key) {
	(*interruptedOp.t_op)(&interruptedOp);
	if (PageFull)
	    return (n);
	interruptedOp.t_key = 0;
    }
    /*
     * For each input character, look for potential
     * matches in the tcap structure.  For each possible
     * match, construct the resultant output buffer.
     * On first match process the operation (e.g. invoke
     * internal routine) and flush extraneous matches.
     * If input doesn't match any capability, send it to
     * the window.
     */
    while (n > 0 && !PageFull) {
	/*
	 * If we're not in the middle of a match, then
	 * try and bypass the pattern matcher by performing
	 * special checks on the most common input.
	 */
	if (!MatchInProgress) {
	    while (n > 0 && !PageFull) {
		/*
		 * If terminal has only non-printing escape sequences,
		 * then process printable characters w/o matching against
		 * the termcap strings.
		 */
		if (!TerminalIsBraindamaged) {
		    for (dp = (char *)cp; n > 0 && isprint((int)*cp); n--)
			cp++;
		    if ((char *)cp > dp) {
			tp = T+Ts;
			tp->t_text = dp;	/* use original storage */
			tp->t_size = (char *)cp - dp;
			(*tp->t_op)(tp);
			if (PageFull)
			    return (n);
			continue;
		    }
		}
		/*
		 * Make quick checks for standard NL, CR, BS, and TAB
		 * characters.  This speeds up scrolling for most
		 * terminal types.
		 */
		c = *cp;
		if (CheckNL && c == '\n')
		    tp = CheckNL;
		else if (CheckCR && c == '\r')
		    tp = CheckCR;
		else if (CheckTAB && c == '\t')
		    tp = CheckTAB;
		else if (CheckBS && c == '\b')
		    tp = CheckBS;
		else
		    break;
		cp++, n--;
		(*tp->t_op)(tp);
		if (PageFull)
		    return (n);
	    }
	    dp = dbuf;
	    if (n == 0)
		break;
	}
	c = *dp++ = *cp++, n--;
	while (!MatchStackEmpty()) {
	    tp = PopMatch();
again:
	    j = tp->t_index;
	    restart = 0;
	    /*
	     * Check match against numeric %[d23] specification.
	     */
	    if (tp->t_text[j] == '%') {
		switch (tp->t_text[j+1]) {
		case 'd':			/* series of decimal digits */
		    lim = 127;
		    goto digit;
		case '2':			/* two decimal digits */
		    lim = 2;
		    goto digit;
		case '3':			/* three decimal digits */
		    lim = 3;
		    /* fall thru.. */
	    digit:
		    if (isdigit(c) && tp->t_matched < lim) {
			tp->t_matched++;
			tp->t_param = tp->t_param*10 + (c-'0');
			goto plainmatch;
		    } else {
			if (tp->t_matched == 0)
				tp->t_param = 1 ;
			tp->t_matched = 0;
			restart = !isdigit(c);
			goto gotvalue;
		    }
		    /*NOTREACHED*/
		    break;
		case '.':			/* binary character */
		    tp->t_param = c;
	    gotvalue:
		    switch (tp->t_2nd + tp->t_pc_r) {
		    case 0:
		    case 2:
			if ((tp->t_y = tp->t_param) >= tp->t_yilim)
			    tp->t_y -= tp->t_yi;
			break;
		    case 1:
			if ((tp->t_x = tp->t_param) >= tp->t_xilim)
			    tp->t_x -= tp->t_xi;
			break;
		    }
		    tp->t_2nd = !tp->t_2nd;
		    tp->t_index += 2;
		    tp->t_param = 0 ;
		    goto plainmatch;
		case '%':
		    if ((c & 0177) == '%') {
			tp->t_index += 2;
			goto plainmatch;
		    } else
			goto nomatch;
		default:
		    abort();	/* XXX */
		    /* NOTREACHED */
		}
	    } else if ((c & 0177) == (tp->t_text[j] & 0177)) {
		tp->t_index++;
    plainmatch:					/* plain match */
		if (tp->t_index >= tp->t_size) {/* match completed */
		    if (tp->t_op)
			(*tp->t_op)(tp);
		    dp = dbuf;
		    tp->t_index = 0;
		    tp->t_matched = 0;
		    tp->t_param = 0 ;
		    tp->t_2nd = 0;
		    goto done;
		}
		/*
		 * The end of a %d match is the only case where a
		 * character must be pushed-back and re-parsed.
		 */
		if (restart)
		    goto again;
		PushMatch(tp);			/* push partial match */
	    } else {
    nomatch:					/* failed match */
		tp->t_index = 0;
		tp->t_param = 0 ;
		tp->t_matched = 0;
		tp->t_2nd = 0;
	    }
	}
	if (!MatchedStackEmpty()) {
	    SwapStacks();
	    continue;
	}
done:
	/*
	 * Come here either because no partial matches were
	 * found in the table, or because a match completed.
	 * In the first case we send the input data off
	 * immediately.  In the second case we reset the
	 * state machines and go on to the next character.  
	 */
	if (dp - dbuf) {			/* flush output */
	    tp = T+Ts;
	    tp->t_text = dbuf;
	    tp->t_size = dp - dbuf;
	    (*tp->t_op)(tp);
	    dp = dbuf;
	}
	FlushMatchedStack();			/* reset partial matches */
	FlushMatchStack();			/* reset unchecked partials */
	ResetMatchStack();			/* re-init match stack */
    }
    return (n);				/* return number of chars processed */
}