/* vi:sw=4:ts=4:
   The following software is (C) 1984 Peter da Silva,
   the Mad Australian, in the public domain. It may
   be re-distributed for any purpose with the inclusion
   of this notice. */

/* modified by Bram Moolenaar for use with VIM - Vi Improved */

/* TERMLIB: Terminal independant database. */

#include "vim.h"
#include "proto.h"
#include "proto/termlib.pro"

#if !defined(__CYGWIN__) && !defined(AMIGA) && !defined(MSDOS)	/* DOSGEN */
# include <sgtty.h>
#endif

static int	getent __PARMS((char *, char *, FILE *, int));
static int	nextent __PARMS((char *, FILE *, int));
static int	_match __PARMS((char *, char *));
static char	*_addfmt __PARMS((char *, char *, int));
static char	*_find __PARMS((char *, char *));

/*
 * Global variables for termlib
 */

char	*tent;                /* Pointer to terminal entry, set by tgetent */
#ifdef MSDOS		/* DOSGEN */
extern char		PC, *UP, *BC;
#else
char	PC = 0;               /* Pad character, default NULL */
char	*UP = 0, *BC = 0;     /* Pointers to UP and BC strings from database */
#endif
short	ospeed;               /* Baud rate (1-16, 1=300, 16=19200), as in stty */

/*
 * Module: tgetent
 *
 * Purpose: Get termcap entry for <term> into buffer at <tbuf>.
 *
 * Calling conventions: char tbuf[TBUFSZ+], term=canonical name for
 *			terminal.
 *
 * Returned values: 1 = success, -1 = can't open file,
 *		    0 = can't find terminal.
 *
 * Notes
 *		Should probably supply static buffer.
 *
 *		Uses environment variables "TERM" and
 *	"TERMCAP". If TERM = term (that is, if the argument
 *	matches the environment) then it looks at TERMCAP.
 *		If TERMCAP begins with a slash, then it assumes
 *	this is the file to search rather than /etc/termcap.
 *		If TERMCAP does not begin with a slash, and it
 *	matches TERM, then this is used as the entry.
 *
 *		This could be simplified considerably for non-UNIX
 *	systems.
 */

#ifdef AMIGA
# define TERMCAPFILE "s:termcap"
#else
# define TERMCAPFILE "/etc/termcap"
#endif

tgetent(tbuf, term)
	char	*tbuf;               /* Buffer to hold termcap entry, TBUFSZ bytes max */
	char	*term;               /* Name of terminal */
{
	char    tcbuf[32];           /* Temp buffer to handle */
	char	*tcptr = tcbuf;      /* extended entries */
	char    *tcap = TERMCAPFILE; /* Default termcap file */
	char    *tmp;
	FILE    *termcap;
	int		retval = 0;
	int		len;

	if ((tmp = (char *)vimgetenv("TERMCAP")) != NULL)
	{
#ifdef MSDOS		/* DOSGEN */
		if (*tmp == '/' || *tmp == '\\' || tmp[1] == ':')
#else
		if (*tmp == '/')            /* TERMCAP = name of termcap file */
#endif
			tcap = tmp ;
		else                    	/* TERMCAP = termcap entry itself */
		{
			int tlen = strlen(term);

			while (*tmp && *tmp != ':') /* Check if TERM matches */
			{
				while (*tmp == '|')
					tmp++;
				if (_match(tmp, term) == tlen)
				{
					strcpy(tbuf, tmp);
					tent = tbuf;
					return 1;
				} 
				else
					tmp = _find(tmp, ":|");
			}
		}
	}
	if ((termcap = fopen(tcap, "r")) == NULL)
	{
		strcpy(tbuf, tcap);
		return -1;
	}

	len = 0;
	while (getent(tbuf + len, term, termcap, TBUFSZ - len))
	{
		if ((term = tgetstr("tc", &tcptr)) != NULL)     /* extended entry */
		{
			rewind(termcap);
			len = strlen(tbuf);
		}
		else
		{
			retval = 1; 
			tent = tbuf;
			break;
		}
	}
	fclose(termcap);
	return retval;
}

	static int
getent(tbuf, term, termcap, buflen)
	char	*tbuf, *term;
	FILE	*termcap;
	int		buflen;
{
	char    *tptr;
	int		tlen = strlen(term);

	while (nextent(tbuf, termcap, buflen))   /* For each possible entry */
	{
		tptr = tbuf;
		while (*tptr && *tptr != ':')    /* : terminates name field */
		{
			while (*tptr == '|')             /* | seperates names */
				tptr++;
			if (_match(tptr, term) == tlen)             /* FOUND! */
			{
				tent = tbuf;
				return 1;
			} 
			else                           /* Look for next name */
				tptr = _find(tptr, ":|");
		}
	}
	return 0;
}

	static int
