/* TTY input driver */

#include <stdio.h>
#include <ctype.h>
#include "global.h"

int ttymode;
#define TTY_COOKED	0
#define TTY_RAW 1

#define LINESIZE	256
#define MAXHIST		25

#define CTLR		18
#define CTLX		24
#define CTLU		21
#define CTLV		22
#define CTLW		23
#define CTLZ		26

struct linehist
{
    struct linehist *next;
    struct linehist *prev;
    char linebuf[1];
};
#define NULLHIST	(struct linehist *)0

struct linehist *linehist = NULLHIST;

raw()
{
    ttymode = TTY_RAW;
}

cooked()
{
    ttymode = TTY_COOKED;
}

static void echochar (c)		/* echo character */
    unsigned char c;

{
    if (c < ' ') {			/* a control char? */
       putchar('^');			/* show as ^char */
       c += 64;
    }

    putchar(c);
}

static void bschar (c)			/* backspace over character */
    unsigned char c;

{
    if (c < ' ')			/* a control char? */
	putchar('\b');			/* 2 backspaces needed */

    putchar('\b');
}

static void erachar (c)			/* erase character */
    unsigned char c;

{
    if (c < ' ')			/* a control char? */
	putchar(' ');			/* 2 spaces needed */

    putchar(' ');

    if (c < ' ')			/* a control char? */
	putchar('\b');			/* 2 backspaces needed */

    putchar('\b');
}

/* Accept characters from the incoming tty buffer and process them
 * (if in cooked mode) or just pass them directly (if in raw mode).
 * Returns the number of characters available for use; if non-zero,
 * also stashes a pointer to the character(s) in the "buf" argument.
 */
 /*Control-R added by df for retype of lines - useful in Telnet */
 /* full editing and history added by PE1CHL */

