/* ----------------------------------------------------------------------------
 * tw_term.c
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#ifndef _SunOS
#include <sys/ioctl.h>
#endif
#include <memory.h>
#include <string.h>
#ifdef _BSDI
#include <stdlib.h>
#else
#include <malloc.h>
#endif

#ifdef HAVE_SGTTY
#include <sgtty.h>
#endif
#ifdef HAVE_TERMIOS
#include <termios.h>
#endif

#if defined(_SunOS) || defined(_FreeBSD)
#include <sys/filio.h>
#endif

#ifdef _BSDI
#include <newcurses.h>
#endif

#ifdef _Linux
#include <termcap.h>
#endif

#include <signal.h>

#define  TC_BUFFER  1024        /* Size of termcap(3) buffer    */
char tc_buffer[TC_BUFFER];

extern char *getenv();
extern char *tgetstr();

#include "tw_term.h"
#include "mystring.h"

/* ------------------------------------------------------------------------- */
/* Dies sollte nicht hier stehen, sondern in ner lib :-( */

int strhead(s1,s2)
char *s1;
char *s2;
{	register int l;
	return((strlen(s1) >= (l=strlen(s2))) && !memcmp(s1,s2,l));
	}

/* ------------------------------------------------------------------------- */

char *tw_title;
char tw_type[64];
int tw_char_type=0;
int max_x,max_y;
struct TW_term *stdterm,*curterm;
int tw_sig_winch;
int tw_term_cleanexit=1;

/* ------------------------------------------------------------------------- */
/* Dies sollte nicht hier stehen, sondern in ner lib :-( */

#ifdef HAVE_SGTTY
#ifndef CBREAK
#define CBREAK 0
#endif

struct sgttyb   _tty;
int		_tty_ch;
short           _res_flg;

#define raw()      (_tty.sg_flags|=RAW,         stty(_tty_ch,&_tty))
#define noraw()    (_tty.sg_flags&=~RAW,        stty(_tty_ch,&_tty))
#define crmode()   (_tty.sg_flags |= CBREAK,    stty(_tty_ch,&_tty))
#define nocrmode() (_tty.sg_flags &= ~CBREAK,   stty(_tty_ch,&_tty))
#define echo()     (_tty.sg_flags |= ECHO,      stty(_tty_ch,&_tty))
#define noecho()   (_tty.sg_flags &= ~ECHO,     stty(_tty_ch,&_tty))
#define nl()       (_tty.sg_flags |= CRMOD,     stty(_tty_ch,&_tty))
#define nonl()     (_tty.sg_flags &= ~CRMOD,    stty(_tty_ch,&_tty))

#define savetty()  (gtty(_tty_ch,&_tty), _res_flg=_tty.sg_flags)
#define resetty()  (_tty.sg_flags=_res_flg, stty(_tty_ch,&_tty))
#endif

#ifdef HAVE_TERMIOS
struct termios	_tty;
int		_tty_ch;
tcflag_t        _res_iflg, _res_lflg;

#define raw()	 (_tty.c_lflag&=~(ICANON|ISIG),\
	tcsetattr(_tty_ch, TCSANOW, &_tty))

#define noraw()	 (_tty.c_lflag|=ISIG, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))

/* cbreak is like raw, but without turning off signals. */

#define cbreak() (_tty.c_lflag&=~ICANON, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))

#define nocbreak() (_tty.c_lflag |= ICANON, _tty.c_lflag &= ~ISIG, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))

#define crmode() cbreak()	/* backwards compatability */
#define nocrmode() nocbreak()	/* backwards compatability */

#define echo()	 (_tty.c_lflag |= ECHO, \
	tcsetattr(_tty_ch, TCSADRAIN, &_tty))
#define noecho() (_tty.c_lflag &= ~ECHO, \
	tcsetattr(_tty_ch, TCSADRAIN, &_tty))
#define nl()	 (_tty.c_iflag |= ICRNL, \
	_tty.c_oflag |= ONLCR, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))
