/* terminal.c - terminal handling routines
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif				/* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#ifdef HAVE_LIBTERMCAP
#include <termcap.h>
#endif				/* HAVE_LIBTERMCAP */
#include <ctype.h>
#include <time.h>
#include "terminal.h"

#define M_ML_TERMTYPE	32		    /* max length of a terminal type */

int t_cols = 80;			 /* number of columns terminal has */
int t_rows = 25;			 /* number of rows terminal has */

char t_clearscreen = 0;			 /* clear-screen-now flag */
char t_quit_input = 0;			 /* quit-input-now flag */

char PC;				 /* for tputs() */
char *BC;				 /* for tgoto() */
char *UP;				 /* for tgoto() */
char *t_str_ce = "\033[K";		 /* clear to end of line */
char *t_str_cl = "\033[H\033[J";	 /* clear screen and home cursor */
char *t_str_cm = "\033[%i%d;%dH";	 /* move to row %1, column %2 */
char *t_str_se = "\033[m";		 /* end standout mode */
char *t_str_so = "\033[7m";		 /* standout mode */
char *t_str_ve = "\033[?25h";		 /* cursor visible */
char *t_str_vi = "\033[?25l";		 /* cursor invisible */
char *t_str_vb = "\033[?5h\033[?5l";	 /* visible bell */
char *t_str_cs = "\033[%i%d;%dr";	 /* set scrolling region */


/* Autosize code which attempts to get the user's terminal size without
 * using termcap. Switches off echo and canonical input first.
 */
void t_autosize(int *xsize, int *ysize)
{
	char readbuf[2];
	char buffer[128];
	int buflen, result, respnum, origx, origy, maxx, maxy;
	int nread, x, y, c;

	t_echo_off();
	t_canon_off();

	buffer[0] = 0;
	buflen = 0;
	result = 0;
	respnum = 0;
	origx = 0;
	origy = 0;
	maxx = 0;
	maxy = 0;

	write(fileno(stdout), "Screen size...\e[6n", 18);
	write(fileno(stdout), " [Q]:Quit", 9);
	write(fileno(stdout), "\e[0;0r\e[24;80H\e[999;999H\e[6n", 28);

	while (result == 0) {
		nread = read(fileno(stdin), readbuf, 1);
		readbuf[1] = 0;
		if (nread < 1) {
			result = 3;
		} else {
			c = readbuf[0];
			strcat(buffer, readbuf);
			buflen++;
			if (buflen >= (sizeof(buffer) - 4)) {
				buffer[0] = 0;
				buflen = 0;
			}
			if ((c == 'Q') || (c == 'q')) {
				result = 2;
			} else if ((c == 'R') && (strstr(buffer, "\e["))) {
				x = 0;
				y = 0;
				sscanf(buffer, "\e[%d;%dR", &y, &x);
				buffer[0] = 0;
				buflen = 0;
				if (x > maxx)
					maxx = x;
				if (y > maxy)
					maxy = y;
				respnum++;
				if (respnum == 1) {
					origx = x;
					origy = y;
				} else if (respnum == 2) {
					result = 1;
				}
			}
		}
	}

	if ((maxx == 0) || (maxy == 0)) {
		if (result == 1)
			result = 3;
		maxx = 80;
		maxy = 24;
	} else {
		*xsize = maxx;
		*ysize = maxy;
	}

	sprintf(buffer, "\e[0;%dr\e[%dHScreen size... (%d:%d %s)\n", maxy,
		origy, x, y,
		(result == 1) ? "OK" : (result == 2) ? "Quit" : "Unknown");
	write(fileno(stdout), buffer, strlen(buffer));
}


/* Attempt to determine the user's terminal size by using the termcap
 * library or reading the environment variables COLUMNS and LINES; if these
 * fail, the default is 80x24.
 *
 * Returns non-zero on failure.
 */
