/*
 *  Project   : tin - a Usenet reader
 *  Module    : curses.c
 *  Author    : D.Taylor & I.Lea
 *  Created   : 01-01-86
 *  Updated   : 17-05-94
 *  Notes     : This is a screen management library borrowed with permission
 *              from the Elm mail system. This library was hacked to provide 
 *              what tin needs.
 *  Copyright : Copyright (c) 1986-94 Dave Taylor & Iain Lea
 *              The Elm Mail System  -  $Revision: 2.1 $   $State: Exp $
 */

#include "tin.h"

#ifndef ns32000
#	undef	sinix
#endif

#ifdef HAVE_ERRNO_H
#	include <errno.h>
#else
#	include <sys/errno.h>
#endif

#define DEFAULT_LINES_ON_TERMINAL	24
#define DEFAULT_COLUMNS_ON_TERMINAL	80

int cLINES = DEFAULT_LINES_ON_TERMINAL - 1;
int cCOLS  = DEFAULT_COLUMNS_ON_TERMINAL;
int inverse_okay = TRUE;
static int _inraw = FALSE;	/* are we IN rawmode?    */
int _hp_glitch = FALSE;		/* standout not erased by overwriting on HP terms */
static int xclicks=FALSE;	/* do we have an xterm? */

#ifndef INDEX_DAEMON

#define		BACKSPACE	'\b'
#define		VERY_LONG_STRING	2500

#if (defined(M_AMIGA) && !defined(__SASC)) || defined(COHERENT) || defined(BSD)
#	ifndef BSD4_1
#		include <sgtty.h>
#	else
#		include <termio.h>
#	endif
#else
#	ifndef SYSV
#		ifndef MINIX
#			ifndef QNX42
#				ifdef sinix
#					include <termios.h>
#				else
#					include <termio.h>
#				endif
#			endif
#		else
#			include <sgtty.h>
#		endif
#	else
#		if defined(__hpux) || (defined(sun) && defined(SVR4))
#			include <termio.h>
#		endif
#	endif
#endif

#define TTYIN	0

#ifdef SHORTNAMES
# define _clearinverse	_clrinv
# define _cleartoeoln	_clrtoeoln
# define _cleartoeos	_clr2eos
#endif

#if (defined(M_AMIGA) && !defined(__SASC)) || defined(BSD) || defined(MINIX)
#	ifdef TCGETA
#		undef TCGETA
#	endif
#	define TCGETA	TIOCGETP
#	ifdef TCSETAW
#		undef TCSETAW
#	endif
#	define TCSETAW	TIOCSETP
struct sgttyb _raw_tty,
	      _original_tty;
#else
#	if !defined(M_AMIGA)
#		if defined(HAVE_TERMIOS_H) || defined(sinix)
#			ifndef TCGETA
#				define TCGETA	STCGETA
#			endif
#			ifndef TCSETA
#				define TCSETAW	STCSETAW
#			endif
struct termios _raw_tty, 
              _original_tty;
#		else
#			ifndef M_OS2	
struct termio _raw_tty, 
              _original_tty;
#			endif              
#		endif
#	endif
#endif

static char *_clearscreen, *_moveto, *_cleartoeoln, *_cleartoeos,
			*_setinverse, *_clearinverse, *_setunderline, *_clearunderline,
			*_xclickinit, *_xclickend, *_cursoron, *_cursoroff,
			*_terminalinit, *_terminalend, *_keypadlocal, *_keypadxmit;

#ifdef M_AMIGA
static char *_getwinsize;
#endif

static int _columns, _line, _lines;

#ifdef M_UNIX
static char _terminal[1024];		/* Storage for terminal entry */
static char _capabilities[1024];	/* String for cursor motion */

static char *ptr = _capabilities;	/* for buffering         */

/* Linux already prototypes the termcap stuff -- and there's more than
 * one variation -- so we don't prototype them in that case.
 */
