/*
    TTY interface file
    Copyright (c) Tudor Hulubei & Andrei Pitis, May 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

int    tty_kbdmode = 0;	/* If tty_mode == 1 single characters are inserted
			   in the linked list. This feature is used by uitps
			   (no command line ... ) */

static struct termios oldterm;
static struct termios newterm;

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

extern int LinuxConsole;


struct key_struct *key_list_head = NULL;
struct key_struct *current_key;
struct key_struct default_key;


void fatal(char *);


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


char key_ctrl_tbl[0x5f] = 
{
    0x20,   			/* 0x20 ( ) */
    0x21,   			/* 0x21 (!) */
    0x22,   			/* 0x22 (") */
    0x23,   			/* 0x23 (#) */
    0x24,   			/* 0x24 ($) */
    0x25,   			/* 0x25 (%) */
    0x26,   			/* 0x26 (&) */
    0x07,   			/* 0x27 (') */
    0x28,   			/* 0x28 (() */
    0x29,   			/* 0x29 ()) */
    0x2a,   			/* 0x2a (*) */
    0x2b,   			/* 0x2b (+) */
    0x2c,   			/* 0x2c (,) */
    0x2d,   			/* 0x2d (-) */
    0x2e,   			/* 0x2e (.) */
    0x2f,   			/* 0x2f (/) */
    0x20,   			/* 0x30 (0) */
    0x20,   			/* 0x31 (1) */
    0x20,   			/* 0x32 (2) */
    0x1b,   			/* 0x33 (3) */
    0x1c,  			/* 0x34 (4) */
    0x1d,   			/* 0x35 (5) */
    0x1e,   			/* 0x36 (6) */
    0x1f,   			/* 0x37 (7) */
    0x7f,   			/* 0x38 (8) */
    0x39,   			/* 0x39 (9) */
    0x3a,   			/* 0x3a (:) */
    0x3b,   			/* 0x3b (;) */
    0x3c,   			/* 0x3c (<) */
    0x20,   			/* 0x3d (=) */
    0x3e,   			/* 0x3e (>) */
    0x20,   			/* 0x3f (?) */
    0x20,   			/* 0x40 (@) */
    0x01,   			/* 0x41 (A) */
    0x02,   			/* 0x42 (B) */
    0x03,   			/* 0x43 (C) */
    0x04,   			/* 0x44 (D) */
    0x05,   			/* 0x45 (E) */
    0x06,   			/* 0x46 (F) */
    0x07,   			/* 0x47 (G) */
    0x08,   			/* 0x48 (H) */
    0x09,   			/* 0x49 (I) */
    0x0a,   			/* 0x4a (J) */
    0x0b,   			/* 0x4b (K) */
    0x0c,   			/* 0x4c (L) */
    0x0d,   			/* 0x4d (M) */
    0x0e,   			/* 0x4e (N) */
    0x0f,   			/* 0x4f (O) */
    0x10,   			/* 0x50 (P) */
    0x11,   			/* 0x51 (Q) */
    0x12,   			/* 0x52 (R) */
    0x13,   			/* 0x53 (S) */
    0x14,   			/* 0x54 (T) */
    0x15,   			/* 0x55 (U) */
    0x16,   			/* 0x56 (V) */
    0x17,   			/* 0x57 (W) */
    0x18,   			/* 0x58 (X) */
    0x19,   			/* 0x59 (Y) */
    0x1a,   			/* 0x5a (Z) */
    0x1b,   			/* 0x5b ([) */
    0x1c,   			/* 0x5c (\) */
    0x1d,   			/* 0x5d (]) */
    0x5e,   			/* 0x5e (^) */
    0x1f,   			/* 0x5f (_) */
    0x20,   			/* 0x60 (`) */
    0x01,   			/* 0x61 (a) */
    0x02,   			/* 0x62 (b) */
    0x03,   			/* 0x63 (c) */
    0x04,   			/* 0x64 (d) */
    0x05,   			/* 0x65 (e) */
    0x06,   			/* 0x66 (f) */
    0x07,   			/* 0x67 (g) */
    0x08,   			/* 0x68 (h) */
    0x09,   			/* 0x69 (i) */
    0x0a,   			/* 0x6a (j) */
    0x0b,   			/* 0x6b (k) */
    0x0c,   			/* 0x6c (l) */
    0x0d,   			/* 0x6d (m) */
    0x0e,   			/* 0x6e (n) */
    0x0f,   			/* 0x6f (o) */
    0x10,   			/* 0x70 (p) */
    0x11,   			/* 0x71 (q) */
    0x12,   			/* 0x72 (r) */
    0x13,   			/* 0x73 (s) */
    0x14,   			/* 0x74 (t) */
    0x15,   			/* 0x75 (u) */
    0x16,   			/* 0x76 (v) */
    0x17,   			/* 0x77 (w) */
    0x18,   			/* 0x78 (x) */
    0x19,   			/* 0x79 (y) */
    0x1a,   			/* 0x7a (z) */
    0x20,   			/* 0x7b ({) */
    0x20,   			/* 0x7c (|) */
    0x20,   			/* 0x7d (}) */
    0x20   			/* 0x7e (~) */
};


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


