/*
 *  Module	v_doscur		MSDOS-specific video routines
 *  Author	larry gensch, ESQ
 *
 *  Copyright (c) 1987, 1988, 1989
 *	by Larry Gensch  /  104 Lowell Road  /  Salem, NH  03079
 *
 *  This code may be included in any work, public or private, with the
 *  exception of creating a commercial curses-compatible subroutine
 *  library.  (In other words, use the code all you want, but please don't
 *  rip off the author by reselling this code as your own).
 *
 *  If you make modifications to this code (specifically, enhancements that
 *  are compatible with System V.x curses), please send them to larry gensch at
 *  the address above to be considered for inclusion in subsequent releases.
 *  Any machine specific implementation modules (similar to v_doscur.c) for
 *  other machines are welcomed.
 *
 *  v_doscur.c is a machine-specific driver for curses that writes directly to
 *  the memory mapped video display.  Currently, there is NO support for color
 *  or routines using PADS (windows larger than the physical terminal screen).
 *  To port curses to a new machine, this is the module that will have to be
 *  reimplemented.
 *  
 */

#include "curses.h"

#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>

/*
 * EXPORT is used to show entry points into this module.  Simply grep
 * using the pattern '^EXPORT' to list them out.
 */

#ifndef EXPORT
#define EXPORT
#endif

static unsigned	GetDS	(void);
static void	Doinit		(int *lines, int *cols);
static void	Mapscreen	(WINDOW *win);
static void	Dispscreen	(void);
static void	Setcursor	(int line, int col);
static void	Dosrestore	(void);
static bool	Kbhit		(void);
static int	Getkey		(bool raw);

/*
 * ms_init() - Perform any necessary initializations of the terminal or to
 * the implementation module.  The LINES and COLS variables should be set
 * as well.
 */

EXPORT bool	ms_init		(void)
{
	Doinit(&LINES, &COLS);

	return TRUE;
}

/*
 * ms_refresh() - Refresh a WINDOW image onto our internal representation
 * of our screen.  outflag specifies whether or not the actual display
 * should be updated.  Returns ERR if an error occurred.
 *
 * Note:  If win is NULL, just update the display.
 */

EXPORT int	ms_refresh	(WINDOW *win, bool outflag)
{
	static int	Lastcol = 0;	/* Save latest cursor position.  No provision here for */
	static int	Lastrow = 0;	/*   invisible cursor. */

	if (win)
		Mapscreen(win);

	if (win) {
		if (! win->_leave) {
			Lastrow = _CURS_CURSOR_SCREEN_ROW(win);
			Lastcol = _CURS_CURSOR_SCREEN_COL(win);
		}
	}

	if (outflag) {
		Dispscreen();
		Setcursor(Lastrow, Lastcol);
	}

	return OK;
}

/*
 * ms_end() - Restore the display so that a non-curses application can use
 * it.  (Restore terminal modes, move cursor to lower left corner, etc.)
 */

EXPORT bool	ms_end		(void)
{
	Setcursor(LINES - 1, 0);
	return OK;
}

/*
 * ms_restore_scrn() - Allows user to repaint screen as it was before curses initialization.
 */

EXPORT void ms_restore_scrn	(void)
{
	Dosrestore();
}

/*
 * ms_beep() - Sound the workstation alarm.  If flag is TRUE, then the
 * workstation should be flashed, instead, if possible.
 */

EXPORT void	ms_beep		(int flag)
{
	fputc('\a', stderr);
}

/*
 * ms_kbinp() - Get input from keyboard.
 */

EXPORT int	ms_kbinp	(WINDOW *win, bool raw, bool cbreak)
{
	if (win->_nodelay) {
		if (! Kbhit())
			return ERR;
	}

	Setcursor(_CURS_CURSOR_SCREEN_ROW(win),
								_CURS_CURSOR_SCREEN_COL(win));
	return Getkey(raw);
}

/*
 * ms_flushinp() - Throw away pending keyboard input.
 */

EXPORT void ms_flushinp	(void)
{
	while (Kbhit()) getch();
}


