/************************************************************************
 *									*
 *			Copyright (c) 1982, Fred Fish			*
 *			    All Rights Reserved				*
 *									*
 *	This software and/or documentation is released for public	*
 *	distribution for personal, non-commercial use only.		*
 *	Limited rights to use, modify, and redistribute are hereby	*
 *	granted for non-commercial purposes, provided that all		*
 *	copyright notices remain intact and all changes are clearly	*
 *	documented.  The author makes no warranty of any kind with	*
 *	respect to this product and explicitly disclaims any implied	*
 *	warranties of merchantability or fitness for any particular	*
 *	purpose.							*
 *									*
 ************************************************************************
 */


/*
 *	tgetflag   extract boolean termcap capability
 *
 *	Returns TRUE if specified id is present in terminal
 *	entry, FALSE otherwise.
 *
 */

#include <stdio.h>
#include <string.h>
#include <termcap.h>
#include <ctype.h>

#define TRUE 1
#define FALSE 0
#define BUFSIZE 1024			/* Assumed size of external buffer */

#define NO_FILE	 -1			/* Returned if can't open file */
#define NO_ENTRY  0			/* Returned if can't find entry */
#define SUCCESS   1			/* Returned if entry found ok */
#define TRUNCATED 2			/* Returned if entry found but trunc */

# define DEFAULT_ROOT "termcap"		/* name without path component */
# ifdef __MINT__
# define DEFAULT_FILE "u:\\etc\\termcap"
# else
# define DEFAULT_FILE "/etc/termcap"	/* default termcap filename */
# endif

char *_tcpbuf;		/* Termcap entry buffer pointer */

int tgetflag(char *id)
{
    char *bp;

    bp = _tcpbuf;

    while ((bp = strchr(bp, ':')) != NULL) {
	bp++;
	if (*bp++ == id[0] && *bp != '\0' && *bp++ == id[1]) {
	    if (*bp == '\0' || *bp++ == ':') {
		return(TRUE);
	    } else {
		return(FALSE);
	    }
	}
    }
    return(FALSE);
}

/*
 *	do_esc    process an escaped sequence
 *
 *	Processes an escape sequence pointed to by
 *	in, transfering it to location pointed to
 *	by out, and updating the pointer to in.
 *
 */

static char *maplist = {"E\033b\bf\fn\nr\rt\t"};

static char *do_esc(char *out,char *in)
{   int count;
    char ch;
    char *cp;

    if (*in != '\0') {
	if (isdigit(*in)) {
	    ch = 0;
	    for (count = 0; count < 3 && isdigit(*in); in++) {
		 ch <<= 3;
		 ch |= (*in - '0');
	    }
	    *out++ = ch;
	} else if ((cp = strchr(maplist,*in)) != NULL) {
	    *out++ = *++cp;
	    in++;
	} else {
	    *out++ = *in++;
	}
    }
    return(in);
}

/*
 *
 *	decode   transfer string capability, decoding escapes
 *
 *  DESCRIPTION
 *
 *	Transfers the string capability, up to the next ':'
 *	character, or null, to the buffer pointed to by
 *	the pointer in *area.  Note that the initial
 *	value of *area and *area is updated to point
 *	to the next available location after the null
 *	terminating the transfered string.
 *
 *  BUGS
 *
 *	There is no overflow checking done on the destination
 *	buffer, so it better be large enough to hold
 *	all expected strings.
 *
 */

static char *decode(char *bp,char **area)
{   char *cp, *bgn;

    cp = *area;
    while (*bp != '\0' && *bp != ':') {
	switch(*bp) {
	case '\\':
	    bp = do_esc(cp++,++bp);
	    break;
	case '^':
	    *cp++ = *++bp & 037;
	    bp++;
	    break;
	default:
	    *cp++ = *bp++;
	    break;
	}
    }
    *cp++ = '\0';
    bgn = *area;
    *area = cp;
    return(bgn);
}

