/*
    TTY interface file
    Copyright (c) Tudor Hulubei & Andrei Pitis, April 1994

This file is part of UIT (UNIX Interactive Tools)

UIT is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2, or (at your option) any later version.

UIT is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details .

You should have received a copy of the GNU General Public License along with
UIT; see the file COPYING.  If not, write to the Free Software Foundation,
675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include "tty.h"
#include "termcap.h"


#define INPUT  	0
#define OUTPUT	1

static struct termios oldterm;
static struct termios newterm;

static int terminal;
static int tty_perm;
static int rows, columns;
static int fg_color, bg_color, br_status, rv_status;

extern int LinuxConsole;

void fatal(char *);


static char colors[10][10] = 
{
    "BLACK",
    "RED",
    "GREEN",
    "YELLOW",
    "BLUE",
    "MAGENTA",
    "CYAN",
    "WHITE",
    "OFF",
    "ON"
};


extern char termcap[TTY_MAXCAP][16];
extern char termcap_len[TTY_MAXCAP];


#define NO	0
#define YES	1


#define TTY_KEYS	191
#define TTY_FIRST_TTY_INDEPENDENT_KEY	TTY_TAB


char kbdmap[TTY_KEYS][16] =
{
    "",				/* dummy */

    /* Not configurable terminal-dependent keys */
    
    "",				/* Backspace    */

    /* Configurable terminal-dependent keys */

    "",				/* Cursor left  */
    "",				/* Cursor right */
    "",				/* Cursor up    */
    "",				/* Cursor down  */
    "",				/* Home      */
    "",				/* End       */
    "",				/* Ins       */
    "",				/* Del       */
    "",				/* Page Up   */
    "",				/* Page Down */
    "",				/* F1  */
    "",				/* F2  */
    "",				/* F3  */
    "",				/* F4  */
    "",				/* F5  */
    "",				/* F6  */
    "",				/* F7  */
    "",				/* F8  */
    "",				/* F9  */
    "",				/* F10 */
    
    /* Configurable terminal-independent keys */
    
    "\11",			/* Tab */
    "\1",			/* ^a  */
    "\2",			/* ^b  */
    "\4\141",			/* ^da */
    "\4\142",			/* ^db */
    "\4\143",			/* ^dc */
    "\4\144",			/* ^dd */
    "\4\145",			/* ^de */
    "\4\146",			/* ^df */
    "\4\147",			/* ^dg */
    "\4\150",			/* ^dh */
    "\4\151",			/* ^di */
    "\4\152",			/* ^dj */
    "\4\153",			/* ^dk */
    "\4\154",			/* ^dl */
    "\4\155",			/* ^dm */
    "\4\156",			/* ^dn */
    "\4\157",			/* ^do */
    "\4\160",			/* ^dp */
    "\4\161",			/* ^dq */
    "\4\162",			/* ^dr */
    "\4\163",			/* ^ds */
    "\4\164",			/* ^dt */
    "\4\165",			/* ^du */
    "\4\166",			/* ^dv */
    "\4\167",			/* ^dw */
    "\4\170",			/* ^dx */
    "\4\171",			/* ^dy */
    "\4\172",			/* ^dz */
    "\5",			/* ^e  */
    "\6",			/* ^f  */
    "\7",			/* ^g  */
    "\13\141",			/* ^ka */
    "\13\142",			/* ^kb */
    "\13\143",			/* ^kc */
    "\13\144",			/* ^kd */
    "\13\145",			/* ^ke */
    "\13\146",			/* ^kf */
    "\13\147",			/* ^kg */
    "\13\150",			/* ^kh */
    "\13\151",			/* ^ki */
    "\13\152",			/* ^kj */
    "\13\153",			/* ^kk */
    "\13\154",			/* ^kl */
    "\13\155",			/* ^km */
    "\13\156",			/* ^kn */
    "\13\157",			/* ^ko */
    "\13\160",			/* ^kp */
    "\13\161",			/* ^kq */
    "\13\162",			/* ^kr */
    "\13\163",			/* ^ks */
    "\13\164",			/* ^kt */
    "\13\165",			/* ^ku */
    "\13\166",			/* ^kv */
    "\13\167",			/* ^kw */
    "\13\170",			/* ^kx */
    "\13\171",			/* ^ky */
    "\13\172",			/* ^kz */
    "\14",			/* ^l  */
    "\16\141",			/* ^na */
    "\16\142",			/* ^nb */
    "\16\143",			/* ^nc */
    "\16\144",			/* ^nd */
    "\16\145",			/* ^ne */
    "\16\146",			/* ^nf */
    "\16\147",			/* ^ng */
    "\16\150",			/* ^nh */
    "\16\151",			/* ^ni */
    "\16\152",			/* ^nj */
    "\16\153",			/* ^nk */
    "\16\154",			/* ^nl */
    "\16\155",			/* ^nm */
    "\16\156",			/* ^nn */
    "\16\157",			/* ^no */
    "\16\160",			/* ^np */
    "\16\161",			/* ^nq */
    "\16\162",			/* ^nr */
    "\16\163",			/* ^ns */
    "\16\164",			/* ^nt */
    "\16\165",			/* ^nu */
    "\16\166",			/* ^nv */
    "\16\167",			/* ^nw */
    "\16\170",			/* ^nx */
    "\16\171",			/* ^ny */
    "\16\172",			/* ^nz */
    "\17",			/* ^o  */
    "\20\141",			/* ^pa */
    "\20\142",			/* ^pb */
    "\20\143",			/* ^pc */
    "\20\144",			/* ^pd */
    "\20\145",			/* ^pe */
    "\20\146",			/* ^pf */
    "\20\147",			/* ^pg */
    "\20\150",			/* ^ph */
    "\20\151",			/* ^pi */
    "\20\152",			/* ^pj */
    "\20\153",			/* ^pk */
    "\20\154",			/* ^pl */
    "\20\155",			/* ^pm */
    "\20\156",			/* ^pn */
    "\20\157",			/* ^po */
    "\20\160",			/* ^pp */
    "\20\161",			/* ^pq */
    "\20\162",			/* ^pr */
    "\20\163",			/* ^ps */
    "\20\164",			/* ^pt */
    "\20\165",			/* ^pu */
    "\20\166",			/* ^pv */
    "\20\167",			/* ^pw */
    "\20\170",			/* ^px */
    "\20\171",			/* ^py */
    "\20\172",			/* ^pz */
    "\22",			/* ^r  */
    "\24",			/* ^t  */
    "\25",			/* ^u  */
    "\26\141",			/* ^va */
    "\26\142",			/* ^vb */
    "\26\143",			/* ^vc */
    "\26\144",			/* ^vd */
    "\26\145",			/* ^ve */
    "\26\146",			/* ^vf */
    "\26\147",			/* ^vg */
    "\26\150",			/* ^vh */
    "\26\151",			/* ^vi */
    "\26\152",			/* ^vj */
    "\26\153",			/* ^vk */
    "\26\154",			/* ^vl */
    "\26\155",			/* ^vm */
    "\26\156",			/* ^vn */
    "\26\157",			/* ^vo */
    "\26\160",			/* ^vp */
    "\26\161",			/* ^vq */
    "\26\162",			/* ^vr */
    "\26\163",			/* ^vs */
    "\26\164",			/* ^vt */
    "\26\165",			/* ^vu */
    "\26\166",			/* ^vv */
    "\26\167",			/* ^vw */
    "\26\170",			/* ^vx */
    "\26\171",			/* ^vy */
    "\26\172",			/* ^vz */
    "\27\141",			/* ^wa */
    "\27\142",			/* ^wb */
    "\27\143",			/* ^wc */
    "\27\144",			/* ^wd */
    "\27\145",			/* ^we */
    "\27\146",			/* ^wf */
    "\27\147",			/* ^wg */
    "\27\150",			/* ^wh */
    "\27\151",			/* ^wi */
    "\27\152",			/* ^wj */
    "\27\153",			/* ^wk */
    "\27\154",			/* ^wl */
    "\27\155",			/* ^wm */
    "\27\156",			/* ^wn */
    "\27\157",			/* ^wo */
    "\27\160",			/* ^wp */
    "\27\161",			/* ^wq */
    "\27\162",			/* ^wr */
    "\27\163",			/* ^ws */
    "\27\164",			/* ^wt */
    "\27\165",			/* ^wu */
    "\27\166",			/* ^wv */
    "\27\167",			/* ^ww */
    "\27\170",			/* ^wx */
    "\27\171",			/* ^wy */
    "\27\172",			/* ^wz */
    "\30",			/* ^x  */
    "\31",			/* ^y  */
};   
     

