/*
** console.c -- sco vga console support routines
**
**	out_index2 and out_port are in outindex.s
**
**	compile -DTEST_MAIN for a test main program
**	compile -DCONSOLE_DEBUG=1 for debugging
*/

#ifndef WIN_USE_CONSOLE
#include "windecs.h"
#endif

#if WIN_USE_KB
#   include "kb.h"
#endif

#ifndef CONSOLE_DEBUG
#define	CONSOLE_DEBUG	0
#endif

#if SYSTEM_SCO && defined(TEST_MAIN)
#define	protect_disk()	sync(), sync(), sync()
#else
#define	protect_disk()	/* nothing */
#endif

#if WIN_USE_CONSOLE

#include <stdio.h>
#include <stdlib.h>

#include <fcntl.h>
#include <signal.h>

#ifdef MSDOS
#   include <dos.h>
#   if defined(__ZTC__)
#	include <io.h>
#	if defined(DOS386)
#	    include <x32.h>
#       endif
#   elif defined(GNUDOS)
#	include <pc.h>
#	define	outp(port,value)	outportb((port),(value))
#   elif defined(__TURBOC__)
#	if defined(DOSX286)
#	    include <phapi.h>
#	endif
#   elif defined(M_I86)
#	include <io.h>
#	include <conio.h>
#   endif
#   if !(defined(__ZTC__) && defined(DOS386)) && !defined(GNUDOS)
#	define	console_segmented	1
#   endif
#else
#   include <sys/machdep.h>
#   if !(defined(_SYS_TYPES_H) || defined(GETUCHAR))
#	include <sys/types.h>
#   endif
#   include <sys/vtkd.h>

#   define SIG_RELEASE	SIGUSR1
#   define SIG_ACQUIRE	SIGUSR2
#endif

#define INDEX(x, y, sz_x)	( (x) + (y) * (sz_x) )

#define regen 0xa000

#ifdef console_segmented
#   ifndef MK_FP
#	define MK_FP(seg,offset) \
		((void _far *)(((unsigned long)(seg)<<16) | (unsigned)(offset)))
#   endif
#   define mk_fb_ptr(x, y)\
	(char far *)MK_FP(regen + ((y) >> 4) * 80,\
		 (((y) & 15) * 80) + ((x) >> 3))
#else
#   define	console_segmented	0
#   define mk_fb_ptr(x, y)	(screen_mem + (y) * 80 + ((x) >> 3))
#endif

#if console_segmented
#   define	console_far	far
#else
#if defined(__GNUC__)
#   define	console_far	volatile
#else
#   define	console_far	/* */
#endif
#endif

#if defined(__ZTC__)
    static char console_junk;
#   define console_volatile	console_junk =
#else
#   define console_volatile	/* */
#endif

/*
** Check that we can use O_BINARY, etc.
*/

#ifndef O_BINARY
#define	O_BINARY	0
#endif
#ifndef O_NOINHERIT
#define	O_NOINHERIT	0
#endif
#ifndef O_DENYNONE
#define	O_DENYNONE	0
#endif

#include "consdecs.h"

#include "font.h"

/*
 * For standalone compiles
 */

#ifndef WIN_GRAPHIC
#ifndef TRUE
#   define	TRUE	1
#endif
#ifndef FALSE
#   define	FALSE	0
#endif
#ifndef UNUSED_ARG
#   define UNUSED_ARG(arg)		(void)(arg)
#endif
#define	win_attr	short
#define	WIN_GRAPHIC	0x0080	/* draw in vt100 graphic character set */
#define	WIN_GET_FOR(color)	((win_attr) ((color) & 0x0007))
#define	WIN_GET_BAC(color)	((win_attr) (((color) >> 4) & 0x0007))
#define	WIN_GET_FORB(color)	((win_attr) ((color) & 0x000F))
#define	WIN_GET_BACB(color)	((win_attr) (((color) >> 4) & 0x000F))
#endif	/* standalone */

/*
 * IO addresses
 */

#define GC_INDEX  0x3CE		/* Graphics controller index register    */
#define GC_MAP    0x4		/* index for mapping the READ plane      */
#define GC_MODE   0x5		/* index for setting the READ/WRITE mode */
#define GC_MASK   0x8		/* index for setting WRITE bit mask      */

#define SQ_INDEX  0x3C4		/* Sequence controller index register */
#define SQ_MAP    0x2  		/* index for mapping the WRITE plane  */

#define GC_ADDR	  0x3CF


#define SCREEN_X	640
#define SCREEN_Y	480
#define BLACK		0x0
#define BLUE		0x1
#define RED		0x4
#define WHITE		0x7
#define HI_WHITE	0xF

/*
** VGA12 is 640 X 480
** Each font cell is 8 X 16
** 19 * 25 = 475, so we add 1 pixel above and 2 below each cell
*/

#define	CELL_FILL_TOP	1
#define	CELL_FILL_BOT	2
#define	CELL_TOTAL_LEN	(CELL_LEN + CELL_FILL_TOP + CELL_FILL_BOT)

#define	SHOW	0

static int text_mode = 0;		/* saved text mode */
static int screen_fd = -1;		/* fd of screen */
#if !console_segmented
static char console_far *screen_mem = NULL; /* pointer to video memory */
#endif

static int cell_row = 1;	/* current row (1-based) */
static int cell_col = 1;	/* current col (1-based) */

static int for_color;		/* current foreground color for text */
static int bac_color;		/* current background color for text */

static int font_height;		/* font height in pixels */
static int font_width;		/* font width in pixels */

static unsigned char font_data[MAP_SIZE][CELL_LEN];	/* font bitmap */

#define	BOX_ALL	1
#define	BOX_TOP	2
#define	BOX_BOT	4

static char box[MAP_SIZE];	/* tell if cell should be extended */

static int in_graphics_mode = 0;	/* non-zero if in graphics mode */

