
/* This file contains the terminal "driver" code for splitsh.
   It is designed to keep track of the position of the cursor
   while handling a split window of vt100 emulation.
   All of the routines assume that there is no other output 
   going onto the standard output screen, to mess it up.

   Many thanks to Matt Ostanik who wrote the ANSI Handbook.
*/

#include	<sys/types.h>
#ifdef HAVE_TERMIO_H
#include	<termio.h>		/* Used only for TIOCGWINSZ */
#else
#include	<sys/ioctl.h>		/* Used only for TIOCGWINSZ */
#endif
#include	<errno.h>
#include	<stdio.h>
#include	<ctype.h>

#define SEP_CHAR	' '		/* Separator bar character */

extern int   errno;			/* only used in vt_write() */

/* Function declarations */

extern char *getenv();
static void  vt_goto();			/* Go to a coordinate position */
static void  set_wscroll();		/* Set a scrolling region */
static void  set_uscroll();		/* Set a user scroll region */
static void  end_scroll();		/* End all scrolling regions */
static void  save_cursor();		/* Save the current cursor position */
static void  restor_cursor();		/* Restore previous position */
static void  save_ucursor();		/* Save the current cursor for user */
static void  restor_ucursor();		/* Restore previous cursor for user */
static void  pos_addch();		/* Print a char at a cursor pos */
static void  do_vtesc();		/* Actually do the escape functions */
static void  vt_addch();		/* Add a character to a window */

char *init_vt100();			/* Initialize the window */
void  set_win();			/* Move the cursor to current window */
int   vt_write();			/* Write a buffer to a window */
char  vt_prompt();			/* Prompt the user and return a char */
void  vt_info();			/* Print out an info message */
void  end_vt100();			/* End the vt100 scrolling and such */
void  vt_debug();			/* Show the window information */


/* Handy definitions */

#define NUMWINS	2	/* The supported number of windows (must be 2) */
#define UPPER	0				/* The upper window */
#define LOWER	1				/* The lower window */
#define	ESC	'\033'				/* ^[ */
#define CLEAR	write(1, "\033[;H\033[2J", 8)	/* Go to home and clear */

/* Variables */

typedef struct {  /* Cursor coordinate structure */
	unsigned short x;		/* Line position */
	unsigned short y;		/* Column position */
	} pos;

static int just_init=0;	/* Have we just called vt_init() ? */
static int ul_a, ll_a;	/* The bounds of the upper window */
static int ul_b, ll_b;	/* The bounds of the lower window */
static pos c_a;		/* The current position of the upper window cursor */	
static pos c_b;		/* The current position of the lower window cursor */	
static int scroll_a1=0, scroll_a2=0;	/* The ^[[%d;%dr upper region */
static int scroll_b1=0, scroll_b2=0;	/* The ^[[%d;%dr lower region */
static int lines;	/* The number of lines on the screen */
static int columns;	/* The number of columns on the screen */
static char sep[BUFSIZ];	/* The window separator string */

/* Make these four variables accessable to the calling program */

int UU_lines=0;		/* The user requested lines for the upper window */
int WU_lines=0;		/* The number of lines for the upper window */
int WL_lines=0;		/* The number of lines for the lower window */
int W_columns=0;	/* The number of columns per window */

static int LU_lines;	/* A local copy of UU_lines that is modified */

static char *termtype;

/* A debugging routine */

void vt_debug(debug)
FILE *debug;
{
	fprintf(debug, "Bounds of upper win: %d - %d\n", ul_a, ll_a);
	fprintf(debug, "Upper win cursor: %d, %d\n", c_a.x, c_a.y);
	fprintf(debug, "Upper win scrolling region: %d - %d\n", scroll_a1, scroll_a2);
	fprintf(debug, "\n");
	fprintf(debug, "Bounds of lower win: %d - %d\n", ul_b, ll_b);
	fprintf(debug, "Lower win cursor: %d, %d\n", c_b.x, c_b.y);
	fprintf(debug, "Lower win scrolling region: %d - %d\n", scroll_b1, scroll_b2);
	fprintf(debug, "\n");
}