#if !defined(__GNUC__)
extern int	tgetent P_((char *, char *));	/* get termcap entry */
extern char	*tgetstr P_((char *, char **));	/* Get termcap capability (string) */
extern int	tgetflag P_((char *));			/* Get termcap capability (boolean) */
extern int	tgetnum P_((char *));			/* Get termcap capability (number) */
extern char	*tgoto P_((char *, int, int));	/* and the goto stuff    */
#endif

#endif	/* M_UNIX */

static int in_inverse;			/* 1 when in inverse, 0 otherwise */

int	outchar P_((int c));	/* char output for tputs */

#endif /* INDEX_DAEMON */


void
setup_screen ()
{
#ifndef INDEX_DAEMON
	/*
	 * get screen size from termcap entry & setup sizes
	 */
	_line = 1;
	ScreenSize (&cLINES, &cCOLS);
	cmd_line = FALSE;
	Raw (TRUE);
	set_win_size (&cLINES, &cCOLS);

#endif /* INDEX_DAEMON */
}


#ifdef M_UNIX

int
InitScreen ()
{
#ifndef INDEX_DAEMON
	char termname[40], *p;
	
	if ((p = (char *) getenv ("TERM")) == (char *) 0) {
		fprintf (stderr, "%s: TERM variable must be set to use screen capabilities\n", progname);
		return (FALSE);
	}
	if (strcpy (termname, p) == NULL) {
		fprintf (stderr,"%s: Can't get TERM variable\n", progname);
		return (FALSE);
	}
	if (tgetent (_terminal, termname) != 1) {
		fprintf (stderr,"%s: Can't get entry for TERM\n", progname);
		return (FALSE);
	}

	/* load in all those pesky values */
	_clearscreen    = tgetstr ("cl", &ptr);
	_moveto         = tgetstr ("cm", &ptr);
	_cleartoeoln    = tgetstr ("ce", &ptr);
	_cleartoeos     = tgetstr ("cd", &ptr);
	_lines          = tgetnum ("li");
	_columns        = tgetnum ("co");
	_setinverse     = tgetstr ("so", &ptr);
	_clearinverse   = tgetstr ("se", &ptr);
	_setunderline   = tgetstr ("us", &ptr);
	_clearunderline = tgetstr ("ue", &ptr);
	_hp_glitch = tgetflag ("xs");
#ifdef HAVE_BROKEN_TGETSTR
	_terminalinit   = "";
	_terminalend    = "";
	_keypadlocal    = "";
	_keypadxmit     = "";
#else
	_terminalinit   = tgetstr ("ti", &ptr);
	_terminalend    = tgetstr ("te", &ptr);
	_keypadlocal    = tgetstr ("ke", &ptr);
	_keypadxmit     = tgetstr ("ks", &ptr);
#endif
	_cursoron = NULL;
	_cursoroff = NULL;

	if (STRCMPEQ(termname, "xterm")) {
		xclicks = TRUE;
		_xclickinit	= "\033[?9h";
		_xclickend	= "\033[?9l";
	}

	InitWin ();

	if (!_clearscreen) {
		fprintf (stderr,
			"%s: Terminal must have clearscreen (cl) capability\n",progname);
		return (FALSE);
	}
	if (!_moveto) {
		fprintf (stderr,
			"%s: Terminal must have cursor motion (cm)\n", progname);
		return (FALSE);
	}
	if (!_cleartoeoln) {
		fprintf (stderr,
			"%s: Terminal must have clear to end-of-line (ce)\n", progname);
		return (FALSE);
	}
	if (!_cleartoeos) {
		fprintf (stderr,
			"%s: Terminal must have clear to end-of-screen (cd)\n", progname);
		return (FALSE);
	}
	if (_lines == -1)
		_lines = DEFAULT_LINES_ON_TERMINAL;
	if (_columns == -1)
		_columns = DEFAULT_COLUMNS_ON_TERMINAL;
	/* 
	 * kludge to workaround no inverse 
	 */
	if (_setinverse == 0) {
		_setinverse = _setunderline;
		_clearinverse = _clearunderline;
		if (_setinverse == 0)
			draw_arrow_mark = 1;
	}
	return (TRUE);

#else

	return (FALSE);

#endif /* INDEX_DAEMON */
}