/*
 *
 *	tgetstr   extract string capability from termcap entry
 *
 *	Gets the string capability for <id>, placing it in
 *	the buffer at *area, and advancing *area to point
 *	to next available storage.
 *
 *	For example, if the following capabilities are
 *	in the termcap file:
 *
 *		ZZ=zzzz
 *		YY=yyyyyy
 *		WW=www
 *
 *	then successive calls using YY, ZZ, and WW will
 *	build the following buffer:
 *
 *		yyyyyy0zzzz0www0
 *
 *	The first call will return a pointer to yyyyyy, the
 *	second will return a pointer to zzzz and the third
 *	will return a pointer to www.  Note that each
 *	string is null terminated, as are all C strings.
 *
 *	Characters preceded by the carot character (\136)
 *	are mapped into the corresponding control character.
 *	For example, the two character sequence ^A becomes
 *	a single control-A (\001) character.
 *
 *	The escape character is the normal C backslash and
 *	the normal C escape sequences are recognized, along
 *	with a special sequence for the ASCII escape character
 *	(\033).  The recognized sequences are:
 *
 *		\E   =>  '\033'  (ASCII escape character)
 *		\b   =>  '\010'  (ASCII backspace character)
 *		\f   =>  '\014'  (ASCII form feed character)
 *		\n   =>  '\012'  (ASCII newline/linefeed char)
 *		\r   =>  '\015'  (ASCII carriage return char)
 *		\t   =>  '\011'  (ASCII tab character)
 *		\ddd =>  '\ddd'  (arbitrary ASCII digit)
 *		\x   =>  'x'     (ordinary ASCII character)
 *
 */

char *tgetstr(char *id,char **area)
{
    char *bp;

    bp = _tcpbuf;
    while ((bp = strchr(bp,':')) != NULL) {
	bp++;
	if (*bp++ == id[0] && *bp != '\0' && *bp++ == id[1]) {
	    if (*bp != '\0' && *bp++ != '=') {
		return(NULL);
	    } else {
		return(decode(bp,area));
	    }
	}
    }
    return(NULL);
}

/*
 *
 *	tputs     output string with appropriate padding
 *
 *	Outputs string pointed to by cp, using function outc, and
 *	following it with the appropriate number of padding characters.
 *	Affcnt contains the number of lines affected, which is used
 *	as a multiplier for the specified per line pad time.  If
 *	per line pad count is not applicable, affcnt should be 1,
 *	NOT zero.
 *
 *	The format of the string pointed to by cp is:
 *
 *		[pad time][*]<string to send>
 *
 *		where:	pad time => time to delay in milliseconds
 *			* => specifies that time is per line
 *			
 *	The pad character is assumed to reside in the external
 *	variable "PC".  Also, the external variable "ospeed"
 *	should contain the output speed of the terminal as
 *	encoded in /usr/include/sgtty.h  (B0-B9600).
 *
 *  BUGS
 *
 *	Digit conversion is based on native character set
 *	being ASCII.
 *
 */

# ifdef SGTTY
extern char PC;			/* Pad character to use */
extern char ospeed;		/* Encoding of output speed */

static int _times[] = {
    0,				/* Tenths of ms per char 0 baud */
    2000,			/* Tenths of ms per char 50 baud */
    1333,			/* Tenths of ms per char 75 baud */
    909,			/* Tenths of ms per char 110 baud */
    743,			/* Tenths of ms per char 134 baud */
    666,			/* Tenths of ms per char 150 baud */
    500,			/* Tenths of ms per char 200 baud */
    333,			/* Tenths of ms per char 300 baud */
    166,			/* Tenths of ms per char 600 baud */
    83,				/* Tenths of ms per char 1200 baud */
    55,				/* Tenths of ms per char 1800 baud */
    41,				/* Tenths of ms per char 2400 baud */
    20,				/* Tenths of ms per char 4800 baud */
    10				/* Tenths of ms per char 9600 baud */
};
# endif

# ifdef SGTTY
static void do_padding (int ptime, int (*outc )(int));
# endif

void tputs(char *cp,int affcnt,int (*outc) (int))
{
    int ptime;			/* Pad time in tenths of milliseconds */

    if (cp == NULL || *cp == '\0') {
	return;
    } else {
	for (ptime = 0; isdigit(*cp); cp++) {
	    ptime *= 10;
	    ptime += (*cp - '0');
	}
	ptime *= 10;
	if (*cp == '.') {
	    cp++;
	    if (isdigit(*cp)) {
		ptime += (*cp++ - '0');
	    }
	    while (isdigit(*cp)) {cp++;}
	}
	if (*cp == '*') {
	    ptime *= affcnt;
	    cp++;
	}
	while (*cp != '\0') {
	    (*outc)(*cp++);
	}
# ifdef SGTTY
	do_padding(ptime,outc);
# endif
    }
}