int t_init(char *t)
{
	static char *buffer = 0;
#define BUFFADDR &buffer
	char *p;
#ifdef HAVE_LIBTERMCAP
	static char term_buffer[2048];
	int a;
#endif				/* HAVE_LIBTERMCAP */

	if (!buffer)
		buffer = (char *) malloc(2048);

	t_cols = 0;
	t_rows = 0;

#ifdef HAVE_LIBTERMCAP

	if ((!t) || (tgetent(term_buffer, t) < 0)) {
		fprintf(stderr, "terminal type '%s' unknown\n\r", t);
		return (1);
	}

	a = tgetnum("co");
	if (a > 0)
		t_cols = a;
	a = tgetnum("li");
	if (a > 0)
		t_rows = a;

	p = tgetstr("pc", BUFFADDR);
	if (p)
		PC = *p;
	else
		PC = 0;

	BC = tgetstr("le", BUFFADDR);
	UP = tgetstr("up", BUFFADDR);

	p = tgetstr("ce", BUFFADDR);
	if (p)
		t_str_ce = p;
	p = tgetstr("cl", BUFFADDR);
	if (p)
		t_str_cl = p;
	p = tgetstr("cm", BUFFADDR);
	if (p)
		t_str_cm = p;
	p = tgetstr("se", BUFFADDR);
	if (p)
		t_str_se = p;
	p = tgetstr("so", BUFFADDR);
	if (p)
		t_str_so = p;
	p = tgetstr("ve", BUFFADDR);
	if (p)
		t_str_ve = p;
	p = tgetstr("vi", BUFFADDR);
	if (p)
		t_str_vi = p;
	p = tgetstr("vb", BUFFADDR);
	if (p)
		t_str_vb = p;
	p = tgetstr("cs", BUFFADDR);
	if (p)
		t_str_cs = p;

	t_sighandler();			    /* initialise SIGWINCH handler */

	t_abswrite(tgoto(t_str_cs, t_rows - 1, 0));	/* set scrolling region */

#endif				/* HAVE_LIBTERMCAP */

	if ((t_cols == 0) || (t_rows == 0)) {
		t_autosize(&t_cols, &t_rows);
	}

	if ((t_cols == 0) || (t_rows == 0)) {
		p = getenv("COLUMNS");
		if (p)
			t_cols = atoi(p);
		p = getenv("LINES");
		if (p)
			t_rows = atoi(p);
	}

	if ((t_cols == 0) || (t_rows == 0)) {
		t_cols = 80;
		t_rows = 24;
	}

	if (t_cols > 1000)
		t_cols = 1000;		    /* no buffer overflows please */

	return (0);
}


/* Turn off echoing of input characters.
 */
void t_echo_off(void)
{
	struct termios attr;
	if (tcgetattr(STDIN_FILENO, &attr) != 0)
		return;
	attr.c_lflag &= ~(ECHO);
	tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr);
}


/* Turn on echoing of input characters.
 */
void t_echo_on(void)
{
	struct termios attr;
	if (tcgetattr(STDIN_FILENO, &attr) != 0)
		return;
	attr.c_lflag |= ECHO;
	tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr);
}

int t__canon_odisc = 0;

/* Turn off canonical input mode.
 */
void t_canon_off(void)
{
	struct termios attr;
	if (tcgetattr(STDIN_FILENO, &attr) != 0)
		return;
	attr.c_lflag &= ~(ICANON);
#ifdef VDISCARD
	t__canon_odisc = attr.c_cc[VDISCARD];
	attr.c_cc[VDISCARD] = 0;
#endif				/* VDISCARD */
	tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr);
}


/* Turn on canonical input mode.
 */
void t_canon_on(void)
{
	struct termios attr;
	if (tcgetattr(STDIN_FILENO, &attr) != 0)
		return;
	attr.c_lflag |= ICANON;
#ifdef VDISCARD
	attr.c_cc[VDISCARD] = t__canon_odisc;
#endif				/* VDISCARD */
	tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr);
	return;
}


/* Output sequence of characters to clear the screen.
 */
void t_clear(void)
{
	t_abswrite(t_str_cl);
}


/* Output sequence of characters to move to (x,y) - (0,0) is top left.
 */
void t_goto(int x, int y)
{

#ifdef HAVE_LIBTERMCAP
	t_abswrite(tgoto(t_str_cm, x, y));
#else				/* ! HAVE_LIBTERMCAP */
	static char b[16];

	sprintf(b, "\033[%d;%dH", y + 1, x + 1);
	t_abswrite(b);
#endif				/* HAVE_LIBTERMCAP */
}


/* Output sequence of characters to clear to the end of the line.
 */
void t_clrtoeol(void)
{
	t_abswrite(t_str_ce);
}


/* Scroll screen up one line.
 */
void t_scrollup(void)
{
	t_abswrite("\033[H\033M");	    /* use termcap entry? */
}


/* Write "string" directly, no fiddling about.
 */
void t_abswrite(char *string)
{
	write(STDOUT_FILENO, string, strlen(string));
}


/* Read a character with timeout.
 */
int t_getchar(int timeout)
{
	struct timeval tv;
	fd_set readfds;

	if (feof(stdin) || ferror(stdin))
		clearerr(stdin);

	FD_ZERO(&readfds);
	FD_SET(STDIN_FILENO, &readfds);
	tv.tv_sec = timeout;
	tv.tv_usec = 0;

	select(FD_SETSIZE, &readfds, 0, 0, &tv);

	if (FD_ISSET(STDIN_FILENO, &readfds))
		return (t_getch());

	return (KEY_NONE);
}

/* EOF */