/* Routine to initialize the vt100 screening, returning an error message,
   or NULL if all went well. */

char *init_vt100()
{
#ifdef TIOCGWINSZ
	struct /* winsize */ {
		unsigned short	ws_row;		/* rows, in characters */
		unsigned short	ws_col;		/* columns, in characters */
		unsigned short	ws_xpixel;	/* horizontal size - not used */
		unsigned short	ws_ypixel;	/* vertical size - not used */
	} mywinz;
#endif
	int i;
	char *ptr;

	lines=0; columns=0;	/* Reset the lines and columns */

	/* Check to make sure it's okay to run */
	if ( ! isatty(0) || ! isatty(1) )
		return("Standard input and output must be a tty");

	if ( ((termtype=getenv("TERM")) == NULL || 
         (strncmp(termtype, "vt100", 5)) != 0) && 
			/* A vt100 emulation detector -->  */	! vttest() )
		return("Terminal type must be set to vt100");

	if ( (ptr=getenv("LINES")) != NULL )
		lines=atoi(ptr);
	if ( (ptr=getenv("COLUMNS")) != NULL )
		columns=atoi(ptr);

#ifdef TIOCGWINSZ
	if ( ioctl(0, TIOCGWINSZ, &mywinz) == 0 )
	{
		if ( ! lines )
			lines=mywinz.ws_row;
		if ( ! columns )
			columns=mywinz.ws_col;
	}
#endif

	/* Now set defaults if we can't find the window size */
	if ( ! lines )		lines=24;
	if ( ! columns )	columns=80;

	/* Check that each window is at least 3 lines tall */
	if ( lines < 9 )
		return("Screen is not tall enough to split.");

	/* Set the exportable variables */
	if ( UU_lines ) {
		/* Check the user set # of lines */
		if ( UU_lines > (lines-1-3) )
			LU_lines=(lines-1-3);
		else if ( UU_lines < 3 )
			LU_lines=3;
		else
			LU_lines=UU_lines;

		WU_lines=LU_lines;
		WL_lines=(lines-1-LU_lines);
	} else
		WL_lines=WU_lines=((lines-1)/2);
	W_columns=columns;

	/* Set up the separator */
	strcpy(sep, "\033[7m");
	for ( i=0; (i<columns)&&(i<BUFSIZ); ++i )  sep[i+4]=SEP_CHAR;
	sep[i]='\0';
	strcat(sep, "\033[m");

	/* Clear the screen, and set up the windows */
	CLEAR; 
	vt_goto((WU_lines+1), 1);		/* Move past the top win */
	printf("%s", sep);			/* Print separator */
	ul_a=(1);				/* Upper limit of top win */
	ll_a=(WU_lines);			/* Lower limit of top win */
	ul_b=(WU_lines+1+1);			/* Upper limit of bottom win */
	ll_b=(WU_lines+1+WL_lines);		/* Lower limit of bottom win */
	c_a.x=ul_a; c_a.y=1;			/* Set upper cursor */
	c_b.x=ul_b; c_b.y=1;			/* Set lower cursor */
	fflush(stdout);				/* Update the terminal */

	just_init=1;				/* Set just_init flag */

	return(NULL);
}

/* Write to the specified screen, handling ^[[ escape codes */

static char t_1[12]={'\0'}, t_2[12]={'\0'};
static int ti_1=0, ti_2=0;

/* State definitions */
#define NORMAL	0
#define AT_ESC	1
#define	AT_NUM1	2
#define	AT_NUM2	3
#define AT_QNUM	4
#define AT_LANG	5
static int state[NUMWINS]={NORMAL, NORMAL};

/* Some static data we don't have to constantly initialize.  */
static int lastwrote=(-1);	/* The last window argument to vt_write() */
static int *ul, *ll;		/* The upper and lower limits of that window */
static pos *cursor;		/* The current cursor position in that window */
static int *scroll_1, *scroll_2;	/* The scrolling regions of that win */