#else	/* ! M_UNIX  */

int
InitScreen ()
{
#ifndef	INDEX_DAEMON

	char *ptr;

	/* 
	 * we're going to assume a terminal here... 
	 */

	_clearscreen	= "\033[1;1H\033[J";
	_moveto		= "\033[%d;%dH";	/* not a termcap string! */
	_cleartoeoln	= "\033[K";
	_setinverse	= "\033[7m";
	_clearinverse	= "\033[0m";
	_setunderline	= "\033[4m";
	_clearunderline	= "\033[0m";
	_keypadlocal	= "";
	_keypadxmit	= "";
#ifdef M_AMIGA
	_terminalinit	= "\033[12{\033[0 p";
	_terminalend	= "\033[12}\033[ p";
	_cursoron		= "\033[ p";
	_cursoroff		= "\033[0 p";
	_cleartoeos		= "\033[J";
	_getwinsize		= "\2330 q";
#endif	
#ifdef M_OS2
	_cleartoeos	= NULL;
	_terminalinit	= NULL;
	_terminalend	= "";
	initscr ();
#endif

	_lines = _columns = -1;

	/* 
	 * Get lines and columns from environment settings - useful when
	 * you're using something other than an Amiga window 
	 */
         
	if (ptr = getenv ("LINES")) {
		_lines = atol (ptr);
	}
	if (ptr = getenv ("COLUMNS")) {
		_columns = atol (ptr);
	}

	/* 
	 * If that failed, try get a response from the console itself 
	 */
#ifdef M_AMIGA
	if (_lines == -1 || _columns == -1) {
		_lines = DEFAULT_LINES_ON_TERMINAL;
		_columns = DEFAULT_COLUMNS_ON_TERMINAL;
	} else {
		_terminalinit = NULL;		/* don't do fancy things on a non-amiga console */
		_terminalend = NULL;
		_cursoroff = NULL;
		_cursoron = NULL;
		_getwinsize = NULL;
	}
#endif	/* M_AMIGA */
#ifdef M_OS2
	if (_lines == -1 || _columns == -1) {
		_lines = LINES;
		_columns = COLS;
	}
#endif	/* M_OS2 */

	InitWin ();

	Raw (FALSE);

	return (TRUE);
#else
	return (FALSE);

#endif /* INDEX_DAEMON */
}

#endif	/* M_UNIX */

/*
 *  returns the number of lines and columns on the display.
 */
 
void
ScreenSize (num_lines, num_columns)
	int *num_lines, *num_columns;
{
#ifndef INDEX_DAEMON

	if (_lines == 0) _lines = DEFAULT_LINES_ON_TERMINAL;
	if (_columns == 0) _columns = DEFAULT_COLUMNS_ON_TERMINAL;

	*num_lines = _lines - 1;		/* assume index from zero*/
	*num_columns = _columns;		/* assume index from one */

#endif /* INDEX_DAEMON */
}


void
InitWin ()
{
#ifndef INDEX_DAEMON

	if (_terminalinit) {
		tputs (_terminalinit, 1, outchar);
		fflush (stdout);
	}	
	set_keypad_on ();
	set_xclick_on ();

#endif /* INDEX_DAEMON */
}


void
EndWin ()
{
#ifndef INDEX_DAEMON

	if (_terminalend) {
		tputs (_terminalend, 1, outchar);
		fflush (stdout);
	}
	set_keypad_off ();
	set_xclick_off ();
	
	
#endif /* INDEX_DAEMON */
}