/*
 * Alternate Character Set table:
 *
 * Line drawing characters are named as follows:
 *
 *		t
 *
 *		|
 *	l     --+--	r
 *		|
 *
 *		b
 *
 * ACS_trbl (t=top, r=right, b=bottom, l=left)
 *
 * Valid values for each placeholder:
 *
 * B (blank)	S (single)	D (double)	T (thick)
 *
 * Curses (currently) only supports B and S.
 */

EXPORT chtype	acs_map[256] = {
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x801a, 0x801b, 0x8018, 0x8019, 0x0   ,
	0x805b, 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x0   ,
	0x8004, 0x8030, 0x0   , 0x0   , 0x0   , 0x0   , 0x8078, 0x8071,
	0x8032, 0x8068, 0x8059, 0x803f, 0x805a, 0x8040, 0x8045, 0x805f,
	0x0   , 0x8044, 0x0   , 0x802d, 0x8043, 0x8034, 0x8041, 0x8042,
	0x8033, 0x0   , 0x0   , 0x0   , 0x0   , 0x0   , 0x8007, 0x0   ,
};

/* -------------------------------------------- */
/*        Private routines and variables        */
/* -------------------------------------------- */

static const unsigned	Monobase = 0xB000;	
static const unsigned	Colrbase = 0xB800;

#if defined (__WATCOMC__) && defined (__386__)
#	define INT86_FN int386
#else
#	define INT86_FN int86
#endif

typedef struct CHAR {
	char	ch;
	char	at;
} CHAR;

static CHAR	*Screen;		/* PC Screen image		*/
static void		*Oldscreen = NULL;	/* Saved screen image */
static unsigned Screenbase = 0;		/* Segment for OUR machine	*/

#define VIDEO_INT	0x10		/* Video interrupt		*/

#define CUR_SIZE	0x1		/* Set cursor size		*/
#define SET_POSN	0x2		/* Modify cursor posn		*/
#define GET_POSN	0x3		/* Read current cursor posn	*/
#define WRITE		0x9		/* Write character		*/
#define WRITE_TTY	0xe		/* Write char & move cursor	*/
#define GET_VMODE	0xf		/* Get video mode & disp pg	*/

#undef getch			/* Defn in conio.h		*/

#include <dos.h>
#include <string.h>
#include <conio.h>

static union REGS	Regs;			/* Used to talk to DOS	*/
static int		Xmax = 0;		/* Number of columns	*/
static int		Ymax = 0;		/* Number of rows	*/

#define ATTR_NORM	   0x07	/* Normal attr		*lg*	*/
#define ATTR_SOUT	   0x70	/* Standout attr	*lg*	*/
#define ATTR_UOUT	   0x01	/* Underline attr	*lg*	*/
#define	ATTR_BOUT	0x80	/* Blinking attr	*lg*	*/
#define	ATTR_BOLD	0x08	/* Bold attr		*lg*	*/

#define SCAN_BACKSPACE	'\b'		/* Backspace key	*/
#define	SCAN_EXIT	0x001B		/* Exit key (ESC)	*/
#define	SCAN_CBACKSPACE	0x007F		/* Ctrl + Backspace	*/
#define	SCAN_BTAB	0x010F		/* Shift + Tab key	*/
#define	SCAN_F1		0x013B		/* Function key 1	*/
#define SCAN_HOME	0x0147		/* Home key		*/
#define	SCAN_UP		0x0148		/* Up arrow		*/
#define	SCAN_PPAGE	0x0149		/* PgUp key		*/
#define	SCAN_LEFT	0x014B		/* Left arrow		*/
#define SCAN_RIGHT	0x014D		/* Right arrow		*/
#define	SCAN_END	0x014F		/* End key		*/
#define SCAN_DOWN	0x0150		/* Down arrow		*/
#define	SCAN_NPAGE	0x0151		/* PgDn key		*/
#define	SCAN_IC		0x0152		/* Ins key		*/
#define	SCAN_DC		0x0153		/* Del key		*/
#define	SCAN_SF1	0x0154		/* Shift + F Key 1	*/
#define	SCAN_CF1	0x015E		/* Ctrl + F Key 1	*/
#define	SCAN_AF1	0x0168		/* Alt + F Key 1	*/
#define	SCAN_SLEFT	0x0173		/* Ctrl + Left arrow	*/
#define	SCAN_SRIGHT	0x0174		/* Ctrl + Right arrow	*/
#define SCAN_SEND	0x0175		/* Ctrl + End key	*/
#define	SCAN_CNPAGE	0x0176		/* Ctrl + PgDn		*/
#define	SCAN_SHOME	0x0177		/* Ctrl + Home key	*/
#define SCAN_CPPAGE	0x0184		/* Ctrl + PgUp		*/