int vt_write(win, buf, len)
int win;			/* The window; 0 if top, 1 if bottom */
char *buf;		/* The data to write */
int len;			/* The amount of data to write */
{
	char *ptr;
	int i;

	if ( win && win != 1 ) {   /* Bad 'win' argument */
		errno=EINVAL;
		return(-1);
	}

	/* Set the current window variables (only if we need to) */
	if ( win != lastwrote ) {
		lastwrote=win;
		if ( win == UPPER ) {
			cursor=(&c_a);
			ul=(&ul_a);
			ll=(&ll_a);
			scroll_1=(&scroll_a1);
			scroll_2=(&scroll_a2);
		} else {
			cursor=(&c_b);
			ul=(&ul_b);
			ll=(&ll_b);
			scroll_1=(&scroll_b1);
			scroll_2=(&scroll_b2);
		}
	}

	/* Move the cursor into position if we need to */
	set_win(win);

	for ( ptr=buf, i=0; i<len; ++i, ++ptr )
	{
		switch (state[win])
		{
			case NORMAL:	if ( *ptr == ESC )
						state[win]=AT_ESC;
					else
						vt_addch(win, *ptr);
					break;
			case AT_ESC:	if ( *ptr == '[' )
						state[win]=AT_NUM1;
					else if ( *ptr == '(' )
						state[win]=AT_LANG;
					else if ( *ptr == 'c' ) 
					{  /* Terminal reset */
						printf("\033c");
						fflush(stdout);
						state[win]=NORMAL;
					}
					else if ( *ptr == 'E' )
					{  /* Effectively a linefeed */
						if ( cursor->x != *ll )
							++(cursor->x);
						printf("\n");
					}
					else if ( *ptr == 'D' )
					{  /* Scroll text up. */
						printf("\033D");
						fflush(stdout);
						state[win]=NORMAL;
					}
					else if ( *ptr == 'M' )
					{  /* Scroll text down */
						printf("\033M");
						fflush(stdout);
						state[win]=NORMAL;
					}
					else if ( *ptr == '7' )
					{  /* Save cursor. */
						save_ucursor();
						state[win]=NORMAL;
					}
					else if ( *ptr == '8' )
					{  /* Restore cursor */
						restor_ucursor(win);
						state[win]=NORMAL;
					}
					else if ( *ptr == 'Z' )
					{ /* Query terminal type */
						printf("\033Z");
						fflush(stdout);
						state[win]=NORMAL;
					}
					else	
						state[win]=NORMAL;
					break;
			/* For vt100 compatibility */
			case AT_LANG:	switch (*ptr) {
						case 'A': /* US charset */
							  break;
						case 'B': /* UK charset */
							  break;
						default:  /* ROM charset */
							  break;
					}
					printf("\033(%c", *ptr);
					fflush(stdout);
					state[win]=NORMAL;
					break;
			case AT_NUM1:	if ( isdigit(*ptr) )
						t_1[ti_1++]=(*ptr);
					else if ( *ptr == '?' )
						state[win]=AT_QNUM;
					else {
						t_1[ti_1]='\0';
						ti_1=0;
						if ( *ptr == ';' )
							state[win]=AT_NUM2;
						else
						{
							do_vtesc(win, *ptr);
							state[win]=NORMAL;
						}
					}
					if ( ti_1 > 10 )
					{  /* Way too many digits */
						ti_1=0;
						state[win]=NORMAL;
					}
					break;
			case AT_NUM2:	if ( isdigit(*ptr) )
						t_2[ti_2++]=(*ptr);
					else 
					{
						t_2[ti_2]='\0';
						ti_2=0;
						do_vtesc(win, *ptr);
						state[win]=NORMAL;
					}
					if ( ti_2 > 10 )
					{  /* Way too many digits */
						ti_2=0;
						state[win]=NORMAL;
					}
					break;
			case AT_QNUM:	if ( isdigit(*ptr) )
						t_1[ti_1++]=(*ptr);
					else
					{ /* Write the escape code */
						t_1[ti_1]='\0';
						ti_1=0;
						printf("\033[?%s%c", t_1, *ptr);	
						fflush(stdout);
						t_1[0]='\0';
						state[win]=NORMAL;
					}
					break;
			default:	state[win]=NORMAL;	/* Say what?? */
		}
	}
	return(i);
}
					