#define nonl()	 (_tty.c_iflag &= ~ICRNL, \
	_tty.c_oflag &= ~ONLCR, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))

#define	savetty() ((void) tcgetattr(_tty_ch, &_tty), \
	_res_iflg = _tty.c_iflag, _res_lflg = _tty.c_lflag, _tty.c_cc[VMIN] = 1)
#define	resetty() (_tty.c_iflag = _res_iflg, _tty.c_lflag = _res_lflg,\
	(void) tcsetattr(_tty_ch, TCSADRAIN, &_tty))
#endif

/* ------------------------------------------------------------------------- */

#ifdef TIOCGWINSZ
void tw_term_getsize()
{       struct winsize w;

        if ((ioctl(2, TIOCGWINSZ, &w) == 0) && 
            (w.ws_row > 0) && (w.ws_col > 0)) {
                max_y = w.ws_row;
                max_x = w.ws_col;
                }
        else    {
                max_y = 24; max_x=80;
                }
	tw_sig_winch = 0;
        }
#else
void tw_term_getsize()
{       max_x = 80; max_y=24;
	tw_sig_winch = 0;
        }
#endif

#ifdef SIGWINCH
void tw_winch(int type)
{	signal(SIGWINCH, tw_winch);
	tw_sig_winch = 1;
	}
#endif
	
/* ------------------------------------------------------------------------- */

void tw_term_on()
{       raw();
        crmode();
        noecho();
        tw_term_getsize();
        }

void tw_term_off()
{       noraw();
        nocrmode();
        echo();
#ifdef M_XENIX
        system("stty -istrip"); /* Dummer Patch fuer Xenix */
#endif
        }

/* ------------------------------------------------------------------------- */

struct TC_keys {
	char *tc_up;
	char *tc_down;
	char *tc_left;
	char *tc_right;
	char *tc_del;
	char *tc_mouse;

	char *tc_k1;
	char *tc_k2;
	char *tc_k3;
	char *tc_k4;
	char *tc_k5;
	char *tc_k6;
	char *tc_k7;
	char *tc_k8;
	char *tc_k9;
	char *tc_k0;

	char *tc_pgup;
	char *tc_pgdn;
	char *tc_pgbeg;
	char *tc_pgend;
	} tc_keys;

void tc_key(id,s)
char *id;
char **s;
{	char  entry[ 50 ];
	char *p = entry;

	if (tgetstr(id,&p) == NULL) {
		p=0; *s=0;
		/*
		fprintf(stderr,"key %s not found\n",id);
		*/
		return;
		}

	*s=stralloc(strlen(entry)+1);
	strcpy(*s,entry);
	/*
	fprintf(stderr,"key %s found\n",id);
	*/
	}

void tc_out(str)
unsigned char *str;
{	fputs((char*)str,stdout);
	}

void tc_put(id)
char *id;
{	char  entry[ 50 ];
	char *p = entry;

	if (tgetstr(id,&p) == NULL) return;

	tc_out(entry);
	}

void tc_goto(x,y)
int x,y;
{       char  entry[ 50 ];
        char *p = entry;

	if (tgetstr("cm",&p) == NULL) return;

	tc_out(tgoto(entry,x,y));
	}

int tc_a;
void tc_attr(attr)
int attr;
{	if (tc_a && (tc_a != attr)) tc_put("me");
	if (attr & TW_BOLD) tc_put("md");
	if (attr & TW_UNDER) tc_put("us");
	if (attr & TW_INVERS) tc_put("so");
	tc_a = attr;
	}