/*
 * GetDS() - Return the data segment as an unsigned value.
 */

static unsigned	GetDS		(void)
{
	struct SREGS seg;

	segread(&seg);

	return seg.ds;
}

/*
 * Doinit() - Perform our initializations, and set lines and columns.
 */

static void	Doinit		(int *lines, int *cols)
{
	register	int i;
	CHAR	*ptr;
	char *src_ptr;

	Regs.h.ah = GET_VMODE;

	INT86_FN( VIDEO_INT, &Regs, &Regs );

	*cols  = Xmax = Regs.h.ah;
	*lines = Ymax = 25;

	if (Screen == NULL)
		Screen = (CHAR *)malloc(Xmax * Ymax * sizeof(CHAR));

	if (Screen == NULL) {
		fputs("v_doscur: memory allocation failure.\n", stderr);
		exit(1);
	}
	
	for (ptr = Screen, i = *lines * *cols; i >= 0; i--, ptr++) {
		ptr->ch = ' ';
		ptr->at = ATTR_NORM;
	}

	if (Regs.h.al == 7)
		Screenbase = Monobase;
	else
		Screenbase = Colrbase;

	if (Oldscreen == NULL)
		Oldscreen = malloc(Xmax * Ymax * 2);

	if (Oldscreen == NULL) {
		fputs("v_doscur: memory allocation failure.\n", stderr);
		exit(1);
	}

#if defined (__WATCOMC__) && defined (__386__)
	src_ptr = (Screenbase << 4);
	memcpy(Oldscreen, src_ptr, Xmax * Ymax * 2);
#else
	movedata(Screenbase, 0, GetDS(), (unsigned) Oldscreen, Xmax * Ymax * 2);
#endif
}

/*
 * Mapscreen() - Copy the screen image given to us by CURSES into our
 * internal display representation.
 */

static void	Mapscreen	(WINDOW *win)
{
	int		maxx, maxy, i_rw, i;
	chtype	*src_ptr, attr;
	CHAR	*sptr, *dst_ptr;

	if (win->_begy < Ymax && win->_begx < Xmax)
	{
		dst_ptr = Screen + (win->_begy * Xmax) + win->_begx;
		if (win->_flags & _ISPAD)
		{
			maxx = win->_pmap_maxx - win->_pmap_orgx;				/* For pads, get # rows and columns to display. */
			maxy = win->_pmap_maxy - win->_pmap_orgy;
			src_ptr = win->_y +										/* Point to 1st displayed character in pad. */
							((win->_pmap_orgy * win->_xdim) +
							win->_pmap_orgx);
		}
		else
		{
			maxx = win->_maxx;
			maxy = win->_maxy;
			src_ptr = win->_y;
		}

		maxy = MIN(maxy, Ymax - win->_begy);
		maxx = MIN(maxx, Xmax - win->_begx);

		for (i_rw = 0; i_rw < maxy; i_rw++,							/* Copy text from window to screen image. */
							dst_ptr += Xmax, src_ptr += win->_xdim)
		{
			for (i = 0; i < maxx; i++)
			{
				sptr = dst_ptr + i;
				attr = *(src_ptr + i) & A_ATTRIBUTES;

				sptr->ch = *(src_ptr + i) & A_CHARTEXT;

				if (attr & A_ALTCHARSET)
					sptr->ch |= 0x80;

				sptr->at = ATTR_NORM;

				if (attr & A_STANDOUT)
					sptr->at = ATTR_SOUT;
				if (attr & A_REVERSE)
					sptr->at = ATTR_SOUT;
				if (attr & A_UNDERLINE)
					sptr->at = ATTR_UOUT;
				if (attr & A_BOLD)
					sptr->at |= ATTR_BOLD;
				if (attr & A_BLINK)
					sptr->at |= ATTR_BOUT;
			}
		}
	}
}