nextent(tbuf, termcap, buflen)         /* Read 1 entry from TERMCAP file */
	char    *tbuf;
	FILE    *termcap;
	int		buflen;
{
	char *lbuf = tbuf;           /* lbuf=line buffer */
	                             /* read lines straight into buffer */

	while (lbuf < tbuf+buflen &&                        /* There's room and */
	      fgets(lbuf, (int)(tbuf+buflen-lbuf), termcap))        /* another line */
	{
		int llen = strlen(lbuf);

#ifdef MSDOS		/* DOSGEN */
		if (lbuf[llen-2] == '\r' && lbuf[llen-1] == '\n')
		{
			lbuf[llen-2] = '\n';
			lbuf[llen-1] = '\0';
			llen--;
		}
#endif

		if (*lbuf == '#')                               /* eat comments */
			continue;
		if (lbuf[-1] == ':' &&                        /* and whitespace */
			lbuf[0] == '\t' &&
			lbuf[1] == ':')
		{
			strcpy(lbuf, lbuf+2);
			llen -= 2;
		}
		if (lbuf[llen-2] == '\\')                  /* and continuations */
			lbuf += llen-2;
		else
		{
			lbuf[llen-1]=0;           /* no continuation, return */
			return 1;
		}
	}

	return 0;                                    /* ran into end of file */
}

/*
 * Module: tgetflag
 *
 * Purpose: returns flag true or false as to the existence of a given
 *	    entry. used with 'bs', 'am', etc...
 *
 * Calling conventions: id is the 2 character capability id.
 *
 * Returned values: 1 for success, 0 for failure.
 */

tgetflag(id)
	char *id;
{
	char	buf[256], *ptr = buf;

	return tgetstr(id, &ptr) ? 1 : 0;
}

/*
 * Module: tgetnum
 *
 * Purpose: get numeric value such as 'li' or 'co' from termcap.
 *
 * Calling conventions: id = 2 character id.
 *
 * Returned values: -1 for failure, else numerical value.
 */

tgetnum(id)
char *id;
{
	char *ptr, buf[256];
	ptr = buf;

	if (tgetstr(id, &ptr))
		return atoi(buf);
	else
		return 0;
}

/*
 * Module: tgetstr
 *
 * Purpose: get terminal capability string from database.
 *
 * Calling conventions: id is the two character capability id.
 *			(*buf) points into a hold buffer for the
 *			id. the capability is copied into the buffer
 *			and (*buf) is advanced to point to the next
 *			free byte in the buffer.
 *
 * Returned values: 0 = no such entry, otherwise returns original
 *		    (*buf) (now a pointer to the string).
 *
 * Notes
 *		It also decodes certain escape sequences in the buffer.
 *	they should be obvious from the code:
 *		\E = escape.
 *		\n, \r, \t, \f, \b match the 'c' escapes.
 *		^x matches control-x (^@...^_).
 *		\nnn matches nnn octal.
 *		\x, where x is anything else, matches x. I differ
 *	from the standard library here, in that I allow ^: to match
 *	:.
 *
 */

char *
tgetstr(id, buf)
char    *id, **buf;
{
	int    len = strlen(id);
	char *tmp=tent;
	char *hold;
	int		i;

	do {
		tmp = _find(tmp, ":");                     /* For each field */
		while (*tmp == ':')                        /* skip empty fields */
			tmp++;
		if (!*tmp)
			break;

		if (_match(id, tmp) == len) {
			tmp += len;                   /* find '=' '@' or '#' */
			if (*tmp == '@')                  /* :xx@: entry for tc */
				return 0;                   /* deleted entry */
			hold= *buf;
			while (*++tmp && *tmp != ':') {/* not at end of field */
				switch(*tmp) {
				case '\\':            /* Expand escapes here */
					switch(*++tmp) {
					case 0:        /* ignore backslashes */
						tmp--;    /* at end of entry */
						break;   /* shouldn't happen */
					case 'e':
					case 'E':                     /* ESC */
						*(*buf)++ = '\033'; 
						break;
					case 'n':                      /* \n */
						*(*buf)++ = '\n'; 
						break;
					case 'r':                      /* \r */
						*(*buf)++ = '\r'; 
						break;
					case 't':                      /* \t */
						*(*buf)++ = '\t'; 
						break;
					case 'b':                      /* \b */
						*(*buf)++ = '\b'; 
						break;
					case 'f':                      /* \f */
						*(*buf)++ = '\f'; 
						break;
					case '0':                    /* \nnn */
					case '1': 
					case '2': 
					case '3': 
					case '4':
					case '5': 
					case '6': 
					case '7': 
					case '8': 
					case '9':
						**buf = 0;
							/* get up to three digits */
						for (i = 0; i < 3 && isdigit(*tmp); ++i)
							**buf = **buf * 8 + *tmp++ - '0';
						(*buf)++;
						tmp--;
						break;
					default:      /* \x, for all other x */
						*(*buf)++= *tmp;
					}
					break;
				case '^':              /* control characters */
					*(*buf)++ = *++tmp - '@'; 
					break;
				default: 
					*(*buf)++ = *tmp;
				}
			}
			*(*buf)++ = 0;
			return hold;
		}
	} while (*tmp);

	return 0;
}