static void (*console_repaint)(void);	/* user repaint procedure */

boolean console_is_active = TRUE;	/* current multi-screen is active */

/*
** Bitmap for mouse
*/

#if MSDOS
#else

#define ON	HI_WHITE
#define OFF	BLACK

#if 1
#define MOUSE_SIZE	12

static unsigned char mouse_bitmap[MOUSE_SIZE * MOUSE_SIZE] = {
  OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF,
  OFF, ON,  ON,  ON,  ON,  ON,  ON,  ON,  ON,  ON,  OFF, OFF,
  OFF, ON,  ON,  ON,  ON,  ON,  ON,  ON,  ON,  ON,  OFF, OFF,
  OFF, ON,  ON,  ON,  ON,  ON,  OFF, OFF, OFF, OFF, OFF, OFF,
  OFF, ON,  ON,  ON,  ON,  ON,  ON,  OFF, OFF, OFF, OFF, OFF,
  OFF, ON,  ON,  OFF, ON,  ON,  ON,  ON,  OFF, OFF, OFF, OFF,
  OFF, ON,  ON,  OFF, OFF, ON,  ON,  ON,  ON,  OFF, OFF, OFF,
  OFF, ON,  ON,  OFF, OFF, OFF, ON,  ON,  ON,  ON,  OFF, OFF,
  OFF, ON,  ON,  OFF, OFF, OFF, OFF, ON,  ON,  ON,  ON,  OFF,
  OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, ON,  ON,  ON,  OFF,
  OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, ON,  ON,  OFF,
  OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF, OFF,
};
#else
#define MOUSE_SIZE	6

static unsigned char mouse_bitmap[MOUSE_SIZE * MOUSE_SIZE] = {
	OFF,	OFF,	OFF,	OFF,	OFF,	OFF,
	OFF,	ON,	ON,	ON,	ON,	OFF,
	OFF,	ON,	ON,	ON,	OFF,	OFF,
	OFF,	ON,	OFF,	ON,	ON,	OFF,
	OFF,	ON,	OFF,	OFF,	ON,	ON,
	OFF,	OFF,	OFF,	OFF,	OFF,	ON,
};
#endif

static unsigned char mouse_mask[ MOUSE_SIZE * MOUSE_SIZE ];

/*
** Screen under mouse
*/

static unsigned char under_mouse[ MOUSE_SIZE * MOUSE_SIZE ];

#endif

/*
** Current mouse location
*/

static int mouse_row = -MOUSE_SIZE;
static int mouse_col = -MOUSE_SIZE;

/*
** write to port on vga card
**   source in outindex.s
*/

#ifdef MSDOS
#   define out_index2(index,mode,mask) outp(index,mode),outp((index)+1,mask)
#   define out_port(index,value)	outp(index,value)
#else
#if defined(__GNUC__)
static inline void out_index2(int index, int mode, int mask)
{
	__asm__ volatile ("movb %0,%%ah; movb %1,%%al; outw %%ax,%2" : :
	"qmi" ((unsigned char) mask),
	"g" ((unsigned char) mode),
	"d" ((unsigned short) index) :
	"eax");
}
static inline void out_port(int index, int value)
{
	__asm__ volatile ("outb %%al,%%dx" : :
	"a" ((unsigned char) value),
	"d" ((unsigned short) index));
}
#else
    void out_index2(int index, int mode, int mask);
    void out_port(int index, int value);
#endif
#endif

#define set_mode(mode)	out_port(GC_INDEX,mode)
#define set_mask_mode()	set_mode(GC_MASK)
#define out_mask(mask)	out_port(GC_INDEX+1,mask)

static int
quit(const char *s)
{
	int err;

	err = errno;
	console_finish();
	errno = err;
	if(s)
		perror(s);
	return(1);
}

/*
** Enter graphics mode
*/

int
console_graphics_mode(force)
int force;
{
#ifdef MSDOS
	union REGS regs;
#endif

#if CONSOLE_DEBUG
	dprintf(
		"console_graphics_mode: in_graphics_mode %d, force %d\r\n",
		in_graphics_mode, force);
#endif

	if (in_graphics_mode && !force)
		return(0);

	if (!console_is_active) {
#if CONSOLE_DEBUG
		dprintf("console_graphics_mode: not active\r\n");
#endif
		return(1);
	}

	protect_disk();

#if CONSOLE_DEBUG
	dprintf("console_graphics_mode: before ioctl\r\n");
#endif

#ifdef MSDOS
	regs.h.ah = 0;
	regs.h.al = 0x12;
	int86(0x10, &regs, &regs);
#else
	if(ioctl(screen_fd, SW_VGA12, 0L) == -1)
		return quit("SW_VGA12");
#endif

#if CONSOLE_DEBUG
	dprintf("console_graphics_mode: out_index2\r\n");
#endif

	out_index2(GC_INDEX, GC_MODE, 2);	/* use graphics mode 2 */
	in_graphics_mode = 1;

#if CONSOLE_DEBUG
	dprintf("console_graphics_mode: console_repaint %d\r\n",
		console_repaint);
#endif

	if (console_repaint)
		(*console_repaint)();

#if CONSOLE_DEBUG
	dprintf("console_graphics_mode: returning\r\n");
#endif

	return(0);
}

/*
** Enter text mode
*/