/*
 * Dispscreen() - Make the physical screen look like our internal screen.
 */

static void	Dispscreen	(void)
{
#if defined (__WATCOMC__) && defined (__386__)
	char *src_ptr = (Screenbase << 4);
	memcpy(src_ptr, Screen, Xmax * Ymax * 2);
#else
	movedata(GetDS(), (unsigned) Screen, Screenbase, 0, Xmax * Ymax * 2);
#endif
}

/*
 * Setcursor() - Set the cursor to the specified location.
 */

static void	Setcursor	(int line, int col)
{
	Regs.h.ah = GET_VMODE;
	INT86_FN( VIDEO_INT, &Regs, &Regs );

	Regs.h.dh = MIN(line, Ymax - 1);
	Regs.h.dl = MIN(col, Xmax - 1);
	Regs.h.ah = SET_POSN;
	INT86_FN(VIDEO_INT, &Regs, &Regs);
}

/*
 * Dosrestore() - Restore the screen to what it was before we started()'d
 */

static void	Dosrestore	(void)
{
	if (Oldscreen != NULL)
	{
#if defined (__WATCOMC__) && defined (__386__)
		char *scr_ptr = (Screenbase << 4);
		memcpy(scr_ptr, Oldscreen, Xmax * Ymax * 2);
#else
		movedata(GetDS(), (unsigned) Oldscreen, Screenbase, 0, Xmax * Ymax * 2);
#endif
	}
}

/*
 * Kbhit() - Returns TRUE if a character in the k/b buffer, else FALSE.
 */

static bool	Kbhit		(void)
{
	return kbhit();
}

static int	Getkey		(bool raw)
{
	int		c;

	if (raw)
		return getch();

	c = getch();
	if (c == 0)
		c = getch() | 0x100;

	if (c >= SCAN_F1 && c <= SCAN_F1+9)
		return KEY_F(c-SCAN_F1+1);
	if (c >= SCAN_SF1 && c <= SCAN_SF1+9)
		return KEY_F(c-SCAN_SF1+11);
	if (c >= SCAN_CF1 && c <= SCAN_CF1+9)
		return KEY_F(c-SCAN_CF1+21);
	if (c >= SCAN_AF1 && c <= SCAN_AF1+9)
		return KEY_F(c-SCAN_AF1+31);

	switch (c) {
		case SCAN_DOWN:		return KEY_DOWN;
		case SCAN_UP:		return KEY_UP;
		case SCAN_LEFT:		return KEY_LEFT;
		case SCAN_RIGHT:	return KEY_RIGHT;
		case SCAN_HOME:		return KEY_HOME;
		case SCAN_BACKSPACE:	return KEY_BACKSPACE;
		case SCAN_DC:		return KEY_DC;
		case SCAN_IC:		return KEY_IC;
		case SCAN_NPAGE:	return KEY_NPAGE;
		case SCAN_PPAGE:	return KEY_PPAGE;
		case SCAN_CNPAGE:	return KEY_SF;
		case SCAN_CPPAGE:	return KEY_SR;
		case SCAN_BTAB:		return KEY_BTAB;
		case SCAN_END:		return KEY_END;
		case SCAN_EXIT:		return KEY_EXIT;
		case SCAN_SEND:		return KEY_SEND;
		case SCAN_SHOME:	return KEY_SHOME;
		case SCAN_SLEFT:	return KEY_SLEFT;
		case SCAN_SRIGHT:	return KEY_SRIGHT;
		default:
			return c & 0xff;
	}
}