#define NO	0
#define YES	1


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


void tty_init(void)
{
    tty_defaults();

    tcgetattr(OUTPUT, &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(OUTPUT, TCSADRAIN, &newterm);
}


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


void tty_kbdinit(int mode)
{
    default_key.key_seq  = (char *)malloc(16);
    default_key.aux_data = NULL;
    default_key.next     = NULL;
    tty_kbdmode 	 = mode;
}


char *tty_key_convert(char *key_seq)
{
    char *first, *second;

    first = second = key_seq;

    if (tty_kbdmode == 0 && *key_seq != '^') return NULL;

    while (*second)
    {
	if (*second == '^')
	    if (*++second)
		*first++ = key_ctrl_tbl[(*second++ & 0x7F) - ' '];
	    else
		return NULL;
	else
	    *first++ = *second++;
    }
    *first = 0;
    return key_seq;
}


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);
}


void tty_key_list_insert_sequence(struct key_struct **key,
				  char *key_seq, void *aux_data)
{
    struct key_struct *new_key;

    new_key = (struct key_struct *)malloc(sizeof(struct key_struct));
    if (new_key == NULL)
    {
	fprintf(stderr, "not enough memory\n");
	return;
    }

    new_key->key_seq = (char *)malloc(strlen(key_seq) + 1);
    if (new_key->key_seq == NULL)
    {
	fprintf(stderr, "not enough memory\n");
	free(new_key);
	return;
    }

    strcpy(new_key->key_seq, key_seq);
    new_key->aux_data = aux_data;
    new_key->next = *key;
    *key = new_key;
}


void tty_key_list_insert(char *key_seq, void *aux_data)
{
    struct key_struct **key;

    if (*key_seq == 0) return;               /* bad key sequence ! */

    for (key = &key_list_head; *key; key = &(*key)->next)
	if (strcmp(key_seq, (*key)->key_seq) <= 0)
	{
	    tty_key_list_insert_sequence(key, key_seq, aux_data);
	    return;
	}

    tty_key_list_insert_sequence(key, key_seq, aux_data);
}


struct key_struct *tty_key_search(char *key_seq)
{
    int cmp;

    if (current_key == NULL) return NULL;

    for (; current_key; current_key = current_key->next)
    {
	cmp = strcmp(key_seq, current_key->key_seq);
	if (cmp == 0) return current_key;
	if (cmp  < 0) break;
    }

    if (current_key == NULL ||
    	strncmp(key_seq, current_key->key_seq, strlen(key_seq)))
        return (struct key_struct*) - 1;
    else
        return NULL;
}


void tty_key_search_restart(void)
{
    current_key = key_list_head;
}


void tty_key_list_delete(void)
{
    struct key_struct *key, *next_key;

    for (key = key_list_head; key; key = next_key)
    {
	next_key = key->next;
	free(key->key_seq);
	free(key);
    }
}


struct key_struct *tty_getkey(int *repeat_count)
{
    int i, j;
    struct key_struct *key;
    char c, key_seq[16];


  restart:

    while ((c = tty_getch()) == 0);

    if (repeat_count) *repeat_count = 1;

    if (!tty_kbdmode)
    {
	if (c == '\n' || c == '\r')
	{
	    default_key.key_seq[0] = KEY_ENTER;
	    default_key.key_seq[1] = 0;
	    return &default_key;
	}

	if (is_print(c) || c == KEY_TAB || c == KEY_BACKSPACE)
	{
	    default_key.key_seq[0] = c;
	    default_key.key_seq[1] = 0;
	    return &default_key;
	}
    }

    tty_key_search_restart();

    for (j = 0; j < 15; j++)
    {
        key_seq[j    ] = c;
        key_seq[j + 1] = 0;

        key = tty_key_search(key_seq);
	if (key == (struct key_struct *)-1) goto restart;
	if (key) break;
	while ((c = tty_getch()) == 0);
    }

    if (repeat_count)
	while (keyno > j && (memcmp(key_seq, &keybuf[keyindex], j + 1) == 0))
	{
	    keyindex += j + 1;
	    keyno    -= j + 1;
	    (*repeat_count)++;
	}
    return key;
}


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, len;
    char *tty_name;

    if (LinuxConsole)
    {
	tty_name = ttyname(1);
	len = strlen(tty_name);
	if (tty_name[len - 4] != 't' ||
	    tty_name[len - 3] != 't' ||
	    tty_name[len - 2] != 'y' ||
	    !isdigit(tty_name[len - 1]))
	{
	    tty_perm = 0;
	    return;
	}
	buf[0] = 0;
	buf[1] = tty_name[len - 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_defaults();
	    tty_clrscr();
	}
    }
    else
    {
	tty_defaults();
	tty_clrscr();
    }
#else
    tty_defaults();
    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;
}