int
console_text_mode(force)
int force;
{
#ifdef MSDOS
	union REGS regs;
#else
	int i, ok;
#endif

#if CONSOLE_DEBUG
	dprintf(
		"console_text_mode: in_graphics_mode %d, force %d\r\n",
		in_graphics_mode, force);
#endif

	if (!in_graphics_mode && !force)
		return(0);

	protect_disk();

#if CONSOLE_DEBUG
	dprintf("console_text_mode: before ioctl\r\n");
#endif

#ifdef MSDOS
	if (text_mode > 0) {
		regs.h.ah = 0;
		regs.h.al = (char) text_mode;
		int86(0x10, &regs, &regs);
	}
#else
	ok = 0;
	for (i = 0; i < 10; i++) {
		ok = (ioctl(screen_fd, SW_VGA80x25, 0L) != -1);
		if (ok || errno != EINTR) break;
	}
	if (!ok &&
	    text_mode != 0 &&
	    ioctl(screen_fd, MODESWITCH|text_mode, 0L) == -1) {
		return quit("ioctl");
	}
#endif

	in_graphics_mode = 0;

#if CONSOLE_DEBUG
	dprintf("console_text_mode: returning\r\n");
#endif

	return(0);
}

#ifdef MSDOS
#else
/*
** release the console
*/

static int
console_release()
{
	int i;

#if CONSOLE_DEBUG
	dprintf("console_release: before ioctl\r\n");
#endif

	protect_disk();

	i = ioctl(screen_fd, VT_RELDISP, VT_TRUE);

#if CONSOLE_DEBUG
	dprintf("console_release: ioctl returned %d, returning %d\r\n",
		i, (i == -1));
#endif

	return(i == -1);
}

static void
release(int sig)
{
	UNUSED_ARG(sig);
#if CONSOLE_DEBUG
	dprintf("release: top\r\n");
#endif
	console_is_active = FALSE;
	signal(SIG_RELEASE, SIG_IGN);
	console_release();
	signal(SIG_RELEASE, release);
#if CONSOLE_DEBUG
	dprintf("release: returning\r\n");
#endif
}

/*
** acquire the console
*/

static int
console_acquire()
{
	int i;

#if CONSOLE_DEBUG
	dprintf("console_acquire: before ioctl\r\n");
#endif

	protect_disk();
	console_graphics_mode(TRUE);
	i = ioctl(screen_fd, VT_RELDISP, VT_ACKACQ);
#if WIN_USE_KB
        kb_switched_screens = TRUE;
#endif

#if CONSOLE_DEBUG
	dprintf("console_acquire: ioctl returned %d\r\n", i);
#endif
	return(0);
}

static void
acquire(int sig)
{
	UNUSED_ARG(sig);
#if CONSOLE_DEBUG
	dprintf("acquire: top\r\n");
#endif
	signal(SIG_ACQUIRE, SIG_IGN);
	console_is_active = TRUE;
	console_acquire();
	signal(SIG_ACQUIRE, acquire);
#if CONSOLE_DEBUG
	dprintf("acquire: returning\r\n");
#endif
}
#endif

/*
** Read the font table from a file, returns 0 if OK
*/

static int console_read_font(void)
{
	int fd;
	char *font_name;
	font_header h;

#if CONSOLE_DEBUG
	dprintf("console_read_font: top\r\n");
#endif

	font_name = getenv("SPICEFONT");

	if (font_name == (char *)NULL || *font_name == '\0')
		return(1);

	fd = open(font_name, O_RDONLY|O_BINARY|O_NOINHERIT|O_DENYNONE);
	if (fd == -1) {
		perror(font_name);
		return(1);
	}

	if (read(fd, &h.font_version, sizeof(h.font_version)) !=
			sizeof(h.font_version) ||
	    read(fd, &h.font_num_chars, sizeof(h.font_num_chars)) !=
			sizeof(h.font_num_chars) ||
	    read(fd, &h.font_width, sizeof(h.font_width)) !=
			sizeof(h.font_width) ||
	    read(fd, &h.font_length, sizeof(h.font_length)) !=
			sizeof(h.font_length)) {
		fprintf(stderr,
			"Error reading font header of %s\r\n", font_name);
		close(fd);
		return(1);
	}

	if (h.font_version != VERSION ||
	    h.font_num_chars <= 0 ||
	    h.font_num_chars > MAP_SIZE ||
	    h.font_width != CELL_WID ||
	    h.font_length != CELL_LEN) {
		fprintf(stderr, "Invalid font header in %s\r\n", font_name);
#ifdef TEST_MAIN
		fprintf(stderr, " version %d, want %d\n",
					h.font_version, VERSION);
		fprintf(stderr, " num_chars %d, want 0 to %d\n",
					h.font_num_chars, MAP_SIZE);
		fprintf(stderr, " width %d, want %d\n",
					h.font_width, CELL_WID);
		fprintf(stderr, " len %d, want %d\n",
					h.font_length, CELL_LEN);
#endif
		close(fd);
		return(1);
	}

	if (read(fd, (char *)font_data, h.font_num_chars * CELL_LEN) !=
			h.font_num_chars * CELL_LEN) {
		fprintf(stderr, "Error reading font data in %s\r\n", font_name);
		close(fd);
		return(1);
	}

	close(fd);
	return(0);
}

/*
** initialize
*/