/* Add a character to the requested screen, updating screen position */
static void vt_addch(win, ch)
int win;
char ch;
{
	int i;

	switch (ch)
	{
		/* Taken from vt102.codes */
		case '\000':	/* NULL (fill character) */
		case '\003':	/* EXT  (half duplex turnaround) */
		case '\004':	/* EOT  (can be disconnect char) */
		case '\005':	/* ENQ  (generate answerback) */
		case '\007':	/* BEL  (sound terminal bell) */
				printf("%c", ch);
				break;
		case '\b':	do_vtesc(win, 'D');	/* (^H) Move 1 left */
				break;
#undef HANDLE_TABS	/* Tab handling doesn't seem to work very well. */
#ifdef HANDLE_TABS
		case '\t':	/* Tab. Assume tabstops are every 8 columns */
				i=((cursor->y%8)?(cursor->y%8):8);
				if ( cursor->y == columns ) {
					printf("\r");
					vt_addch(win, '\n');
					cursor->y=8;
					printf("\033[8C");
				} 
				else if ( (cursor->y+i) > columns ) {
					i=(columns-cursor->y);
					printf("\033[%dC", i);
					cursor->y=columns;
				} else {
					printf("\033[%dC", i);
					cursor->y+=i;
				}
				break;
#endif /* HANDLE_TABS */
		case '\013':	/* Processed as linefeeds */
		case '\014':	/* Don't let the cursor move below window or scrolling region */
		case '\n':	if ( (cursor->x != *ll) && (cursor->x != *scroll_2) )
					++(cursor->x);
				printf("\n");
				break;
		case '\r':	cursor->y = 1; /* Move cursor to left margin */
				printf("\r");
				break;
		case '\016':	/* S0 (selects G1 charset) */
		case '\017':	/* S1 (selects G0 charset) */
		case '\021':	/* XON (continue transmission) */
		case '\022':	/* XOFF (stop transmission) */
				printf("%c", ch);
				break;
		case '\030':	/* Processed as escape cancel */
		case '\032':	state[win]=NORMAL;
				break;
		default:	if ( cursor->y > columns )	/* Wrap */
				{
					if ( cursor->x != *ll )
						++(cursor->x);
					printf("\r\n");
					cursor->y=1;
				}
				else
					++(cursor->y);
				printf("%c", ch);
				break;
	}
	fflush(stdout);
	return;
}

					
/* This is the function that processes the vt100 escape codes */
static void do_vtesc(win, esc)
int win;			/* Which window do we affect? */
char esc;			/* The actual escape code */
{
	int i, count1, count2;

	/* Set the numeric parameters */
	count1=( *t_1 ? atoi(t_1) : 0 );
	count2=( *t_2 ? atoi(t_2) : 0 );
	t_1[0]='\0'; t_2[0]='\0';
	
	/* Execute the escape */
	switch (esc)
	{
		case 'f':  /* Go to position */
		case 'H':	if ( count1 == 0 )
					count1=1;
				if ( count2 == 0 )
					count2=1;
				cursor->x=((*ul)+(count1-1));
				cursor->y=count2;
				if ( cursor->x < *ul )
					cursor->x=(*ul);
				else if ( cursor->x > *ll )
					cursor->x=(*ll);
				if ( cursor->y > columns )
					cursor->y=columns;
				else if ( cursor->y < 1 )
					cursor->y=1;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow up */
		case 'A':	if ( count1 == 0 )
					count1=1;
				cursor->x=((cursor->x)-(count1));
				if ( cursor->x < *ul )
					cursor->x=(*ul);
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow down */
		case 'B':	if ( count1 == 0 )
					count1=1;
				cursor->x=((cursor->x)+(count1));
				if ( cursor->x > *ll )
					cursor->x=(*ll);
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow right */
		case 'C':	if ( count1 == 0 )
					count1=1;
				cursor->y=((cursor->y)+(count1));
				if ( cursor->y > columns )
					cursor->y=columns;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow left */
		case 'D':	if ( count1 == 0 )
					count1=1;
				cursor->y=((cursor->y)-(count1));
				if ( cursor->y < 1 )
					cursor->y=1;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Delete characters to right of cursor; no motion */
		case 'P':	printf("\033[%dP", count1);
				fflush(stdout);
				break;
		/* Erase to the end of the line (or to the beginning, etc) */
		case 'K':	printf("\033[%dK", count1);
				fflush(stdout);
				break;
		/* Clear the screen.. Whew. */
		case 'J':	if ( count1 == 2 ) 
				{  /* Clear whole screen */
					save_cursor(win);
					vt_goto(*ul, 1);
					for ( i=(*ul); i<=(*ll); ++i )
						printf("\033[K\033[B");
					fflush(stdout);
					restor_cursor(win);
				}
				else if ( count1 == 1 )
				{  /* Clear to top */
					save_cursor(win);
					printf("\r");
					for ( i=(cursor->x); i>=(*ll); --i )
						printf("\033[K\033[A");
					fflush(stdout);
					restor_cursor(win);
				}	
				else if ( count1 == 0 )
				{  /* Clear to the bottom */
					save_cursor(win);
					printf("\r");
					for ( i=(cursor->x); i<=(*ll); ++i )
						printf("\033[K\033[B");
					fflush(stdout);
					restor_cursor(win);
				}
				break;	
		/* Set a user defined scrolling region for the window */
		case 'r':	if ( ! count1 && ! count2 )
				{  /* Unset the user scrolling region */
					if ( *scroll_1 || *scroll_2 )
					{
						*scroll_1=0;
						*scroll_2=0;
						set_wscroll(win);
					}
					vt_goto(*ul, *ll);
					break;
				}	
				*scroll_1=(*ul+(count1 ? (count1-1) : 0));
				if ( *scroll_1 > *ll )
					*scroll_1=(*ll);
				*scroll_2=(*ul+(count2 ? (count2-1) : 0));
				if ( *scroll_2 > *ll )
					*scroll_2=(*ll);

				if ( (*scroll_1 == *ul && *scroll_2 == *ll) ||
				     (*scroll_1 > *scroll_2) )
				{  /* Invalid scroll region */
					if ( *scroll_1 || *scroll_2 )
					{
						*scroll_1=0;
						*scroll_2=0;
						set_wscroll(win);
					}
					vt_goto(*ul, *ll);
					break;
				}
				/* Yay! User scroll region! */
				set_uscroll(*scroll_1, *scroll_2);
				vt_goto(*ul, *ll);
				break;
		/* Set graphics color mode */
		case 'm':	if ( count2 > 0 )
					printf("\033[%d;%dm", count1, count2);
				else if ( count1 > 0 )
					printf("\033[%dm", count1 );
				else 
					printf("\033[m");
				fflush(stdout);
				break;
		/* Request to identify vt100 terminal */
		case 'c':	printf("\033[c");
				fflush(stdout);
				break;
		/* Request hardware status */
		case 'n':	printf("\033[%dn", count1);
				fflush(stdout);
				break;
		/* LED change status (count1=0; off / count1=1; on) */
		case 'q':	printf("\033[%dq", count1);
				fflush(stdout);
				break;
		default:	break;
	}
	return;
}