/*
 * Module: tgoto
 *
 * Purpose: decode cm cursor motion string.
 *
 * Calling conventions: cm is cursor motion string.
 *			line, col, are the desired destination.
 *
 * Returned values: a string pointing to the decoded string, or
 *		    "OOPS" if it cannot be decoded.
 *
 * Notes
 *		The accepted escapes are:
 *			%d	 as in printf, 0 origin.
 *			%2, %3	 like %02d, %03d in printf.
 *			%.	 like %c
 *			%+x	 adds <x> to value, then %.
 *			%>xy	 if value>x, adds y. No output.
 *			%i	 increments line& col, no output.
 *			%r	 reverses order of line&col. No output.
 *			%%	 prints as a single %.
 *			%n	 exclusive or row & col with 0140.
 *			%B	 BCD, no output.
 *			%D	 reverse coding (x-2*(x%16)), no output.
 */

char *
tgoto(cm, col, line)
char	*cm;                                      /* cm string, from termcap */
int	col,                                           /* column, x position */
	line;                                            /* line, y position */
{
	char	gx, gy,                                           /*    x, y */
		*ptr,                                     /* pointer in 'cm' */
		reverse = 0,                                 /* reverse flag */
		*bufp,                         /* pointer in returned string */
		addup = 0,                                     /* add upline */
		addbak = 0,                                    /* add backup */
		c;
	static char buffer[32];

	if (!cm)
		return "OOPS";                       /* Kludge, but standard */

	bufp = buffer;
	ptr = cm;

	while (*ptr) {
		if ((c = *ptr++) != '%') {                     /* normal char */
			*bufp++ = c;
		} else {                                         /* % escape */
			switch(c = *ptr++) {
			case 'd':                                 /* decimal */
				bufp = _addfmt(bufp, "%d", line);
				line = col;
				break;
			case '2':                         /* 2 digit decimal */
				bufp = _addfmt(bufp, "%02d", line);
				line = col;
				break;
			case '3':                         /* 3 digit decimal */
				bufp = _addfmt(bufp, "%03d", line);
				line = col;
				break;
			case '>':                      /* %>xy: if >x, add y */
				gx = *ptr++;
				gy = *ptr++;
				if (col>gx) col += gy;
				if (line>gx) line += gy;
				break;
			case '+':                              /* %+c: add c */
				line += *ptr++;
			case '.':                               /* print x/y */
				if (line == '\t' ||                /* these are */
				   line == '\n' ||             /* chars that */
				   line == '\004' ||             /* UNIX hates */
				   line == '\0') {
					line++;         /* so go to next pos */
					if (reverse == (line == col))
						addup=1;      /* and mark UP */
					else
						addbak=1;           /* or BC */
				}
				*bufp++=line;
				line = col;
				break;
			case 'r':                              /* r: reverse */
				gx = line; 
				line = col; 
				col = gx;
				reverse = 1;
				break;
			case 'i':             /* increment (1-origin screen) */
				col++;
				line++;
				break;
			case '%':                          /* %%=% literally */
				*bufp++='%';
				break;
			case 'n':                       /* magic DM2500 code */
				line ^= 0140;
				col ^= 0140;
				break;
			case 'B':                            /* bcd encoding */
				line = ((line/10)<<4)+line%10;
				col = ((col/10)<<4)+col%10;
				break;
			case 'D':                   /* magic Delta Data code */
				line = line-2*(line&15);
				col = col-2*(col&15);
				break;
			default:                           /* Unknown escape */
				return "OOPS";
			}
		}
	}

	if (addup)                                              /* add upline */
		if (UP) {
			ptr=UP;
			while (isdigit(*ptr) || *ptr == '.')
				ptr++;
			if (*ptr == '*')
				ptr++;
			while (*ptr)
				*bufp++ = *ptr++;
		}

	if (addbak)                                          /* add backspace */
		if (BC) {
			ptr=BC;
			while (isdigit(*ptr) || *ptr == '.')
				ptr++;
			if (*ptr == '*')
				ptr++;
			while (*ptr)
				*bufp++ = *ptr++;
		} 
		else
			*bufp++='\b';

	*bufp = 0;

	return(buffer);
}