int
console_init(void (*repaint)(void))
{
	int i, j;
#ifdef MSDOS
	union REGS regs;
#else
	int pos;
	struct vt_mode mode;
#endif

#if CONSOLE_DEBUG
	dprintf("console_init: screen_fd %d\r\n", screen_fd);
#endif

	if (screen_fd == -1) {
		font_width = CELL_WID;
		font_height = CELL_TOTAL_LEN;

		memset(font_data, '\0', sizeof(font_data));

#ifdef MSDOS
		screen_fd = 0;
#   if !console_segmented
#	if	GNUDOS
		screen_mem = (char *)0xD0000000;
#	elif	__ZTC__
		screen_mem = (char *)_x32_zero_base_ptr + (regen << 4);
#	else
		screen_mem = FIXME;
#	endif
#   endif
#else
		screen_fd = open("/dev/tty", O_RDWR);
		if (screen_fd == -1) return quit("/dev/tty");

		if (ioctl(screen_fd, GIO_FONT8x16, font_data) == -1) {
#ifdef TEST_MAIN
			screen_fd = open("/dev/vga", O_RDWR);
			if (screen_fd == -1) return quit("/dev/vga");
			if (ioctl(screen_fd, GIO_FONT8x16, font_data) == -1)
#endif
				return quit("get font");
		}
#endif

		if (console_read_font() != 0) {
#if MSDOS
			return quit("console_read_font");
#endif
		}

		for (i = 0; i < MAP_SIZE; i++) {
			for (j = 1; j < CELL_LEN; j++)
				if (font_data[i][j] != font_data[i][0])
					break;
			box[ i ] = 0;
			if (j >= CELL_LEN)
				box[i] |= BOX_ALL;
			if (j > 2 && font_data[i][0] != 0)
				box[i] |= BOX_TOP;
			if (font_data[i][CELL_LEN-1] != 0 &&
			    font_data[i][CELL_LEN-1] == font_data[i][CELL_LEN-2] &&
			    font_data[i][CELL_LEN-1] == font_data[i][CELL_LEN-3])
				box[i] |= BOX_BOT;
		}

#ifdef MSDOS
#else
		pos = 0;
		for (i = 0; i < MOUSE_SIZE; i++) {
			for (j = 0; j < MOUSE_SIZE; j++) {
				mouse_mask[ pos ] = 0;
				if (mouse_bitmap[pos] != OFF ||
				    (i > 0 &&
				    	mouse_bitmap[pos-MOUSE_SIZE] != OFF) ||
				    (i < MOUSE_SIZE-1 &&
				    	mouse_bitmap[pos+MOUSE_SIZE] != OFF) ||
				    (j > 0 &&
				    	mouse_bitmap[pos-1] != OFF) ||
				    (j < MOUSE_SIZE-1 &&
				    	mouse_bitmap[pos+1] != OFF))
					mouse_mask[pos] = 1;
				pos++;
			}
		}
#endif
	}

	console_repaint = repaint;

	protect_disk();

	if (text_mode == 0) {
#ifdef MSDOS
		regs.h.ah = 0xf;
		int86(0x10, &regs, &regs);
		text_mode = regs.h.al;
#else
		text_mode = ioctl(screen_fd, CONS_GET, 0L);
#endif
	}

#ifdef MSDOS
#else
	if ( -1 == ioctl(screen_fd, VGA_IOPRIVL, 1L) )
		return quit("VGA_IOPRIVL");
#endif

	console_graphics_mode(FALSE);

#ifdef MSDOS
#else
	if(0 == (screen_mem = (char *)ioctl(screen_fd, MAPCONS, 0L)))
		return quit("MAPCONS");

	mode.mode = VT_PROCESS;
	mode.waitv = 0;
	mode.relsig = SIG_RELEASE;
	mode.acqsig = SIG_ACQUIRE;
	mode.frsig = SIGINT;

	if (ioctl(screen_fd, VT_SETMODE, &mode) == -1)
		return quit("VT_SETMODE");

	signal(SIG_RELEASE, release);
	signal(SIG_ACQUIRE, acquire);
#endif

#if CONSOLE_DEBUG
	dprintf("console_init: returning\r\n");
#endif

	return(0);
}

int
console_in_graphics_mode(void)
{
	int i, mode;
#ifdef MSDOS
	union REGS regs;
#endif

	if (screen_fd == -1)
		return(0);

#ifdef MSDOS
	regs.h.ah = 0xf;
	int86(0x10, &regs, &regs);
	mode = regs.h.al;
	i = (mode == 0x12);
#else
	mode = ioctl(screen_fd, CONS_GET, 0L);
	i = (mode == M_VGA12);
#endif

	if (i) i = TRUE;

#if CONSOLE_DEBUG
	dprintf("in_console_graphics_mode: mode %d -> %d\n", mode, i);
#endif

	return(i);
}

int
console_finish(void)
{
	int i;

#ifdef MSDOS
#else
	struct vt_mode mode;

	if (screen_fd == -1)
		return(0);
#endif

#if CONSOLE_DEBUG
	dprintf("console_finish: top\r\n");
#endif

	i = console_text_mode(TRUE);
#ifdef MSDOS
#else
	mode.mode = VT_AUTO;
	mode.waitv = 0;
	mode.relsig = SIG_RELEASE;
	mode.acqsig = SIG_ACQUIRE;
	mode.frsig = SIGINT;

	if (ioctl(screen_fd, VT_SETMODE, &mode) == -1)
		quit("VT_SETMODE auto");

	signal(SIG_RELEASE, SIG_IGN);
	signal(SIG_ACQUIRE, SIG_IGN);

	console_release();

	close(screen_fd);
#endif
	screen_fd = -1;

#if CONSOLE_DEBUG
	dprintf("console_finish: returning\r\n");
#endif

	return(i);
}

#ifdef MSDOS
#else
/*
** Read data under mouse from video memory
*/

static void console_read(int row, int col, unsigned char *data)
{
	int p, x, y, bit;
	register char console_far *byte;
	unsigned char mask;

	memset(data, 0, MOUSE_SIZE * MOUSE_SIZE);

	for(p = 0; p < 4; p++) {
		out_index2(GC_INDEX, GC_MAP, p);
		bit = (1 << p);
		for (x = 0; x < MOUSE_SIZE; x++) {
			mask = (unsigned char) (0x80 >> ((x+col)%8));
			/* byte = &screen_mem[ INDEX((x+col)/8,row,SCREEN_X/8) ]; */
			byte = mk_fb_ptr(x+col, row);
			for (y = 0; y < MOUSE_SIZE; y++) {
				if (*byte & mask)
					data[INDEX(x,y,MOUSE_SIZE)] |= bit;
				byte += (SCREEN_X / 8);
			}
		}
	}

	out_index2(SQ_INDEX, SQ_MAP, 0x0F);
	out_index2(GC_INDEX, GC_MODE, 2);	/* write mode 2 */
}