# ifdef SGTTY
/*
 *	do_padding    transmit any pad characters required
 *
 *	Does any padding required as specified by ptime (in tenths
 *	of milliseconds), the output speed given in the external
 *	variable ospeed, and the pad character given in the
 *	external variable PC.
 *
 */


static void do_padding(ptime,outc)
int ptime;
int (*outc) (int);
{
    register int nchars;
    register int tpc;

    if (ptime != 0) {
	if (ospeed >= 0 && ospeed <= (sizeof(_times)/ sizeof(int))) {
	    tpc = _times[ospeed];
	    ptime += (tpc / 2);
	    nchars = ptime / tpc;
	    for ( ; nchars > 0; --nchars) {
		(*outc)(PC);
	    }
	}
    }
}
# endif

/*
 *	build_entry    construct termcap entry in a given buffer
 *
 *      For a given name build in a buffer bp a termcap description
 *      using a contents of file fp.  Continue this until the entry
 *      is complete or we reached stop.  Concatenate entries if
 *      required by tc capability.   White space characters and
 *      backslashes escaping newlines are not copied into bp.
 *      Returns SUCCESS if there was no problems, NO_ENTRY if an
 *      entry with given name was not found and TRUNCATED if we
 *      run out of a buffer space or continuation entry specified
 *      with tc was not found
 *
 *  BUGS
 *
 *      Termcap specifications require for tc to be the last capability
 *      for the given name.  This is not enforced but anything which
 *      follows tc in the same description will be discarded.
 *      It is not entirely clear what we should return when continuation
 *      specified with tc failed.
 *      Other stuff which goes beyond termcap specs can be accepted.
 *      Terminal names starting with '#' are not accepted.
 *      Continuation with names over 127 characters long will likely bomb!
 *
 *  AUTHOR
 *
 *	Michal Jaegermann
 *
 */

static int build_entry(char *bp,char *stop,FILE *fp, char *name)
{
    int c;
    int so_far, skip_all = 0;
    char *np;
    char nbuf[128];
    extern int _tgetc (FILE *fp);

    /* rewind file - we may seek for a continuation entry */
    rewind(fp);

    /*
     * this is FSM - sort of 
     */
    while (EOF != (c = getc(fp))) {
	 /*
	  * we are looking at a comment - skip it
	  */
	if ('#' == c) {
	    do {
		if (EOF == (c = getc(fp)))
		    return NO_ENTRY;
	    } while ('\n' != c);
	}
	/*
	 * empty line or we finished comment traversal;
	 * a little bit to good - but valid termcap file will be
	 * stil accepted
	 */
	if (isspace(c))
	    continue;
	/*
	 * try matching name
	 */
	np = name;
	while (*np == c) {
	    np += 1;
	    c = _tgetc(fp);
	}
	/*
	 * we finished traversing our name - is this really a match ?
	 */
	if (*np == '\0') {
	    if (c == '|' || c == ':')
		break; /* we have a match */
	    if  (c == EOF)
		return (TRUNCATED); /* match - but we wanted more */
	}
	/*
	 * no match - skip until next name or next entry
	 * if we are past all possible names here 
	 */
	skip_all = 0;
	while ('\n' != c) {
	    if (':' == c)
		skip_all = 1; /* we are past all valid names for this entry */
	    if ('|' == c && 0 == skip_all)
		break;
	    c = _tgetc(fp);
	}
    }
    if (EOF == c)
	return (NO_ENTRY);
    while (':' != c)	/* skip the remainig variants of terminal names */
	c = _tgetc(fp); /* we do not want any mixups later             */

    /*
     * at last we got it - copy terminal description into bp
     */
    so_far = 0;  /* rough indicator how far we are into a capability */
    while ('\n' != (c = _tgetc(fp))) {
	if (0 == so_far && !isalpha(c))
	    continue;	 /* do not bother with all kind of spurious stuff */
	so_far++;
	if (1 == so_far && 't' == c ) {
	    /* a special case - maybe we have "tc=" string? */
	    if ((bp + 3) > stop) {
		ungetc(c, fp);
		continue;
		/* cheating with so_far, but we want to skip this case! */
	    }
	    *bp++ = c;
	    c = _tgetc(fp);
	    if ('c' == c) {
		*bp++ = c;
		c = _tgetc(fp);
		if ('=' == c) {
		    /* we will continue with a name which follows */
		    bp -= 2;
		    /* copy new name to nbuf */
		    np = nbuf;
		    while (':' != (c = _tgetc(fp))) {
			if ('\n' == c || EOF == c)
			    break;
			*np++ = c;
		    }
		    *np = '\0';
		    return (SUCCESS == build_entry(bp, stop, fp, nbuf) ?
			    SUCCESS : TRUNCATED);
		}
	    }
	}  /* end of 'tc=' check */
	if (':' == c) /* literal colon cannot occur in capabilities strings -
		       * one has to use '\072' instead */
	    so_far =  0;
	*bp++ = c;
	if (bp >= stop)
	    return(TRUNCATED);
    }
    if (bp < stop)
	*bp = '\0';
    return(SUCCESS);
}