/* Print out a prompt on the screen, get a character in response, and 
   return it.  (Assume a one line prompt)
*/

char vt_prompt(prompt)
char *prompt;
{
	char format[80], buff[1];

	/* Save cursor position, go home, clear line and print prompt */
	if ( prompt ) {
		sprintf(format, "\0337\033[%d;H\033[K%%.%ds", (WU_lines+1), columns);
		printf(format, prompt);
		fflush(stdout);
	}
	
	/* Read the response */
	if ( read(0, buff, 1) <= 0 )
		buff[0]='\0';

	/* Print out the top separator and go back to where we were */
	if ( prompt )
		printf("\r%s\0338", sep);
	fflush(stdout);

	return(buff[0]);
}

/* Print out information at the bottom of the screen */

void vt_info(info)
char *info;
{
	char format[80], buff[1];

	if ( info ) {
		sprintf(format, "\0337\033[%d;0H\033[K\033%%.%ds\0338", 
					(1+WU_lines+1+WL_lines+1), columns);
		printf(format, info);
	} 
	else   /* Get rid of the info message */
		printf("\0337\033[%d;0H%s\0338",(1+WU_lines+1+WL_lines+1),sep);
}


/* Clean up the screen and clear the scrolling regions */
void end_vt100()
{
	end_scroll();
	vt_goto(lines, 1);
	printf("\033[K");
	fflush(stdout);
}