void tc_init()
{	if (tgetent(tc_buffer, tw_type) != 1) {
		fprintf(stderr,"termcap:  No termcap entry for %s\n", tw_type);
		exit(0);
		}

	tc_put("ti"); tc_goto(0,0);
	tc_a=TW_BOLD | TW_UNDER | TW_INVERS;
	tc_attr(TW_NORMAL);
	tc_put("cl"); 

	tc_key("ku",&tc_keys.tc_up);
	tc_key("kd",&tc_keys.tc_down);
	tc_key("kl",&tc_keys.tc_left);
	tc_key("kr",&tc_keys.tc_right);
	tc_key("kD",&tc_keys.tc_del);

	tc_key("k1",&tc_keys.tc_k1);
	tc_key("k2",&tc_keys.tc_k2);
	tc_key("k3",&tc_keys.tc_k3);
	tc_key("k4",&tc_keys.tc_k4);
	tc_key("k5",&tc_keys.tc_k5);
	tc_key("k6",&tc_keys.tc_k6);
	tc_key("k7",&tc_keys.tc_k7);
	tc_key("k8",&tc_keys.tc_k8);
	tc_key("k9",&tc_keys.tc_k9);
	tc_key("k0",&tc_keys.tc_k0);

	tc_key("kN",&tc_keys.tc_pgup);
	if (!tc_keys.tc_pgup) tc_keys.tc_pgup=strdup("\033[5~");
	tc_key("kP",&tc_keys.tc_pgdn);
	if (!tc_keys.tc_pgdn) tc_keys.tc_pgdn=strdup("\033[6~");

	if (!strcmp(tw_type,"xterm")) {
		tw_char_type=1;
		/* fprintf(stdout,"\033[?9h"); */                 /* Maus-On */
		fprintf(stdout,"\033[?1000h");                    /* Maus-On */
		tc_keys.tc_mouse = "\033[M";
		fprintf(stdout,"\033[?1h");     /* Application-Cursor-KeyPad */
		}
	else {	tc_keys.tc_mouse = 0;
		tw_char_type=0;
		}
#ifdef SIGNWINCH
	signal(SIGWINCH, tw_winch);
#endif
	}

void tc_exit()
{       if (tw_term_cleanexit) {
		tc_goto(0,0); tc_put("cl"); tc_put("me"); 
		}
        if (!strcmp(tw_type,"xterm")) {
                /* fprintf(stdout,"\033[?9l"); */                /* Maus-Off */
		fprintf(stdout,"\033[?1000l");                   /* Maus-Off */
		}
	}

/* ------------------------------------------------------------------------- */

void tw_term_open()
{       int i;

	_tty_ch = fileno(stdin);
        savetty();

        strcpy(tw_type,getenv("TERM"));

        tw_term_on();

	stdterm = (struct TW_term *)stralloc(sizeof(struct TW_term));
	stdterm->x = stdterm->y =0;
	stdterm->c = (char **)stralloc(sizeof(char *)*MAX_Y);
	stdterm->a = (char **)stralloc(sizeof(char *)*MAX_Y);

	curterm = (struct TW_term *)stralloc(sizeof(struct TW_term));
	curterm->x = curterm->y =0;
	curterm->c = (char **)stralloc(sizeof(char *)*MAX_Y);
	curterm->a = (char **)stralloc(sizeof(char *)*MAX_Y);

	for (i=0;i < MAX_Y; i++) {
		stdterm->c[i] = (char *)stralloc(MAX_X); memset(stdterm->c[i],' ',MAX_X);
		stdterm->a[i] = (char *)stralloc(MAX_X); memset(stdterm->a[i],0,MAX_X);
		curterm->c[i] = (char *)stralloc(MAX_X); memset(curterm->c[i],' ',MAX_X);
		curterm->a[i] = (char *)stralloc(MAX_X); memset(curterm->a[i],0,MAX_X);
		}

	tc_init();
        }

void tw_term_close()
{       tc_exit();
	tw_term_off();

	free(stdterm); free(curterm);
        }

void tw_term_name(char *name)
{	if (!strcmp(tw_type,"xterm"))
                fprintf(stdout,"\033]0;%s%c",name,7);
	}

/* ------------------------------------------------------------------------- */

void tw_term_dirty()
{	int i;

        for (i=0;i < MAX_Y; i++) {
                memset(curterm->c[i],' ',MAX_X);
                memset(curterm->a[i],0,MAX_X);
                }
	
        tc_put("ti"); tc_goto(0,0);
        tc_a=TW_BOLD | TW_UNDER | TW_INVERS;
        tc_attr(TW_NORMAL);
        tc_put("cl"); 
	}