/*
 * Auxilary function to read a character from a text file
 * with skipping escaped line terminators; any escaped
 * '\n' will be replaced by a character which follows.
 * After escape any number of ^M's will vanish,
 * i.e a string of three characters '\\', 0x0d, 'a' will read
 * as a string of two characters '\\', 'a' and so on...
 * We do not tolerate such garbage in text files. :-)
 */

int _tgetc(FILE *fp)
{   int c;

    if ('\\' == (c = getc(fp))) {
	while ('\r' == (c = getc(fp)))
	    ;				/* Messy stuff - go away */
	if (c != '\n') {
	    ungetc(c, fp);
	    return ('\\');
	}
	c = getc(fp);
    }
    return(c);
}

#ifdef MINT
# define PATHSEP ','
#else
# define PATHSEP ';'
#endif

/* Follow the PATH, trying to fopen the file.  Takes one additional
 * argument which can be NULL.  Otherwise this argument gets filled
 * in the full path to the file.  Returns as does fopen().

   Fopenp was already defined in stdio.h on the GCC compiler, so I
   renamed it to nfopenp() (Kees Lemmens, 93)
 */

static FILE *
nfopenp(char *name,char *mode,char *pathname)
{
	char buffer[BUFSIZ], *buf, *bufp, *pathp, lastch;
	FILE *fp;
	extern char *getenv (const char *);

	/* If pathname is given, use it instead of buf so the calling
	 * process knows the path we found name under
	 */
	if (pathname)
		buf = pathname;
	else
		buf = buffer;

	strcpy(buf, name);
	pathp = getenv("PATH");
	while (pathp && *pathp) {
		bufp = buf;
		while (*pathp && *pathp != PATHSEP)
			lastch = *bufp++ = *pathp++;
		if (lastch != '\\')
			*bufp++ = '\\';
		strcpy(bufp, name);
		if ((fp = fopen(buf, mode)) != NULL)
			return fp;
		if (*pathp)
			pathp++;
	}
	return NULL;
}

/*
 * we do not really need the following part once the stuff is in
 * our termcap  buffer
 */

static char term_default[] = "st52:co#80:li#25\
:al=\\EL:am:bs:cd=\\EJ:ce=\\EK:cl=\\EE:cm=\\EY%+ %+ :dl=\\EM\
:do=\\EB:eo:ho=\\EH:is=\\Eq\\EE\\Ee\\Ev:it#8:pt:kb=^H:ll=\\EY9!\
:me=\\Eq:mr=\\Ep:le=\\ED:nd=\\EC:rc=\\Ek:sc=\\Ej:se=\\Eq:so=\\Ep:ta=^I\
:up=\\EA:ve=\\Ee:vi=\\Ef:km:bl=^G:cr=^M:ti=\\Ev\\Ee:sr=\\EI:sf=^J";