/*
** Write data (mouse or under mouse) to video memory
*/

static void console_write(int row, int col, unsigned char *data)
{
	int x, y;
	register char console_far *byte;
	unsigned char mask, junk;

	set_mask_mode();

	for (x = 0; x < MOUSE_SIZE; x++) {
		mask = (unsigned char) (0x80 >> ((x+col)%8));
		out_mask(mask);

		/* byte = &screen_mem[ INDEX( (x+col)/8, row, SCREEN_X/8 ) ]; */
		byte = mk_fb_ptr(x+col, row);

		for (y = 0; y < MOUSE_SIZE; y++) {
			if (mouse_mask[ INDEX(x, y, MOUSE_SIZE) ]) {
				console_volatile junk = *byte;
				*byte = (char) data[ INDEX(x, y, MOUSE_SIZE) ];
			}
			byte += (SCREEN_X / 8);
		}
	}
}

/*
** Draw the mouse
**   clears the previous mouse image
**   set to -1 -1 to make the mouse go away
*/

void console_place_mouse(int row, int col)
{
	if (mouse_row >= 0 && mouse_col >= 0)
		console_write(mouse_row, mouse_col, under_mouse);

	if (row >= 0 && col >= 0) {
		if (row >= SCREEN_Y - MOUSE_SIZE) row = SCREEN_Y - MOUSE_SIZE;
		if (col >= SCREEN_X - MOUSE_SIZE) col = SCREEN_X - MOUSE_SIZE;
		console_read(row, col, under_mouse);
		console_write(row, col, mouse_bitmap);
		mouse_row = row;
		mouse_col = col;
	} else {
		mouse_row = mouse_col = -MOUSE_SIZE;
	}
}
#endif

/*
** Check mouse for overlap
*/

static int mouse_save_row = -1;
static int mouse_save_col = -1;
static int mouse_saved = 0;

static void
console_hide_mouse(int row, int col, int len, int wid)
{
	mouse_saved = 0;
#if MSDOS
#else
	if (mouse_row >= 0 && mouse_col >= 0 &&
	    mouse_row + MOUSE_SIZE - 1 >= row &&
	    mouse_row <= row + len - 1 &&
	    mouse_col + MOUSE_SIZE - 1 >= col &&
	    mouse_col <= col + wid - 1) {
		mouse_save_row = mouse_row;
		mouse_save_col = mouse_col;
		mouse_saved = 1;
		console_place_mouse(-1, -1);
	}
#endif
}

static void
console_restore_mouse()
{
	if (mouse_saved) {
#if MSDOS
#else
		console_place_mouse(mouse_save_row, mouse_save_col);
#endif
	}
}

/*
** Clear the screen to black
*/

int
console_clear()
{
	console_hide_mouse(0, 0, SCREEN_Y, SCREEN_X);
	out_index2(GC_INDEX, GC_MASK, 0xFF);
#if console_segmented
	{
		int row, col;
		char console_far *b;
		for (row = 0; row < SCREEN_Y; row++) {
			b = mk_fb_ptr(0, row);
			for (col = 0; col < SCREEN_X / 8; col++) {
				*b++ = 0;
			}
		}
	}
#else
	memset(screen_mem, 0, SCREEN_Y*(SCREEN_X/8));
#endif
	console_restore_mouse();
	return(0);
}

/*
** Set the current colors
*/

void console_setcolor(int color)
{
	for_color = WIN_GET_FORB(color) | (color & WIN_GRAPHIC);
	bac_color = WIN_GET_BAC(color);
}


/*
** Set the current screen location
*/

void console_gotorc(int row, int col)
{
#if 0
	if (row < 1 || row > 24 ||
	    col < 1 || col > SCREEN_X / CELL_WID) {
		fprintf(stderr, "gotorc: r %d c %d\r\n", row, col);
	}
#endif

	if (row < 1) row = 1;
	if (row > 24) row = 24;
	cell_row = row;

	if (col < 1) col = 1;
	if (col > SCREEN_X / CELL_WID) col = SCREEN_X / CELL_WID;
	cell_col = col;
}

/*
** Write a string at the current cell, can handle backspaces
*/

