/* Replacement console read/write routines that allow for key
   remappings */
/* 
 * console input/output routines.
 * written by Eric R. Smith and placed in the public domain
 */

/*
 * BUGS: assumes all input is coming from the same place;
 * this bug shows up only when keys are remapped, though.
 */

#include <osbind.h>
#include <support.h>
#include <stdlib.h>
#include <ioctl.h>
#include <tchars.h>
#include <signal.h>
#include <unixlib.h>
#include <keycodes.h>
#include <string.h>

#define STRDUP(x,y) if (x) free(x); x = strdup(y);

#define S_SHIFT 0x03
#define S_CNTRL	0x04
#define S_ALT	0x08

extern int _console_dev;	/* in main.c */
int __check_signals = 0;

/* arrays to hold keyboard strings */

static char *kb_unshft[N_KEYCODES], *kb_shft[N_KEYCODES], *kb_alt[N_KEYCODES];
static char *_str = NULL;

void console_set_key(keycode, regular, shifted, alted)
int keycode;
char *regular, *shifted, *alted;
{
	if (keycode < 1 || keycode >= N_KEYCODES)
		return;

	if (regular) {
		if (!*regular) regular = 0;
		STRDUP(kb_unshft[keycode],regular);
	}
	if (shifted) {
		if (!*shifted) shifted = 0;
/* shifted function keys are a bit screwy */
		if (keycode >= F_1 && keycode <= F_10) {
			STRDUP(kb_shft[keycode - F_1 + 0x54],shifted);
		}
		else {
			STRDUP(kb_shft[keycode],shifted);
		}
	}
	if (alted) {
		if (!*alted) alted = 0;
		STRDUP(kb_alt[keycode],alted);
	}
}

#define KBUFSIZ 80
#define NUMDEV	3
	/* 0 == prn:, 1 == aux:, 2 == con: */

typedef struct _buffer {
	short head, tail;
	long  buffer[KBUFSIZ];
} k_buf;

static k_buf in_buf[NUMDEV];
#define IN_BUF(dev) (&in_buf[(dev) > 2 ? _console_dev : (dev)])

/*
 * what handle means:: 0-2: BIOS handle, 3: stdout, 4: stderr
 */

static short
LOOKUP(handle)
short handle;
{
	switch(handle) {
	case -3:
	case -2:
	case -1:
	case 0:
		return handle + 3;
	case 2:
		return 4;
	case 1:
		if (isatty(0)) return 3;
		if (isatty(2)) return 4;
		/* else fall through */
	default:
		return _console_dev;
	}
}

/*
 * raw i/o routines
 */

static long
raw_in(dev)
int dev;
{
	if (dev < 3)
		return Bconin(dev);
	else if (dev == 3)
		return Crawcin();
	else
		return Cauxin();
}

#if 0
static void
raw_out(dev, c)
int dev, c;
{
	if (dev < 3)
		Bconout(dev, c);
	else if (dev == 3)
		Crawio(c);
	else
		Cauxout(c);
}
#endif

static long
raw_instat(dev)
int dev;
{
	if (dev < 3)
		return Bconstat(dev);
	else if (dev == 3)
		return Cconis();
	else
		return Cauxis();
}

/*
 * somewhat less raw i/o routines. The main difference is that these ones
 * will check for pending input before doing output, to see if a signal
 * needs to be raised. This is only done if __check_signals is non-zero;
 * signal() should set __check_signals when the user attempts to catch
 * SIGINT or SIGQUIT. We don't do this checking all the time because
 * the user may be typing ahead input for another program (e.g. if this
 * is a little utility of some sort) and we shouldn't steal keystrokes
 * unless necessary.
 */

unsigned int console_read_byte(handle)
int handle;
{
	k_buf *p;
	short i, j, dev;
	unsigned scan, key, shft;
	long r;

	dev = LOOKUP(handle);

	if (_str) {
		if (*_str)
			return *(unsigned char *)_str++;
		else
			_str = 0;
	}

	p = IN_BUF(dev);
	if ( p->head != (i = p->tail)) {
		j = p->tail + 1;
		if (j >= KBUFSIZ)
			j = 0;
		p->tail = j;
		r = p->buffer[i];
	}
	else
		r = raw_in(dev);

	scan = (r & 0x00ff0000) >> 16;
	key = r & 0x00ff;
	if (scan > 0 && scan < N_KEYCODES) {
		shft = Kbshift(-1);
		if (shft & S_ALT)
			_str = kb_alt[scan];
		else if (shft & S_SHIFT)
			_str = kb_shft[scan];
		else if (!(shft & S_CNTRL))
			_str = kb_unshft[scan];
		if (_str) return *(unsigned char *)_str++;
	}
	return key;
}

int console_input_status(handle)
int handle;
{
	short dev;
	k_buf *p;

	if (_str && *_str) return 1;
	dev = LOOKUP(handle);
	p = IN_BUF(dev);
	return (p->head != p->tail) || raw_instat(dev);
}

void
console_write_byte(handle, n)
int handle;
int n;
{
	long c;
	short i, j, waiting = 0;
	unsigned char ch;
	k_buf *p;
	short dev;

	dev = LOOKUP(handle);
	p = IN_BUF(dev);

	while (__check_signals && (raw_instat(dev) || waiting)) {
		c = raw_in(dev);
		if (!(__ttymode & RAW)) {
			ch = c & 0xff;
			if (ch == ('S'-'@')) {
				waiting = 1;
			}
			else if (ch == ('Q'-'@')) {
				waiting = 0;
			}
			else if (ch == __tchars[TC_INTRC]) {
				p->head = p->tail;
				raise(SIGINT);
			}
			else if (ch == __tchars[TC_QUITC]) {
				p->head = p->tail;
				raise(SIGQUIT);
			}
			else if (!waiting) {
				i = p->head;
				j = i + 1;
				if (j >= KBUFSIZ) j = 0;
				if (j != p->tail) {
					p->buffer[i] = c;
					p->head = j;
				}
			}
		}
	}
#if 0
	raw_out(dev, n);
#endif
	ch = n;
	(void)Fwrite(handle, 1L, &ch);
}

void flush_key_buff(fd)  
int fd;
{ 
    long c;
    short i, j, waiting = 0;
    unsigned char ch;
    k_buf *p;
    short dev;
    short handle;
    
    handle = fd;
    
    dev = LOOKUP(handle);
    p = IN_BUF(dev);
    
    while (raw_instat(dev) || waiting) 
    { 
	c = raw_in(dev);
	if (!(__ttymode & RAW)) 
	{
	    ch = c & 0xff;
	    if (ch == ('S'-'@')) { waiting = 1; }
	    else if (ch == ('Q'-'@')) { waiting = 0; }
	    else if (ch == __tchars[TC_INTRC]) 
	    { 
		p->head = p->tail;
		raise(SIGINT);
	    }
	    else if (ch == __tchars[TC_QUITC]) 
	    { 
		p->head = p->tail;
		raise(SIGQUIT);
	    }
	    else if (!waiting) 
	    { 
		i = p->head;
		j = i + 1;
		if (j >= KBUFSIZ) j = 0;
		if (j != p->tail) 
		{ 
		    p->buffer[i] = c;
		    p->head = j;
		}
	    }
	}
    }
}