/*
 *	find_file    find the termcap file and open it if possible
 *
 *	Attempts to locate and open the termcap file.  Also handles
 *	using the environment TERMCAP string as the actual buffer
 *	(that's why bp has to be an input parameter).
 *
 *	If TERMCAP is defined an begins with a '/' character then
 *	it is taken to be the pathname of the termcap file and
 *	an attempt is made to open it.  If this fails then
 *	the default termcap file is used instead.
 *
 *	If TERMCAP is defined but does not begin with a '/' then
 *	it is assumed to be the actual buffer contents provided
 *	that <name> matches the environment variable TERM.
 *
 *  BUGS
 *
 *	There is currently no way to be sure which termcap
 *	file was opened since the default will always be
 *	tried.
 *
 */

static FILE *find_file(char *bp)
{
    FILE *fp;
    char *cp;
    extern char *getenv (const char *);

    if ((cp = getenv("TERMCAP")) != NULL) {
	if (*cp != '\0') {
	    if (*cp == '/' || *cp == '\\' || (cp[1] == ':')) {
		if ((fp = fopen(cp,"r")) != NULL) {
		    return(fp);
		}
	    } else {
		if (getenv("TERM") != NULL) {
			strcpy(bp,cp);
			return((FILE *)NULL);
		}
	    }
	}
    }
	/* Try current directory, then /etc/termcap, then along the path
	 */
	if ((fp = fopen(DEFAULT_ROOT, "r")) != NULL)
		return fp;
	else if ((fp = fopen(DEFAULT_FILE, "r")) != NULL)
		return fp;
	else if ((fp = nfopenp(DEFAULT_ROOT, "r", NULL)) != NULL)
		return fp;
	else {
		/* 
		 * if we do not have any better information, then
		 */
		strcpy (bp, term_default);
		return (FILE *) NULL;
	}
}

/*
 *
 *	tgetent   load buffer with entry for specified terminal
 *
 *	Extracts the entry for terminal <name> from the termcap file
 *	and places it in the character buffer <bp>.   It is currently
 *	assumed that bp is at least 1024 characters.  If the entry in
 *	the termcap file is larger than 1023 characters the excess
 *	characters will be discarded and appropriate status will
 *	be returned.
 *
 *	Also note that since bp is used by other termcap
 *	routines, the storage associated with the termcap entry
 *	cannot be freed until all termcap calls are completed.
 *
 *	Tgetent can be directed to look in a file other than
 *	the default (/etc/termcap) by defining an environment
 *	variable called TERMCAP to be the pathname of the desired
 *	termcap file.  This is useful for debugging new entries.
 *	NOTE: the pathname MUST begin with a '/' character.
 *      (Atari ST specific change: the pathname may begin with '\',
 *       or with a drive letter followed by ':').
 *
 *	Also, if the string assigned to TERMCAP does not begin with
 *	a '/' and if the environment variable TERM matches <name> then
 *	the string assigned to TERMCAP is copied to buffer <bp> 
 *	instead of reading a termcap file.
 *      (Atari ST specific change: TERM is no longer checked (the
 *       check was buggy).
 *
 *	Modification by ERS: if no termcap file can be found, then
 *	a default termcap is used (this is for GEMDOS).
 *
 *      Further mods by MJ: original routines fail to proces valid
 *      termcap files - replaced with new versions.
 *      Atari specific: default termcap used when nothing better is
 *      around reads a number of rows and colums from Line-A variables.
 *	
 *  RETURNS
 *
 *	-1  if the termcap file cannot be opened
 *	 0  if no entry in termcap file matches <name>
 *	 1  if extraction is successful with no errors
 *	 2  if extraction is successful but entry truncated
 *
 *  SEE ALSO
 *
 *	tgetnum   extract numeric type capability
 *	tgetflag  test boolean type capability
 *	tgetstr   get string value of capability
 *
 *  AUTHOR
 *
 *	Fred Fish
 *
 */

int tgetent(bp,name)
char *bp;				/* Pointer to buffer (1024 char min) */
char *name;				/* Pointer to terminal entry to find */
{
    FILE *fp;

    *bp = '\0';
    _tcpbuf = bp;
    if ((fp = find_file(bp)) == NULL) {
	if (*bp != '\0') {
	    return(SUCCESS);
	} else {
	    return(NO_FILE);
	}
    } else {
	*bp++ = ':';
	return (build_entry(bp, bp + BUFSIZE - 1, fp, name));
    }
}