/* ------------------------------------------------------------------------- */

void tw_out_xterm(str)
unsigned char *str;
{	int flag = 0;
	unsigned char *p;

	p=str; while (*p) {
		if ((*p>=0x80) && (*p <= 0x9f)) {
			if (!flag) fputs("\033(0",stdout);
			fputc((*p)-0x20,stdout);
			flag=1;
			}
		else {	if (flag) fputs("\033(B",stdout);
			fputc(*p,stdout);
			flag=0;
			}
		p++;
		}
	if (flag) fputs("\033(B",stdout);
	}

void tw_out_iso(str)
unsigned char *str;
{	unsigned char *p;

	p=str; while (*p) {
		if ((*p>=0x80) && (*p <= 0x9f)) {
			if (*p == 'x'+0x20)
				fputc('|',stdout);
			else	fputc('-',stdout);
			}
		else {	
			fputc(*p,stdout);
			}
		p++;
		}
	}	

void tw_out(str)
unsigned char *str;
{	if (tw_char_type == 1)
		tw_out_xterm(str);
	else	tw_out_iso(str);
	}

/* ------------------------------------------------------------------------- */

void tw_term_cursor(x,y)
int x,y;
{	stdterm->x =x;
	stdterm->y =y;
	}

void tw_term_puts(x,y,attr,str)
int x;
int y;
char attr;
char *str;
{	int l;

	if (x>=MAX_X) return;
	if (y>=MAX_Y) return;

	l = strlen(str); if (l+x>=MAX_X) l=(MAX_X-x)-1;

	memcpy(stdterm->c[y]+x,str,l);
	memset(stdterm->a[y]+x,attr,l);
	}

void tw_term_set(x,y,attr,l)
int x,y;
char attr;
int l;
{	if (x>=MAX_X) return;
	if (y>=MAX_Y) return;

	if (l+x>=MAX_X) l=(MAX_X-x)-1;

	memset(stdterm->c[y]+x,' ',l);
	memset(stdterm->a[y]+x,attr,l);
	}

/* ------------------------------------------------------------------------- */

void tw_term_refresh()
{	int x,y;

        char attr=0;
        char str[129],*p;

        char *ca,*cc,*sa,*sc;

	x = max_x; y = max_y;
	tw_term_getsize();
	if ((x != max_x) && (y != max_y)) {
		tw_term_dirty();
		}

	for (y=0; y<max_y; y++) {
            x=0;
            sa=(stdterm->a[y])+x;
            sc=(stdterm->c[y])+x;
            ca=(curterm->a[y])+x;
            cc=(curterm->c[y])+x;

            while(x<max_x)
            if ((*sa != *ca) || (*sc != *cc)) {
                attr = *sa;
                tc_goto(x,y); tc_attr(attr);
                memset(str,0,129); p=str;

                while (((*sc!=*cc) || (*sa!=*ca)) && (*sa == attr)) {
                        *ca = *sa;
                        *cc = *sc;

                        if (x<max_x) *p++= *sc;
                        else         break;

                        x++; sa++; ca++; cc++; sc++;
                        }

                tw_out(str);
                }
            else {      x++; sa++; ca++; cc++; sc++; }
	    }

	curterm->x = stdterm->x;
	curterm->y = stdterm->y;

	tc_goto(stdterm->x,stdterm->y);
	fflush(stdout);
	}

/* ------------------------------------------------------------------------- */

void tw_term_error(int pos,char *s1, char *s2)
{	tc_goto(0,(max_y-1)-pos); tc_put("me");
	fprintf(stdout,s1,s2); 
	tc_put("ce");
	fflush(stdout);
	}

int read_alarm_flag=0;

void read_alarm()
{	read_alarm_flag++;
	}

unsigned char read_sequence[16];
unsigned char *read_p=0;
unsigned char *read_q=0;