static int keyno = 0;
static int keyindex = 0;
static char keybuf[1024];


void tty_init(void)
{    
    tty_defaults();

    if ((terminal = open("/dev/tty", O_RDWR)) == -1)
	if ((terminal = open("/dev/console", O_RDWR)) == -1)
	    fatal("neither /dev/tty nor /dev/console can be opened\n");

    tcgetattr(terminal, &oldterm);
    newterm = oldterm;
    newterm.c_iflag &= ~(ICRNL | IGNCR | INLCR | IGNBRK | BRKINT);
    newterm.c_oflag &= ~OPOST;
    newterm.c_lflag |= ISIG | NOFLSH;
    newterm.c_lflag &= ~(ICANON | ECHO);
    newterm.c_cc[VMIN]  = 1;
    newterm.c_cc[VTIME] = 0;
    tcsetattr(terminal, TCSADRAIN, &newterm);
}


void tty_end(void)
{
    tcsetattr(terminal, TCSADRAIN, &oldterm);
    tty_defaults();
    close(terminal);
}


void tty_kbdinit(void)
{
    int i;
    
    for (i = TTY_BACKSPACE; i <= TTY_FIRST_TTY_INDEPENDENT_KEY; i++)
	strcpy(kbdmap[i], termcap[i]);
}