void console_putstr(const char *str, int len)
{
	int i, j, ch;
	unsigned char mask;
	char x;
	char console_far *b;

	/* make sure that we are in graphics mode */

	if (!in_graphics_mode)
		console_graphics_mode(TRUE);

	/* check parameters */

	if (cell_col < 1) cell_col = 1;
	if (cell_col > SCREEN_X / CELL_WID) cell_col = SCREEN_X / CELL_WID;
#if 0
	/* The test below was meant to avoid wrapping. */
	/* This could truncate strings of backspaces. */
	/* The main loop handles wrapping at the end. */
	if (cell_col + len > SCREEN_X / CELL_WID + 1)
		len = SCREEN_X / CELL_WID + 1 - cell_col;
#endif

	if (cell_row < 1) cell_row = 1;
	if (cell_row > 24) cell_row = 24;

	/* hide the mouse, does not account for control characters */

	console_hide_mouse((cell_row - 1) * CELL_TOTAL_LEN,
				(cell_col - 1) * CELL_WID,
				CELL_TOTAL_LEN,
				len * CELL_WID);

	/* row & col are 1-based */

	set_mask_mode();

	for (i = 0; i < len; i++) {

		ch = (unsigned char) str[i];

#if 0
		fprintf(stderr, "putstr: %d '%c' at %d %d\r\n",
			ch, ch, cell_row, cell_col);
#endif

		if (ch == CONSOLE_BS) {
			if (cell_col > 1) --cell_col;
			continue;
		}

		if (for_color & WIN_GRAPHIC)
			ch += 256;

		/* mem_byte = ((cell_row - 1) * CELL_TOTAL_LEN) *
				(SCREEN_X / 8) - 1; */

#if 0
		fprintf(stderr,
			"putstr cell %d %d ch %d %c\r\n",
			cell_row, cell_col, ch, ch);
#endif

		if (box[ ch ] & BOX_ALL) {

			mask = font_data[ ch ][ 0 ];

			if (mask != 0) {
				out_mask(mask);
				/* b = &screen_mem[ mem_byte + cell_col ]; */
				b = mk_fb_ptr((cell_col-1)<<3, (cell_row-1) * CELL_TOTAL_LEN);
				for (j = 0; j < CELL_TOTAL_LEN; j++) {
					console_volatile x = *b;/* fake read sets up port */
					*b = (char) for_color;	/* write bits */
					b += (SCREEN_X / 8);
				}
			}

			if (mask != 0xFF) {
				out_mask(~mask);
				b = mk_fb_ptr((cell_col-1)<<3, (cell_row-1) * CELL_TOTAL_LEN);
				for (j = 0; j < CELL_TOTAL_LEN; j++) {
					console_volatile x = *b;
					*b = (char) bac_color;
					b += (SCREEN_X / 8);
				}
			}

		} else {
			if (box[ ch ] & BOX_TOP)
				mask = font_data[ ch ][ 0 ];
			else
				mask = 0;

			b = mk_fb_ptr((cell_col-1)<<3, (cell_row-1) * CELL_TOTAL_LEN);

			for (j = 0; j < CELL_FILL_TOP; j++) {
				if (mask != 0) {
					out_mask(mask);
					console_volatile x = *b;
					*b = (char) for_color;
				}
				if (mask != 0xFF) {
					out_mask(~mask);
					console_volatile x = *b;
					*b = (char) bac_color;
				}
				b += (SCREEN_X / 8);
			}

			for (j = 0; j < CELL_LEN; j++) {
				mask = font_data[ ch ][ j ];

				if (mask != 0) {
					out_mask(mask);
					console_volatile x = *b;/* fake read sets up port */
					*b = (char) for_color;	/* write bits */
				}
				if (mask != 0xFF) {
					out_mask(~mask);
					console_volatile x = *b;
					*b = (char) bac_color;
				}
				b += (SCREEN_X / 8);
			}

			if ((box[ ch ] & BOX_BOT) == 0)
				mask = 0;

			for (j = 0; j < CELL_FILL_BOT; j++) {
				if (mask != 0) {
					out_mask(mask);
					console_volatile x = *b;
					*b = (char) for_color;
				}
				if (mask != 0xFF) {
					out_mask(~mask);
					console_volatile x = *b;
					*b = (char) bac_color;
				}
				b += (SCREEN_X / 8);
			}
		}

		cell_col++;

		if (cell_col > SCREEN_X / CELL_WID) {
			cell_col = 1;
			if (cell_row < 24) cell_row++;
#if 0
			fprintf(stderr, "putstr: wrapped\r\n");
#endif
		}
	}

	console_restore_mouse();
}

/*
** Draw a string within a pixel area, does not change background
*/

void console_drawstr(int row, int col, int len, int wid,
		const char *str, int slen, int color)
{
	int max_slen, mem_bit, i, j, ch;
	unsigned char mask, full_mask;
	char fcolor, x;
	char console_far *b;

#if 0
	fprintf(stderr, "drawstr str %d '%.*s' r %d c %d l %d w %d cl x%x\n",
		slen, slen, str, row, col, len, wid, color);
#endif

	if (len < CELL_LEN) {
#if 0
		fprintf(stderr, "drawstr, error, need len of %d\n", CELL_LEN);
#endif
		return;
	}

	max_slen = wid / CELL_WID;

	if (slen > max_slen)
		slen = max_slen;

	if (slen <= 0) {
#if 0
		fprintf(stderr, "drawstr, error, max_slen is %d\n", max_slen);
#endif
		return;
	}

	if (row < 0 || row + len > SCREEN_Y ||
	    col < 0 || col + wid > SCREEN_X ||
	    !str) {
#if 0
		fprintf(stderr,
		    "drawstr, invalid location r %d c %d l %d w %d str %ld\n",
			row, col, len, wid, (long)str);
#endif
		return;
	}

	console_hide_mouse(row, col, CELL_TOTAL_LEN, slen * CELL_WID);

	console_setcolor(color);

	fcolor = (char) for_color;

#if 0
	start_byte = (row * (SCREEN_X / 8)) + (col / 8);
	if (len >= CELL_TOTAL_LEN)
		start_byte += ((SCREEN_X / 8) * CELL_FILL_TOP);
#endif

	if (len >= CELL_TOTAL_LEN)
		row += CELL_FILL_TOP;

	mem_bit = col % 8;

	set_mask_mode();

	for (i = 0; i < slen; i++) {

		/* mem_byte = start_byte + i; */

		ch = (unsigned char) str[i];
		if (for_color & WIN_GRAPHIC)
			ch += 256;

#if 0
		fprintf(stderr, "drawstr i %d ch %d fc x%x\n",
			i, ch, fcolor);
#endif

		b = mk_fb_ptr(col, row);

		col += CELL_WID;

		for (j = 0; j < CELL_LEN; j++) {
			full_mask = font_data[ ch ][ j ];

			mask = (unsigned char) (full_mask >> mem_bit);

			if (mask != 0) {
				out_mask(mask);
				console_volatile x = *b; /* fake read sets up port */
				*b = fcolor;	/* write bits */
			}

			mask = (unsigned char) (full_mask << (8 - mem_bit));

			if (mask != 0) {
				out_mask(mask);
				console_volatile x = b[1]; /* fake read sets up port */
				b[1] = fcolor;	/* write bits */
			}

			b += (SCREEN_X / 8);
		}
	}
	console_restore_mouse();
}