/*
 * Module: tinit
 *
 * Purpose: simplified terminal initialisation.
 *
 * Calling conventions: name is name of terminal.
 *
 * Returned values: none.
 *
 * Notes
 *		tinit calls tgetent, then sets up the global
 *	variables PC, UP, BC, ospeed appropriately.
 *
 */

#if 0		/* already included in term.c */

char tbuf[TBUFSZ];                                /* Buffer for termcap entry */
char junkbuf[TBUFSZ];                                  /* Big buffer for junk */
char *junkptr;

tinit(name)
char *name;
{
#ifndef AMIGA
	struct sgttyb sgbuf;
#endif
	char *ps;

	junkptr = junkbuf;

	tgetent(tbuf, name);

	ps = tgetstr("pc", &junkptr);
	if (ps) PC = *ps;
	UP = tgetstr("up", &junkptr);
	BC = tgetstr("bc", &junkptr);

#ifdef AMIGA
	ospeed=0;
#else
	gtty(1, &sgbuf);
	ospeed=sgbuf.sg_ospeed;
#endif
	return 0;
}
#endif

/*
 * Module: tputs
 *
 * Purpose: decode padding information
 *
 * Calling conventions: cp = string to be padded, affcnt = # of items
 *			affected (lines, characters, whatever),
 *			outc = routine to output 1 character.
 *
 * Returned values: none
 *
 * Notes
 *		cp has padding information ahead of it, in the form
 *	nnnTEXT or nnn*TEXT. nnn is the number of milliseconds to delay,
 *	and may be a decimal (nnn.mmm). If the asterisk is given, then
 *	the delay is multiplied by afcnt. The delay is produced by outputting
 *	a number of nulls (or other padding char) after printing the
 *	TEXT.
 *
 */

long _bauds[16]={
	0,	50,	75,	110,
	134,	150,	200,	300,
	600,	1200,	1800,	2400,
	4800,	9600,	19200,	19200 };

tputs(cp, affcnt, outc)
char *cp;                                                 /* string to print */
int affcnt;                                      /* Number of lines affected */
void (*outc) __ARGS((unsigned int));                              /* routine to output 1 character */
{
#ifndef MSDOS		/* DOSGEN */
	long	frac,                    /* 10^(#digits after decimal point) */
		counter,                                           /* digits */
		atol();

	if (isdigit(*cp)) {
		counter = 0;
		frac = 1000;
		while (isdigit(*cp))
			counter = counter * 10L + (long)(*cp++ - '0');
		if (*cp == '.')
			while (isdigit(*++cp)) {
				counter = counter * 10L + (long)(*cp++ - '0');
				frac = frac * 10;
			}
		if (*cp!='*') {                 /* multiply by affected lines */
			if (affcnt>1) affcnt = 1;
		} 
		else
			cp++;

        /* Calculate number of characters for padding counter/frac ms delay */
		if (ospeed)
			counter = (counter * _bauds[ospeed] * (long)affcnt) / frac;

		while (*cp)                                  /* output string */
			(*outc)(*cp++);
		if (ospeed)
			while (counter--)            /* followed by pad characters */
				(*outc)(PC);
	} 
	else
		while (*cp)
			(*outc)(*cp++);
#else
	while (isdigit(*cp) || *cp == '.')
		cp++;
	while (*cp)
		(*outc)(*cp++);
#endif
	return 0;
}

/*
 * Module: tutil.c
 *
 * Purpose: Utility routines for TERMLIB functions.
 *
 */

	static int
_match(s1, s2)                 /* returns length of text common to s1 and s2 */
char *s1, *s2;
{
	int i = 0;

	while (s1[i] && s1[i] == s2[i])
		i++;

	return i;
}

	static char *
_find(s, set)   /* finds next c in s that's a member of set, returns pointer */
char *s, *set;
{
	for(; *s; s++) {
		char    *ptr = set;

		while (*ptr && *s != *ptr)
			ptr++;

		if (*ptr)
			return s;
	}

	return s;
}

	static char *
_addfmt(buf, fmt, val)             /* add val to buf according to format fmt */
char *buf, *fmt;
int val;
{
	sprintf(buf, fmt, val);
	while (*buf)
		buf++;
	return buf;
}