void
set_keypad_on ()
{
#ifndef INDEX_DAEMON
#    ifdef HAVE_KEYPAD
 	if (use_keypad && _keypadxmit) {
		tputs (_keypadxmit, 1, outchar);
		fflush (stdout);
	}
#    endif
#endif /* INDEX_DAEMON */
}


void
set_keypad_off ()
{
#ifndef INDEX_DAEMON
#    ifdef HAVE_KEYPAD
	if (use_keypad && _keypadlocal) {
		tputs (_keypadlocal, 1, outchar);
		fflush (stdout);
	}
#    endif
#endif /* INDEX_DAEMON */
}

/*
 *  clear the screen: returns -1 if not capable
 */

void
ClearScreen ()
{
#ifndef INDEX_DAEMON

	tputs (_clearscreen, 1, outchar);
	fflush (stdout);      /* clear the output buffer */
	_line = 1;
	
#endif /* INDEX_DAEMON */
}

/*
 *  move cursor to the specified row column on the screen.
 *  0,0 is the top left!
 */

#ifdef M_UNIX

void
MoveCursor (row, col)
	int row, col;
{
#ifndef INDEX_DAEMON

	char *stuff;

	stuff = tgoto (_moveto, col, row);
	tputs (stuff, 1, outchar);
	fflush (stdout);
	_line = row + 1;
	
#endif /* INDEX_DAEMON */
}

#else	/* ! M_UNIX */

void
MoveCursor (row, col)
	int row, col;
{
#ifndef INDEX_DAEMON

	char stuff[12], *tgoto();

	if (_moveto) {
		sprintf (stuff, _moveto, row+1, col+1);
		tputs (stuff, 1, outchar);
		fflush (stdout);
		_line = row + 1;
	}

#endif /* INDEX_DAEMON */
}

#endif	/* M_UNIX */

/*
 *  clear to end of line
 */

void
CleartoEOLN ()
{
#ifndef INDEX_DAEMON

	tputs (_cleartoeoln, 1, outchar);
	fflush (stdout);  /* clear the output buffer */

#endif /* INDEX_DAEMON */
}

/*
 *  clear to end of screen
 */

void
CleartoEOS ()
{
#ifndef INDEX_DAEMON

	int i;
	
	if (_cleartoeos) {
		tputs (_cleartoeos, 1, outchar);
	} else {
		for (i=_line - 1 ; i < _lines ; i++) {
			MoveCursor (i, 0);
			CleartoEOLN ();
		}
	}
	fflush (stdout);  /* clear the output buffer */

#endif /* INDEX_DAEMON */
}

/*
 *  set inverse video mode
 */

void
StartInverse ()
{
#ifndef INDEX_DAEMON

	in_inverse = 1;
	if (_setinverse && inverse_okay)
		tputs (_setinverse, 1, outchar);
	fflush (stdout);

#endif /* INDEX_DAEMON */
}

/*
 *  compliment of startinverse
 */

void
EndInverse ()
{
#ifndef INDEX_DAEMON

	in_inverse = 0;
	if (_clearinverse && inverse_okay)
		tputs (_clearinverse, 1, outchar);
	fflush (stdout);

#endif /* INDEX_DAEMON */
}

/*
 *  toggle inverse video mode
 */

void
ToggleInverse ()
{
#ifndef INDEX_DAEMON

	if (in_inverse == 0)
		StartInverse();
	else
		EndInverse();

#endif /* INDEX_DAEMON */
}

/*
 *  returns either 1 or 0, for ON or OFF
 */

int
RawState()
{
	return (_inraw);
}

/*
 *  state is either TRUE or FALSE, as indicated by call
 */