#if 0
static void console_dot(int color, register int x, register int y)
{
	register char console_far *byte;
	unsigned char mask;

	byte = &screen_mem[ INDEX( x/8, y, SCREEN_X/8 ) ];
	mask = (unsigned char) (0x80 >> (x%8));

	out_index2(GC_INDEX, GC_MASK, mask);

	console_volatile x = *byte;	/* HW magic, must read video, x is not important
  */
			/* see: page 154 of EGA/VGA A programmer's Ref Guide */
	*byte = (char) color;
}

static void
console_horz_line(int color, int y, int x1, register int x2)
{
	register char console_far *byte;
	register int cnt;
	int junk, dx;

/*
 *  Compute the distance to the next byte boundary.
 * If you are on a byte boundary then distance=0;
 * and make sure the line extends that far.
 */
	dx = x2-x1+1;
	cnt = 8 - (x1 % 8);
	cnt = (8==cnt) ? 0 : cnt;
	cnt = (cnt > dx) ? dx : cnt;
	while(--cnt >= 0)
		console_dot(color, x1++, y);
/*
 *  Compute the number of whole bytes in the line
 * set the color mask to 0xFF and hammer them out.
 */
	cnt = (x2-x1)/8;
	byte = &screen_mem[ INDEX( x1/8, y, SCREEN_X/8 ) ];
	out_index2(GC_INDEX, GC_MASK, 0xFF);
	while(--cnt >= 0)
	{
		console_volatile junk = *byte;
		*byte++ = (char) color;
		x1 += 8;
	}
/*
 * Complete the line.  Same as above.
 */
	dx = x2-x1+1;
	cnt = (x2 % 8)+1;
	cnt = (cnt > dx) ? dx : cnt;
	while(--cnt >= 0)
		console_dot(color, x2--, y);
}

static void
console_vert_line(int color, register int x, int y1, int y2)
{
	register char console_far *byte;
	unsigned char mask;
	register int cnt;

	byte = &screen_mem[ INDEX( x/8, y1, SCREEN_X/8 ) ];
	mask = (unsigned char) (0x80 >> (x%8));

	out_index2(GC_INDEX, GC_MASK, mask);

	cnt = y2 - y1 + 1;
	while(--cnt >= 0)
	{
		console_volatile x = *byte;	/* HW magic, must read video */
		*byte = (char) color;
		byte += SCREEN_X/8;
	}
}
#endif


static void
console_box2(int y1, int x1, int y2, int x2, int color)
{
	int y;
	int start_byte, end_byte;
	int start_bit, end_bit;
	unsigned char start_mask, end_mask, bcolor;
	char console_far *b;
	char x;

	if (x1 < 0) x1 = 0;
	if (x2 > SCREEN_X) x2 = SCREEN_X;
	if (x1 > x2) return;

	start_bit = x1 % 8;
	start_mask = (unsigned char) (0xFF >> start_bit);

	end_bit = x2 % 8;
	end_mask = (unsigned char) (0xFF << (7 - end_bit));

	if (y1 < 0) y1 = 0;
	if (y2 > SCREEN_Y) y2 = SCREEN_Y;
	if (y1 > y2) return;

	bcolor = (unsigned char) color;

	set_mask_mode();

	if (x1 == x2 && y1 == y2) {
		/* just a dot */
		b = &screen_mem[ INDEX( x1/8, y1, SCREEN_X/8 ) ];
		start_mask = (unsigned char) (0x80 >> (x1%8));
		out_mask(start_mask);
		console_volatile x = *b;
		*b = bcolor;
		return;
	}

#if console_segmented
	start_byte = INDEX( x1/8, 0, SCREEN_X/8 );
	end_byte = INDEX( x2/8, 0, SCREEN_X/8 );

	if (start_byte == end_byte)
		start_mask &= end_mask;

	if (end_mask == 0xFF) { end_byte++; end_mask = 0; }

	for (y = y1; y <= y2; y++) {

		/* paint odd bits on left side */

		b = mk_fb_ptr(0, y);
		b += start_byte;

		out_mask(start_mask);
		console_volatile x = *b;
		*b = bcolor;
		if (start_byte >= end_byte) continue;
		b++;

		/* paint middle */

		if (start_byte + 1 < end_byte) {
			int cnt;

			if (start_mask != 0xFF)
				out_mask(0xFF);
			cnt = end_byte - start_byte - 1;
			while (cnt-- > 0) {
				console_volatile x = *b; *b = bcolor; b++;
			}
		}

		if (end_mask == 0) continue;

		/* paint odd bits on right */

		out_mask(end_mask);
		console_volatile x = *b;
		*b = bcolor;
	}
#else
	start_byte = INDEX( x1/8, y1, SCREEN_X/8 );
	end_byte = INDEX( x2/8, y1, SCREEN_X/8 );

	if (start_byte == end_byte)
		start_mask &= end_mask;

	/* paint odd bits on left side */

	if (start_mask != 0xFF) {
		out_mask(start_mask);
		b = &screen_mem[ start_byte ];

		for (y = y1; y <= y2; y++) {
			console_volatile x = *b;
			*b = bcolor;
			b += (SCREEN_X / 8);
		}

		if (start_byte >= end_byte)
			return;

		start_byte++;
	}

	/* paint middle, horiz then vert to avoid flicker */

	if (start_byte < end_byte) {

		int mem_byte = start_byte;

		if (end_mask == 0xFF) {
			end_byte++;
			end_mask = 0;
		}

		out_mask(0xFF);

		for (y = y1; y <= y2; y++) {

			int cnt = end_byte - start_byte;

			b = &screen_mem[ mem_byte ];

			while (cnt-- > 0) {
				console_volatile x = *b;
				*b = bcolor;
				b++;
			}

			mem_byte += (SCREEN_X / 8);
		}
	}

	if (end_mask == 0) {
		return;
	}

	/* paint odd bits on right */

	out_mask(end_mask);
	b = &screen_mem[ end_byte ];

	for (y = y1; y <= y2; y++) {
		console_volatile x = *b;
		*b = bcolor;
		b += (SCREEN_X / 8);
	}
#endif
}