void tty_clrscr(void)
{
    write(OUTPUT, termcap[TTY_CLRSCR], termcap_len[TTY_CLRSCR]);
}


void tty_cursormove(int y, int x)
{
    char msg[10];
    
    sprintf(msg, "\33[%d;%dH", y + 1, x + 1);
    write(OUTPUT, msg, strlen(msg));
}


void tty_foreground(int color)
{
#ifdef HAVE_LINUX
    char str[] = "\x1b\x5b\x33\x30\x6d";

    if (LinuxConsole)
    {
	str[3] += (fg_color = color);
	write(OUTPUT, str, 5);
    }
    else
	tty_reverse((fg_color = color) != WHITE);
#else
    tty_reverse((fg_color = color) != WHITE);
#endif
}


void tty_background(int color)
{
#ifdef HAVE_LINUX
    char str[] = "\x1b\x5b\x34\x30\x6d";
    
    if (LinuxConsole)
    {
	str[3] += (bg_color = color);
	write(OUTPUT, str, 5);
    }
    else
	tty_reverse((bg_color = color) != BLACK);
#else
    tty_reverse((bg_color = color) != BLACK);
#endif
}


void tty_bright(int status)
{
    if (status == ON)
	write(OUTPUT, termcap[TTY_BRIGHT_ON], termcap_len[TTY_BRIGHT_ON]);
    else
    {
#ifdef HAVE_LINUX
	if (LinuxConsole)
	    write(OUTPUT, "\x1b\x5b\x32\x32\x6d", 5);
	else
#endif
	{
	    write(OUTPUT, termcap[TTY_BRIGHT_OFF], termcap_len[TTY_BRIGHT_OFF]);
	    br_status = OFF;
	    if (rv_status) tty_reverse(rv_status);
	}
    }
    br_status = status;
}


void tty_reverse(int status)
{
    if (status == ON)
	write(OUTPUT, termcap[TTY_REVERSE_ON], termcap_len[TTY_REVERSE_ON]);
    else
    {
	write(OUTPUT, termcap[TTY_REVERSE_OFF], termcap_len[TTY_REVERSE_OFF]);
	rv_status = OFF;
	if (br_status) tty_bright(br_status);
    }
    rv_status = status;
}


void tty_beep(void)
{
    char c = 7;	
    write(OUTPUT, &c, 1);
}


void tty_save(tty_status *status)
{
    status->fg_color  = fg_color;
    status->bg_color  = bg_color;
    status->br_status = br_status;
    status->rv_status = rv_status;
}


void tty_restore(tty_status *status)
{
    tty_foreground(status->fg_color);
    tty_background(status->bg_color);
    tty_bright(status->br_status);
    tty_reverse(status->rv_status);
}


void tty_defaults(void)
{
#ifdef HAVE_LINUX
    if (LinuxConsole)
	write(OUTPUT, "\x1b\x5b\x30\x6d", 4);	/* Linux console defaults */
    else
    {
	write(OUTPUT, termcap[TTY_BRIGHT_OFF],  termcap_len[TTY_BRIGHT_OFF]);
	write(OUTPUT, termcap[TTY_REVERSE_OFF], termcap_len[TTY_REVERSE_OFF]);
    }
#else
    write(OUTPUT, termcap[TTY_BRIGHT_OFF],  termcap_len[TTY_BRIGHT_OFF]);
    write(OUTPUT, termcap[TTY_REVERSE_OFF], termcap_len[TTY_REVERSE_OFF]);
#endif
    fg_color = WHITE;
    bg_color = BLACK;
    br_status = rv_status = OFF;
}