void
Raw (state)
	int state;
{
#if !defined(INDEX_DAEMON) && !defined(M_OS2)

#if defined(M_AMIGA) && defined(__SASC)
	_inraw = state;
	rawcon (state);
#else
	if (state == FALSE && _inraw) {
#ifdef HAVE_TCSETATTR
		(void) tcsetattr (TTYIN, TCSANOW, &_original_tty);
#else
		(void) ioctl (TTYIN, TCSETAW, &_original_tty);
#endif
		_inraw = 0;
	} else if (state == TRUE && ! _inraw) {
#ifdef HAVE_TCGETATTR
		(void) tcgetattr (TTYIN, &_original_tty);
		(void) tcgetattr (TTYIN, &_raw_tty);
#else
		(void) ioctl (TTYIN, TCGETA, &_original_tty);	/* current setting */
		(void) ioctl (TTYIN, TCGETA, &_raw_tty);	/* again! */
#endif

#if defined(BSD) || defined(M_AMIGA) || defined(MINIX)
		_raw_tty.sg_flags &= ~(ECHO | CRMOD);	/* echo off */
		_raw_tty.sg_flags |= CBREAK;		/* raw on */
#ifdef M_AMIGA	
		_raw_tty.sg_flags |= RAW; /* Manx-C 5.2 does not support CBREAK */
#endif
#else
		_raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode        */
		_raw_tty.c_cc[VMIN] = '\01';	/* minimum # of chars to queue    */
		_raw_tty.c_cc[VTIME] = '\0';	/* minimum time to wait for input */
#endif

#ifdef HAVE_TCSETATTR
		(void) tcsetattr (TTYIN, TCSANOW, &_raw_tty);
#else
		(void) ioctl (TTYIN, TCSETAW, &_raw_tty);
#endif
		_inraw = 1;
	}
#endif	/* M_AMIGA */
	
#endif /* INDEX_DAEMON */
}

/*
 *  read a character with Raw mode set!
 */

#ifdef M_OS2

int
ReadCh ()
{
#ifndef INDEX_DAEMON

	extern int errno;
	char ch;
	KBDKEYINFO os2key;
	int rc;
	register int result = 0;
	static secondkey = 0;

	if (secondkey) {
		result = secondkey;
		secondkey = 0;
	} else {
		rc = KbdCharIn((PKBDKEYINFO)&os2key, IO_WAIT, 0);
		result = os2key.chChar;
		if (result == 0xe0) {
			result = 0x1b;
			switch (os2key.chScan) {
				case 'H': 
					secondkey = 'A';
					break;
				case 'P': 
					secondkey = 'B';
					break;
				case 'K': 
					secondkey = 'D';
					break;
				case 'M': 
					secondkey = 'C';
					break;
				case 'I': 
					secondkey = 'I';
					break;
				case 'Q': 
					secondkey = 'G';
					break;
				case 'G': 
					secondkey = 'H';
					break;
				case 'O': 
					secondkey = 'F';
					break;
				default:
					secondkey = '?';
			}
		} else if (result == 0x0d) {
			result = 0x0a;
		}
	}
	return ((result == EOF) ? EOF : result & 0xFF);

#endif /* INDEX_DAEMON */
}

#endif /* M_OS2 */

#ifdef M_AMIGA

static int new_lines, new_columns;