/*
** Fill in a box
*/

void console_box(int row, int col, int len, int wid, int color)
{
	int fill_color;

#if 0
	fprintf(stderr, "console_box, r %d c %d l %d w %d c x%x\n",
	  row, col, len, wid, color);
#endif

	fill_color = WIN_GET_BACB(color);

#if 1
	if (len <= 1) len = 1;
	if (wid <= 1) wid = 1;
	if (mouse_row + MOUSE_SIZE - 1 >= row && mouse_row <= row + len - 1 &&
	    mouse_col + MOUSE_SIZE - 1 >= col && mouse_col <= col + wid - 1) {
		console_hide_mouse(row, col, len, wid);
	}
	console_box2(row, col, row + len - 1, col + wid - 1, fill_color);
	if (mouse_saved) {
		console_restore_mouse();
		mouse_saved = 0;
	}
#else

	if (wid <= 1) {
		if (len <= 1) {
			console_dot(fill_color, col, row);
		} else {
			console_vert_line(fill_color, col, row, row + len - 1);
		}
	} else {
		if (len <= 1) {
			console_horz_line(fill_color, row, col, col + wid - 1);
		} else {
			while (len > 0) {
				console_horz_line(fill_color,
						row, col, col + wid - 1);
				row++;
				len--;
			}
		}
	}
#endif
}

/*
** get size of display area in pixels
*/

void
console_displaysize(int *len, int *wid)
{
  *len = CELL_TOTAL_LEN * 24;
  *wid = CELL_WID * 80;
}

/*
** get size of a character cell in pixels
*/

void
console_fontsize(int *len, int *wid)
{
  *len = font_height;
  *wid = font_width;
}

/*
** Beep
*/

void console_beep(void)
{
#ifdef MSDOS
	putchar('\007');
#else
	if (screen_fd != -1) {
		/* tone arg is in units of 840.3 nanoseconds */
		ioctl(screen_fd, KIOCSOUND, 1200);	/* start tone */
		nap(80);				/* keep it playing */
		ioctl(screen_fd, KIOCSOUND, 0);		/* stop tone */
	}
#endif
}

#ifdef TEST_MAIN

int quit_requested = 0;

int getvalue PP((const char *mesg, int min, int max));

int getvalue(mesg, min, max)
const char *mesg;
int min, max;
{
	char buf[80];
	int v;

	if (quit_requested) return(min);
	printf("Enter %s [%d:%d] > ", mesg, min, max);
	gets(buf);
	if (buf[0] == 'q') quit_requested = 1;
	v = atoi(buf);
	if (v == 0 && buf[0] != '0') v = buf[0];
	if (v < min) v = min;
	if (v > max) v = max;
	printf("  %s -> %d\n", mesg, v);
	return(v);
}

int main(int argc, char *argv[])
{
	int row, row2, col, i;
	int color;
	char ch;

	UNUSED_ARG(argc);
	UNUSED_ARG(argv);

	if (console_init((void (*) (void)) (NULL))) {
		fprintf(stderr, "Could not initialize console.\r\n");
		return(EXIT_FAILURE);
	}

	row = 10*CELL_TOTAL_LEN;
	col = 40*CELL_WID;

	console_box(0, 0, row, col, WIN_BAC_GREEN);
	console_box(2, 2, row-4, col-4, WIN_BAC_BLUE);
	console_box(row, col, row, 0, WIN_BAC_CYAN);
	console_box(row, col, 0, col, WIN_BAC_MAGENTA);
	console_box(row, col, 0, 0, WIN_BAC_RED);

	row2 = 0;

	for (i = 0; row2 + CELL_TOTAL_LEN <= row; i++) {
		console_drawstr(row2, i, row - row2, col - i,
			"Console Test Program", 20,
			WIN_FOR_YELLOW | WIN_BAC_WHITE);
		row2 += (CELL_LEN - 2);
	}

	row++;	col++;

	for (i = 0; row < 20 * CELL_TOTAL_LEN; i++) {
		console_drawstr(row, col,
			CELL_LEN, 20 * CELL_WID,
			"Console Test Program", 20,
			WIN_FOR_YELLOW | WIN_BAC_WHITE);
		row += (CELL_LEN - 2);
		col += 1;
	}

#if CONSOLE_DEBUG && 0
	printf("console_in_graphics_mode is %d\n", console_in_graphics_mode());
	getvalue("enter text mode", 0, 512);
	console_text_mode(TRUE);
	printf("console_in_graphics_mode is %d\n", console_in_graphics_mode());	
	getvalue("return to graphics mode", 0, 512);
	console_graphics_mode(TRUE);
	printf("console_in_graphics_mode is %d\n", console_in_graphics_mode());	
#endif

	while (!quit_requested) {
		row = getvalue("row", 0, 24);
		col = getvalue("col", 0, 79);
		i = getvalue("ch", 0, 512);
		color = getvalue("color", 0, 255);
		if (i >= 256) {
			i -= 256;
			color |= WIN_GRAPHIC;
		}
		ch = (char) i;

		if (quit_requested) break;

		if (i == 'm') {
#if MSDOS
#else
			console_place_mouse(row, col);
#endif
		} else if (i == 'b') {
			console_beep();
		} else if (i == 'r') {
			if (console_finish()) {
				fprintf(stderr, "console_finish failed\n");
				break;
			}
			if (console_init(NULL)) {
				fprintf(stderr, "console_init failed\n");
				break;
			}
		} else {
			console_setcolor(color);
			console_gotorc((int)row + 1, (int)col + 1);
			console_putstr(&ch, 1);
		}
	}

	console_finish();

	return(EXIT_SUCCESS);
}

#endif

#else

int console_hello() { return(0); }

#endif /* WIN_USE_CONSOLE */