/* Move the cursor to the current position on the requested window */
static int lastwin=(-1);	/* The last window we switched to */
void set_win(win)
int win;			/* The window; 0 if top, 1 if bottom */
{
	pos *loc_cursor;
	int *loc_scroll_1, *loc_scroll_2;

	if ( lastwin != win || just_init )
	{
		lastwin=win;
		if ( win == UPPER )
		{
			loc_scroll_1=(&scroll_a1);
			loc_scroll_2=(&scroll_a2);
			loc_cursor=(&c_a);
		}
		else
		{
			loc_scroll_1=(&scroll_b1);
			loc_scroll_2=(&scroll_b2);
			loc_cursor=(&c_b);
		}
		if ( *loc_scroll_1 || *loc_scroll_2 )	/* Set scrolling */
			set_uscroll(*loc_scroll_1, *loc_scroll_2);
		else
			set_wscroll(win);
		vt_goto(loc_cursor->x, loc_cursor->y);	/* Restore cursor */

		just_init=0;				/* Reset flag */
	}
}

static void vt_goto(line, col)
unsigned short line, col;
{
	printf("\033[%d;%dH", line, col);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void set_wscroll(win)
int win;		/* 0 for the top window, 1 for the bottom */
{
	if ( win == UPPER )
		printf("\033[%d;%dr", ul_a, ll_a);
	else
		printf("\033[%d;%dr", ul_b, ll_b);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void set_uscroll(start, stop)
int start;		/* The top X coordinate */
int stop;		/* The bottom X coordinate */
{
	printf("\033[%d;%dr", start, stop);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void end_scroll()
{
	printf("\033[;r");
	fflush(stdout);
}

static pos s_a={0,0}, s_b={0,0};		/* Places to save cursor */
static void save_cursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	if ( win == UPPER )
	{
		s_a.x=c_a.x;
		s_a.y=c_a.y;
	}
	else
	{
		s_b.x=c_b.x;
		s_b.y=c_b.y;
	}
}

static void restor_cursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	pos *cursor, *saved;

	if ( win == UPPER )
	{
		cursor=(&c_a);
		saved=(&s_a);
	}
	else
	{
		cursor=(&c_b);
		saved=(&s_b);
	}
	if ( saved->x == 0 )
		return;		/* No previous cursor saved */

	cursor->x=saved->x;
	cursor->y=saved->y;
	vt_goto(cursor->x, cursor->y);
}

static pos us_a={0,0}, us_b={0,0};		/* Places to save cursor */
static void save_ucursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	if ( win == UPPER )
	{
		us_a.x=c_a.x;
		us_a.y=c_a.y;
	}
	else
	{
		us_b.x=c_b.x;
		us_b.y=c_b.y;
	}
}

static void restor_ucursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	pos *cursor, *saved;

	if ( win == UPPER )
	{
		cursor=(&c_a);
		saved=(&us_a);
	}
	else
	{
		cursor=(&c_b);
		saved=(&us_b);
	}
	if ( saved->x == 0 )
		return;		/* No previous cursor saved */

	cursor->x=saved->x;
	cursor->y=saved->y;
	vt_goto(cursor->x, cursor->y);
}

static void pos_addch(position, ch)
pos *position;
char ch;
{
	vt_goto(position->x, position->y);
	write(1, &ch, 1);
}