static int
AmiReadCh (int getscrsize)
{
#ifndef INDEX_DAEMON

static unsigned char buf[128];
static int buflen = 0, bufp = 0;
	int result;
	unsigned char ch;

	while (getscrsize || buflen == 0) {
		result = read (0, (char *)&buf[buflen], 1);
		if (result <= 0) return EOF;
		buflen++;
		if (buf[bufp] == KEY_PREFIX) {
			do {
				result = read (0, (char *)&buf[buflen], 1);
				if (result <= 0) return EOF;
			} while (buf[buflen++] < 0x40);

			switch (buf[buflen-1])
			{	char *ptr;
				long class;
			  case 'r':		/* Window bounds report */
				ptr = (char *)&buf[bufp+1];
				my_strtol(ptr,&ptr,10); ptr++;
				my_strtol(ptr,&ptr,10); ptr++;
				new_lines = my_strtol(ptr,&ptr,10); ptr++;
				new_columns = my_strtol(ptr,&ptr,10);
				buflen = bufp;
				if (getscrsize) return 0;
				break;
			  case '|':		/* Raw Input Events */
				ptr = (char *)&buf[bufp+1];
				class = my_strtol(ptr,&ptr,10); ptr++;
				switch (class)
				{	int x,y;
				  case 12:	/* Window resized */
					buflen = bufp; /* Must do this before raise() */
					raise(SIGWINCH);
					break;
#ifdef notdef
				  case 2:	/* Mouse event */
					/*
					 * At this point we know what button was pressed
					 * but we don't really know where the mouse is.
					 * The <x> and <y> parameters don't help.
					 * Perhaps looking directly in the window's structure
					 * is the easiest thing to do (after finding out where
					 * the window's structure is! Sending an ACTION_INFO
					 * packet to the handler gives us this. I don't know
					 * if there is an easier way.
					 */
					buflen = bufp;
					break;
#endif
				  default:
					buflen = bufp;
					break;
				}
				break;
			}
		}
	}

	ch = buf[bufp++];
	if (bufp >= buflen) buflen = bufp = 0;
	return ch;
#else
	return -1;
#endif /* INDEX_DAEMON */
}

int
ReadCh()
{
	return AmiReadCh(0);
}

void
AmiGetWinSize(int *lines, int *columns)
{
#ifndef INDEX_DAEMON
	if (_getwinsize) {
		tputs (_getwinsize,1,outchar);	/* identify yourself */
		fflush (stdout);
		AmiReadCh(1);		/* Look for the identification */
		*lines = new_lines;
		*columns = new_columns;
	}
#endif /* INDEX_DAEMON */
}

#endif /* M_AMIGA */

#ifdef M_UNIX

int
ReadCh ()
{
#ifndef INDEX_DAEMON
	extern int errno;
	char ch;
	register int result;
	
#ifdef READ_CHAR_HACK
#undef getc
	while ((result = getc(stdin)) == EOF) {
		if (feof(stdin))
			break;

#ifdef EINTR
		if (ferror(stdin) && errno != EINTR)
#else
		if (ferror(stdin))
#endif
			break;

		clearerr(stdin);
	}

	return ((result == EOF) ? EOF : result & 0xFF);
#else
#  ifdef EINTR
	while ((result = read (0, &ch, 1)) < 0 && errno == EINTR)
		;	/* spin on signal interrupts */
#  else
	result = read (0, &ch, 1);
#  endif
        return((result <= 0 ) ? EOF : ch & 0xFF);
#endif		

#endif /* INDEX_DAEMON */
}

#endif	/* M_UNIX */

/*
 *  output a character. From tputs... (Note: this CANNOT be a macro!)
 */

int
outchar (c)
	int c;
{
	return fputc (c, stdout);
}

/*
 *  setup to monitor mouse buttons if running in a xterm
 */

void
xclick (state)
	int state;
{
#ifndef INDEX_DAEMON
	static int prev_state = 999;

	if (xclicks && prev_state != state) {
		if (state == TRUE) {
			tputs (_xclickinit, 1, outchar);
		} else {
			tputs (_xclickend, 1, outchar);
		}
		fflush (stdout);
		prev_state = state;
	}
#endif	/* INDEX_DAEMON */
}

/*
 *  switch on monitoring of mouse buttons
 */

void
set_xclick_on ()
{
	xclick (TRUE);
}

/*
 *  switch off monitoring of mouse buttons
 */

void
set_xclick_off ()
{
	xclick (FALSE);
}

void
cursoron ()
{
#ifndef INDEX_DAEMON
	if (_cursoron)
		tputs (_cursoron, 1, outchar);
#endif
}

void
cursoroff ()
{
#ifndef INDEX_DAEMON
	if (_cursoroff)
		tputs (_cursoroff, 1, outchar);
#endif
}