int tty_write(char *buf, int length)
{
    return write(OUTPUT, buf, length);
}


int tty_read(char *buf, int length)
{
    return read(INPUT, buf, length);
}


char tty_getch(void)
{
    if (keyno) return keybuf[keyno--, keyindex++];
    for (keyindex = 0; (keyno = read(INPUT, keybuf, 1024)) < 0;);
    return keyno ? keybuf[keyno--, keyindex++] : 0;
}


void tty_putch(int c)
{
    write(OUTPUT, &c, 1);
}


int tty_getkey(int *repeat_count)
{
    int i, j;
    char full_key[16], c = tty_getch();

    if (repeat_count) *repeat_count = 1;
    if (c == 10 || c == 13) return KEY_ENTER;
    if (c >= ' ' && c <= '~') return c;

    for (j = 0; j < 15; c = tty_getch(), j++)
    {
        if (j && full_key[0] >= 1 && full_key[0] <= 031)
	{
	    if (c >= 1 && c <= 031)
		full_key[j] = c - 1 + 'a';
	    else
		full_key[j] = tolower(c);
	}
        else
            full_key[j] = c;
        
	for (i = 1; i < TTY_KEYS; i++)
	    if (memcmp(full_key, kbdmap[i], j + 1) == 0) goto step_ok;
	return KEY_NOKEY;
      step_ok:
        if (kbdmap[i][j + 1] == 0) break;
    }
    if (repeat_count)
	while (keyno > j && (memcmp(full_key, &keybuf[keyindex], j + 1) == 0))
	{
	    keyindex += j + 1;
	    keyno    -= j + 1;
	    (*repeat_count)++;
	}
    return (i < TTY_FIRST_CONFIG_KEY) ? i : -i;
}


void tty_getsize(int *_columns, int *_rows)
{
    char *env;
    struct winsize winsz;
    int temp_rows, temp_columns;

    columns = rows = temp_columns = temp_rows = 0;

    ioctl(OUTPUT, TIOCGWINSZ, &winsz);
    if (winsz.ws_col && winsz.ws_row)
    {
	temp_columns = winsz.ws_col;
	temp_rows    = winsz.ws_row;
    }

    if (env = getenv("COLUMNS")) sscanf(env, "%d", &columns);
    if (env = getenv("LINES"))   sscanf(env, "%d", &rows);
    if (columns < 80) columns = temp_columns;
    if (rows    < 10) rows    = temp_rows;
    if (columns < 80 || columns > 200) columns = 80;
    if (rows    < 10 || rows    > 128) rows    = 24;

    *_columns = columns;
    *_rows    = rows;
}


void tty_getscreen(char *buf)
{
#ifdef HAVE_LINUX
    int x, y, index;
    char *tty_name;

    if (LinuxConsole)
    {
	tty_name = ttyname(1);
	buf[0] = 0;
	buf[1] = tty_name[strlen(tty_name) - 1] - '0';
	if (ioctl(OUTPUT, TIOCLINUX, buf) == -1)
	{
	    buf[0] = buf[1] = 0;			
	    
	    /* Linux bug: bad ioctl on console 8 */
	    
	    if (ioctl(OUTPUT, TIOCLINUX, buf) == -1)
	    {
		tty_perm = 0;
		return;
	    }
	}
	tty_perm = 1;
	index = 2 + rows * columns;
	for (y = rows - 1; y >= 0; y--)
	    for (x = columns - 1; x >= 0; x--)
	        if (buf[--index] != ' ')
	            goto found;
      found:
	
	buf[0] = y + 1;
	buf[1] = 0; 
	tty_cursormove(y + 1, 0);
    }
#endif
}


void tty_putscreen(char *buf)
{    
    tty_defaults();
    tty_cursormove(0, 0);

#ifdef HAVE_LINUX
    if (LinuxConsole)
    {
	if (tty_perm)
	{
	    write(OUTPUT, buf + 2, rows * columns);
	    tty_cursormove(buf[0], buf[1]);
	}
	else
	    tty_clrscr();
    }
    else
	tty_clrscr();
#else
    tty_clrscr();
#endif
}


int tty_getcolorindex(char *colorname)
{
    int i;
    
    for (i = 0; i < 10; i++)
        if (strcmp(colors[i], colorname) == 0)
            return (i < 8) ? i : (i - 8);
    return -1;
}