int
ttydriv(c,buf)
int  c;
char **buf;
{
    static char linebuf[LINESIZE + 1];
    static char *cp = linebuf;
    static struct linehist *lh = NULLHIST;
    static char insmode = 0;
    static char quote = 0;
    static int tailchars = 0;
    register char *p,*q;
    int cnt,i,seenprint;

    if(c == -1)			/* -1 is used to inquire buffered chars */
	return((int) (cp - linebuf) + tailchars);

#ifdef DEBUG
    if(buf == (char **)NULL)
	return 0;		/* paranoia check */
#endif

    /* initialize buffer when 1st call for this line */
    if(cp == linebuf && !tailchars){
	memset(linebuf,0,LINESIZE);
	if((lh = linehist) != NULLHIST)
	    strcpy(linebuf,lh->linebuf);
    }

    cnt = 0;
    switch(ttymode){
    case TTY_RAW:
	tailchars = 0;		/* clear the COOKED line buffer */
	insmode = quote = 0;
	cp = linebuf;
	switch(c){
	case -20:		/* HOME key */
	    strcpy(linebuf,"\033[H");
	    cp += 3;
	    break;
	case -19:		/* UP arrow */
	    strcpy(linebuf,"\033[A");
	    cp += 3;
	    break;
	case -17:		/* LEFT arrow */
	    strcpy(linebuf,"\033[D");
	    cp += 3;
	    break;
	case -16:		/* RIGHT arrow */
	    strcpy(linebuf,"\033[C");
	    cp += 3;
	    break;
	case -14:		/* DOWN arrow */
	    strcpy(linebuf,"\033[B");
	    cp += 3;
	    break;
	default:		/* a normal character */
	    *cp++ = c;
	    break;
	}
	cnt = (int) (cp - linebuf); /* return count of characters */
	break;

    case TTY_COOKED:
	/* Perform cooked-mode line editing */
	if (quote)		/* prev char was quote? (^V) */
	    goto storechar;	/* then store anything */

	switch(c){
	case '\r':		/* CR and LF are equivalent */
	case '\n':
	    while(tailchars > 0){ /* move to end of line */
		echochar(*cp++);
		tailchars--;
	    }
	    *cp++ = '\r';
	    *cp++ = '\n';
	    *cp = '\0';
	    printf("\n");
	    cnt = (int) (cp - linebuf);
	    cp = linebuf;
	    if(cnt > 3){
		i = 0;
		for(lh = linehist; lh != NULLHIST; lh = lh->next){
		    if(!strcmp(lh->linebuf,linebuf) || ++i > MAXHIST){
			if(lh->prev != NULLHIST)
			    lh->prev->next = lh->next;
			else
			    linehist = lh->next;

			if(lh->next != NULLHIST)
			    lh->next->prev = lh->prev;
			free(lh);
		    }
		}
		if((lh = (struct linehist *) malloc(sizeof(struct linehist) + cnt)) != NULLHIST){
		    if((lh->next = linehist) != NULLHIST)
			lh->next->prev = lh;
		    lh->prev = NULLHIST;
		    linehist = lh;
		    strcpy(lh->linebuf,linebuf);
		}
	    }
	    insmode = 0;
	    break;
	case 0x7f:		/* Delete */
	    if (tailchars > 0){
		tailchars--;
		c = *cp;
		for(i = 0, p = cp, q = cp + 1; i < tailchars; i++){
		    echochar(p[1]);
		    *p++ = *q++;
		}
		erachar(c);
		*p = '\0';
		for(i = 0; i < tailchars; i++)
		    bschar(*--p);
	    } else {
		goto storechar;
	    }
	    break;
	case '\b':		/* Backspace */
	    if(cp != linebuf){
		bschar(c = *--cp);
		for(i = 0, p = cp, q = cp + 1; i < tailchars; i++){
		    echochar(p[1]);
		    *p++ = *q++;
		}
		erachar(c);
		*p = '\0';
		for(i = 0; i < tailchars; i++)
		    bschar(*--p);
	    }
	    break;
	case CTLR:		/* print line buffer */
	    echochar(c);	/* ^R */
	    putchar('\n');
	    p = linebuf;
	    cp += tailchars;
	    while(p < cp)
		echochar(*p++);
	    tailchars = 0;
	    break;
	case CTLU:		/* Line kill */
	case CTLX:		/* Also a line kill */
	    while(tailchars > 0){
		echochar(*cp++);
		tailchars--;
	    }
	    while(cp != linebuf){
		bschar(*--cp);
		erachar(*cp);
	    }
	    break;
	case CTLV:		/* Literal Next */
	    quote = 1;
	    c = '^';		/* insert a ^ temporarily */
	    goto storechar;
	case CTLW:		/* Word Erase */
	    seenprint = 0;	/* Haven't seen a printable char yet */
	    while(cp != linebuf){
		if (!isspace(cp[-1]))	/* Next is a printable char? */
		    seenprint = 1;
		else
		    if (seenprint)	/* Next is a space, quit when we */
			break;		/* already erased characters */
		bschar(c = *--cp);
		for(i = 0, p = cp, q = cp + 1; i < tailchars; i++){
		    echochar(p[1]);
		    *p++ = *q++;
		}
		erachar(c);
		*p = '\0';
		for(i = 0; i < tailchars; i++)
		    bschar(*--p);
	    }
	    break;
	case -14:		/* down-arrow = next in history */
	    if(lh != NULLHIST && lh->prev != NULLHIST){
		lh = lh->prev;
		while(tailchars > 0){ /* move to end of line */
		    echochar(*cp++);
		    tailchars--;
		}
		while(cp != linebuf){ /* erase it */
		    bschar(*--cp);
		    erachar(*cp);
		}
		memset(linebuf,0,LINESIZE);
		strcpy(linebuf,lh->linebuf);
		for(cp = linebuf; *cp && *cp != '\r'; cp++){
		    echochar(*cp);
		}
	    }
	    break;
	case -12:		/* insert */
	    insmode ^= 1;	/* toggle insert mode */
	    break;
	case -16:		/* right-arrow = buffer character */
	    insmode = 0;	/* insert mode screws things up */
	    if(*cp && *cp != '\r'){
		c = *cp;
		goto storechar;
	    }
	    break;
	case -17:		/* left-arrow = nondestruct backspace */
	    if(cp != linebuf){
		tailchars++;
		bschar(*--cp);
	    }
	    break;
	case -19:		/* up-arrow = prev in history */
	    if(lh != NULLHIST){
		while(tailchars > 0){ /* move to end of line */
		    echochar(*cp++);
		    tailchars--;
		}
		while(cp != linebuf){ /* erase it */
		    bschar(*--cp);
		    erachar(*cp);
		}
		memset(linebuf,0,LINESIZE);
		strcpy(linebuf,lh->linebuf);
		if (lh->next != NULLHIST)
		    lh = lh->next;
		for(cp = linebuf; *cp && *cp != '\r'; cp++)
		    echochar(*cp);
	    }
	    break;
	case -20:		/* HOME = begin of line */
	    while(cp != linebuf){
		tailchars++;
		bschar(*--cp);
	    }
	    break;
	case -15:		/* END = end of line */
	    while(tailchars > 0){
		echochar(*cp++);
		tailchars--;
	    }
	    break;
	default:		/* Ordinary character */
	    if(c < 0)		/* a non-decoded special character? */
		break;		/* ignore it */

storechar:
	    echochar(c);

	    if(insmode && quote < 2){
		for(i = 0, p = cp; i < tailchars; i++)
		    echochar(*p++);
		for(i = 0, q = p + 1; i < tailchars; i++)
		    bschar(*--q = *--p);
	    } else {
		if(tailchars > 0){
		    tailchars--;
		    if(c < ' ' || *cp < ' '){
			for(i = 0, p = cp + 1; i < tailchars; i++)
			    echochar(*p++);
			erachar(' ');
			for(i = 0; i < tailchars; i++)
			    bschar(*--p);
		    }
		}
	    }

	    if(quote)
		if(quote == 1){		/* immediately after ^V */
		    quote = 2;
		    bschar(c);		/* backspace to the '^' */
		    tailchars++;
		    break;
		} else
		    quote = 0;

	    *cp++ = c;

	    if((cp + tailchars) >= &linebuf[LINESIZE]){
		while(tailchars > 0){ /* move to end of line */
		    echochar(*cp++);
		    tailchars--;
		}
		cnt = (int) (cp - linebuf);
		cp = linebuf;
	    }
	    break;
	}
    }
    if(cnt != 0)
	*buf = linebuf;
    else
	*buf = NULLCHAR;
    fflush(stdout);
    return cnt;
}