/*
 *
 *	tgetnum   extract numeric option from termcap entry
 *
 *	Returns numeric value of capability <id>, or -1 if <id>
 *	is not found.   Knows about octal numbers, which
 *	begin with 0.
 *
 */

extern char *_tcpbuf;		/* Termcap entry buffer pointer */

int tgetnum(id)
char *id;
{
    int value, base;
    char *bp;

    bp = _tcpbuf;
    while ((bp = strchr(bp,':')) != NULL) {
	bp++;
	if (*bp++ == id[0] && *bp != '\0' && *bp++ == id[1]) {
	    if (*bp != '\0' && *bp++ != '#') {
		return(-1);
	    } else {
		value = 0;
		if (*bp == '0') {
		    base = 8;
		} else {
		    base = 10;
		}
		while (isdigit(*bp)) {
		    value *= base;
		    value += (*bp++ - '0');
		}
		return(value);
	    }
	}
    }
    return(-1);
}

/*
 *
 *	tgoto   expand cursor addressing string from cm capability
 *
 *	Returns cursor addressing string, decoded from the cm
 *	capability string, to move cursor to column destcol on
 *	line destline.
 *
 *	The following sequences uses one input argument, either
 *	line or column, and place the appropriate substitution
 *	in the output string:
 *
 *		%d	substitute decimal value (in ASCII)
 *		%2	like %d but forces field width to 2
 *		%3	like %d but forces field width to 3
 *		%.	like %c
 *		%+x	like %c but adds ASCII value of x
 *
 *	The following sequences cause processing modifications
 *	but do not "use up" one of the arguments.  If they
 *	act on an argument they act on the next one to
 *	be converted.
 *
 *		%>xy	if next value to be converted is
 *			greater than value of ASCII char x
 *			then add value of ASCII char y.
 *		%r	reverse substitution of line
 *			and column (line is substituted
 *			first by default).
 *		%i	causes input values destcol and
 *			destline to be incremented.
 *		%%	gives single % character in output.
 *
 *  BUGS
 *
 *	Does not implement some of the more arcane sequences for
 *	radically weird terminals (specifically %n, %B, & %D).
 *	If you have one of these you deserve whatever happens.
 *
 */

#define MAXARGS 2

static char *in;		/* Internal copy of input string pointer */
static char *out;		/* Pointer to output array */
static int args[MAXARGS];	/* Maximum number of args to convert */
static int pcount;		/* Count of args processed */
static char output[64];		/* Converted string */
static void process (void);

char *tgoto(cm,destcol,destline)
char *cm;
int destcol;
int destline;
{
    if (cm == NULL) {
	return("");
    } else {
	in = cm;
	out = output;
	args[0] = destline;
	args[1] = destcol;
	pcount = 0;
	while (*in != '\0') {
	    if (*in != '%') {
		*out++ = *in++;
	    } else {
		process();
	    }
	}
 	*out = 0;
	return(output);
    }
}
/*
 *	process   process the conversion/command sequence
 *
 *	Processes the sequence beginning with the % character.
 *	Directly manipulates the input string pointer, the
 *	output string pointer, and the arguments.  Leaves
 *	the input string pointer pointing to the next character
 *	to be processed, and the output string pointer pointing
 *	to the next output location.  If conversion of
 *	one of the numeric arguments occurs, then the pcount
 *	is incremented.
 *
 */

static void process(void)
{
    int temp;

    in++;
    switch(*in++) {
    case 'd':
	sprintf(out,"%d",args[pcount++]);
	out = &output[(int)strlen(output)];	
	break;
    case '2':
	sprintf(out,"%02d",args[pcount++]);
	out = &output[(int)strlen(output)];
	break;
    case '3':
	sprintf(out,"%03d",args[pcount++]);
	out = &output[(int)strlen(output)];
	break;
    case '.':
	*out++ = args[pcount++];
	break;
    case '+':
	*out++ = args[pcount++] + *in++;
	break;
    case '>':
	if (args[pcount] > *in++) {
	    args[pcount] += *in++;
	} else {
	    in++;
	}
	break;
    case 'r':
	temp = args[pcount];
	args[pcount] = args[pcount+1];
	args[pcount+1] = temp;
	break;
    case 'i':
	args[pcount]++;
	args[pcount+1]++;
	break;
    case '%':
	*out++ = '%';
	break;
    }
}