void read_reset()
{	unsigned char buff[16];
	unsigned char *p;

	if (!read_p)
		memset(read_sequence,0,16);
	else {	memset(buff,0,16);
		p=buff;
		while (read_p > read_q) {
			*p++=*read_q++;
			}
		memcpy(read_sequence,buff,16);
		}
	read_p=read_sequence;
	read_q=read_sequence;
	}

void read_consume(n)
int n;
{	read_q+=n;
	if (read_q>read_p) read_q=read_p;
	}

int read_load(int checkit)
{	unsigned char ch;
	int ret;

	if (read_p >= read_sequence + 16) return(0);

	read_alarm_flag=0;
	
	if (checkit) {
		signal(SIGALRM,read_alarm);
#ifndef _Linux
		ualarm(300000,0);
#else
		alarm(1);
#endif
		}

	if (read(0, &ch, 1) <=0) ch=0;

	if (checkit) {
		alarm(0);
		signal(SIGALRM,SIG_IGN);
		}

	if (read_alarm_flag) ch=0;

	if (ch && (read_p < read_sequence + 16)) {
		*read_p++=ch;
		}

	ret = ch;
	if (ret <0) ret+=256;

	return(ret);
	}

int read_char()
{	int ret;

	if (read_q<read_p) 
		ret=*read_q++;
	else	ret=read_load(0);

	if (ret <0) ret+=256;

	return (ret);
	}

/* ----------------------------------------------------------------------------
 * tw_getch()
 */

#define KEY_CHK(TC,TERM)	\
	if (tc_keys.TC && strlen(tc_keys.TC) && \
	    !memcmp(tc_keys.TC,read_sequence,strlen(tc_keys.TC))) { \
		ret = TERM; \
		read_consume(strlen(tc_keys.TC)-1); \
		break; \
		}
		

int tw_term_getch()
{       unsigned char ch;
	int ret=0;

	read_reset();

	while (!ret) {
        	ch = read_char();
        	switch (ch) {
		case TERM_ESC :
			if (!read_load(1))
				ret = TERM_ESC;
			else do {
				KEY_CHK(tc_up,TERM_UP)
				else
				KEY_CHK(tc_down,TERM_DN)
				else
				KEY_CHK(tc_left,TERM_LE)
				else
				KEY_CHK(tc_right,TERM_RE)
				else
				KEY_CHK(tc_mouse,TERM_MAUS1)
				else
				KEY_CHK(tc_k1,TERM_F1)
				else
				KEY_CHK(tc_k2,TERM_F2)
				else
				KEY_CHK(tc_k3,TERM_F3)
				else
				KEY_CHK(tc_k4,TERM_F4)
				else
				KEY_CHK(tc_k5,TERM_F5)
				else
				KEY_CHK(tc_k6,TERM_F6)
				else
				KEY_CHK(tc_k7,TERM_F7)
				else
				KEY_CHK(tc_k8,TERM_F8)
				else
				KEY_CHK(tc_k9,TERM_F9)
				else
				KEY_CHK(tc_k0,TERM_F10)
				else
				KEY_CHK(tc_pgup,TERM_PGUP)
				else
				KEY_CHK(tc_pgdn,TERM_PGDN)
				} while(read_load(1));
			break;
		case TERM_BS  :
			ret = TERM_BS;
			break;
		case TERM_TAB :
			ret = TERM_TAB;
			break;
		case '\n'     :
		case '\r'     :
			ret = TERM_CR;
			break;
		case 127      :
			ret = TERM_DEL;
			break;
		case 12:
			ret = ch;
			break;
		default       :
			if (ch >= 32) ret = ch;
			break;
			}
		}
	if (ret == TERM_MAUS1) {
		read_load(1);
		read_load(1);
		read_load(1);
		ret = read_sequence[3]%4 + TERM_MAUS1;
		curterm->x = read_sequence[4] -33;
		curterm->y = read_sequence[5] -33;
		}
	return(ret);
	}
