/*	SCCS Id: @(#)nttty.c	3.3	2000/08/02
/* Copyright (c) NetHack PC Development Team 1993    */
/* NetHack may be freely redistributed.  See license for details. */

/* tty.c - (Windows NT) version */
/*                                                  
 * Initial Creation 				M. Allison	93/01/31 
 *
 */

#ifdef WIN32CON
#define NEED_VARARGS /* Uses ... */
#include "hack.h"
#include "wintty.h"
#include <sys\types.h>
#include <sys\stat.h>
#include "win32api.h"
#include <wincon.h>

void FDECL(cmov, (int, int));
void FDECL(nocmov, (int, int));

/*
 * The following WIN32 Console API routines are used in this file.
 *
 * CreateFile
 * GetConsoleScreenBufferInfo
 * GetStdHandle
 * SetConsoleCursorPosition
 * SetConsoleTextAttribute
 * SetConsoleCtrlHandler
 * PeekConsoleInput
 * ReadConsoleInput
 * WriteConsole
 */

/* Win32 Console handles for input and output */
HANDLE hConIn;
HANDLE hConOut;

/* Win32 Screen buffer,coordinate,console I/O information */
CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD ntcoord;
INPUT_RECORD ir;

/* Flag for whether NetHack was launched via the GUI, not the command line.
 * The reason we care at all, is so that we can get
 * a final RETURN at the end of the game when launched from the GUI
 * to prevent the scoreboard (or panic message :-|) from vanishing
 * immediately after it is displayed, yet not bother when started
 * from the command line. 
 */
int GUILaunched;
static BOOL FDECL(CtrlHandler, (DWORD));
 
# ifdef TEXTCOLOR
int ttycolors[CLR_MAX];
static void NDECL(init_ttycolor);
# endif

static char nullstr[] = "";
char erase_char,kill_char;

#define LEFTBUTTON  FROM_LEFT_1ST_BUTTON_PRESSED
#define RIGHTBUTTON RIGHTMOST_BUTTON_PRESSED
#define MIDBUTTON   FROM_LEFT_2ND_BUTTON_PRESSED
#define MOUSEMASK (LEFTBUTTON | RIGHTBUTTON | MIDBUTTON)

/*
 * Called after returning from ! or ^Z
 */
void
gettty(){

	erase_char = '\b';
	kill_char = 21;		/* cntl-U */
	iflags.cbreak = TRUE;
#ifdef TEXTCOLOR
	init_ttycolor();
#endif
}

/* reset terminal to original state */
void
settty(s)
const char *s;
{
	end_screen();
	if(s) raw_print(s);
}

/* called by init_nhwindows() and resume_nhwindows() */
void
setftty()
{
	start_screen();
}

static BOOL CtrlHandler(ctrltype)
DWORD ctrltype;
{
	switch(ctrltype) {
	/*	case CTRL_C_EVENT: */
		case CTRL_BREAK_EVENT:
			clear_screen();
		case CTRL_CLOSE_EVENT:
		case CTRL_LOGOFF_EVENT:
		case CTRL_SHUTDOWN_EVENT:
#ifndef NOSAVEONHANGUP
			if (program_state.something_worth_saving) {
				program_state.exiting++;
				(void) dosave0();
			}
#endif
			clearlocks();
			terminate(EXIT_FAILURE);
		default:
			return FALSE;
	}
}

/* called by init_tty in wintty.c for WIN32CON port only */
void
nttty_open()
{
        HANDLE hStdOut;
        long cmode;
        long mask;
        
	/* Initialize the function pointer that points to
         * the kbhit() equivalent, in this TTY case nttty_kbhit()
         */

	nt_kbhit = nttty_kbhit;

        /* The following 6 lines of code were suggested by 
         * Bob Landau of Microsoft WIN32 Developer support,
         * as the only current means of determining whether
         * we were launched from the command prompt, or from
         * the NT program manager. M. Allison
         */
        hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
        GetConsoleScreenBufferInfo( hStdOut, &csbi);
        GUILaunched = ((csbi.dwCursorPosition.X == 0) &&
                           (csbi.dwCursorPosition.Y == 0));
        if ((csbi.dwSize.X <= 0) || (csbi.dwSize.Y <= 0))
            GUILaunched = 0;
	hConIn = GetStdHandle(STD_INPUT_HANDLE);
	hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
#if 0
        /* Obtain handles for the standard Console I/O devices */
	hConIn = CreateFile("CONIN$",
			GENERIC_READ |GENERIC_WRITE,
			FILE_SHARE_READ |FILE_SHARE_WRITE,
			0, OPEN_EXISTING, 0, 0);					
	hConOut = CreateFile("CONOUT$",
			GENERIC_READ |GENERIC_WRITE,
			FILE_SHARE_READ |FILE_SHARE_WRITE,
			0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0);					        
#endif       
	GetConsoleMode(hConIn,&cmode);
#ifndef NO_MOUSE_ALLOWED
	mask = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
	       ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT;   
#else
	mask = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
	       ENABLE_MOUSE_INPUT | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT;   
#endif
	/* Turn OFF the settings specified in the mask */
	cmode &= ~mask;
	cmode |= ENABLE_MOUSE_INPUT;
	SetConsoleMode(hConIn,cmode);
	if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE)) {
		/* Unable to set control handler */
		cmode = 0; 	/* just to have a statement to break on for debugger */
	}
	get_scr_size();
}

/*
 *  Keyboard translation tables.
 *  (Adopted from the MSDOS port)
 */

#define KEYPADLO	0x47
#define KEYPADHI	0x53

#define PADKEYS 	(KEYPADHI - KEYPADLO + 1)
#define iskeypad(x)	(KEYPADLO <= (x) && (x) <= KEYPADHI)

/*
 * Keypad keys are translated to the normal values below.
 * Shifted keypad keys are translated to the
 *    shift values below.
 */

static const struct pad {
	uchar normal, shift, cntrl;
} keypad[PADKEYS] = {
			{'y', 'Y', C('y')},		/* 7 */
			{'k', 'K', C('k')},		/* 8 */
			{'u', 'U', C('u')},		/* 9 */
			{'m', C('p'), C('p')},		/* - */
			{'h', 'H', C('h')},		/* 4 */
			{'g', 'g', 'g'},		/* 5 */
			{'l', 'L', C('l')},		/* 6 */
			{'p', 'P', C('p')},		/* + */
			{'b', 'B', C('b')},		/* 1 */
			{'j', 'J', C('j')},		/* 2 */
			{'n', 'N', C('n')},		/* 3 */
			{'i', 'I', C('i')},		/* Ins */
			{'.', ':', ':'}			/* Del */
}, numpad[PADKEYS] = {
			{'7', M('7'), '7'},		/* 7 */
			{'8', M('8'), '8'},		/* 8 */
			{'9', M('9'), '9'},		/* 9 */
			{'m', C('p'), C('p')},		/* - */
			{'4', M('4'), '4'},		/* 4 */
			{'g', 'G', 'g'},		/* 5 */
			{'6', M('6'), '6'},		/* 6 */
			{'p', 'P', C('p')},		/* + */
			{'1', M('1'), '1'},		/* 1 */
			{'2', M('2'), '2'},		/* 2 */
			{'3', M('3'), '3'},		/* 3 */
			{'i', 'I', C('i')},		/* Ins */
			{'.', ':', ':'}			/* Del */
};
/*
 * Unlike Ctrl-letter, the Alt-letter keystrokes have no specific ASCII
 * meaning unless assigned one by a keyboard conversion table
 * To interpret Alt-letters, we use a
 * scan code table to translate the scan code into a letter, then set the
 * "meta" bit for it.  -3.
 */
#define SCANLO		0x02

static const char scanmap[] = { 	/* ... */
	'1','2','3','4','5','6','7','8','9','0',0,0,0,0,
	'q','w','e','r','t','y','u','i','o','p','[',']', '\n',
	0, 'a','s','d','f','g','h','j','k','l',';','\'', '`',
	0, '\\', 'z','x','c','v','b','n','m',',','.','?'	/* ... */
};

static const char *extendedlist = "acdefijlmnopqrstuvw?2";

#define inmap(x)	(SCANLO <= (x) && (x) < SCANLO + SIZE(scanmap))

int process_keystroke(ir, valid)
INPUT_RECORD *ir;
boolean *valid;
{
	int metaflags = 0;
	unsigned char ch;
	unsigned short int scan;
	unsigned long shiftstate;
	int altseq;
	const struct pad *kpad;

	shiftstate = 0L;
	ch    = ir->Event.KeyEvent.uChar.AsciiChar;
	scan  = ir->Event.KeyEvent.wVirtualScanCode;
	shiftstate = ir->Event.KeyEvent.dwControlKeyState;
	altseq=(shiftstate & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED) && (ch || inmap(scan)));
	if (ch || (iskeypad(scan)) || altseq)
			*valid = 1;
	/* if (!valid) return 0; */
    	/*
	 * shiftstate can be checked to see if various special
         * keys were pressed at the same time as the key.
         * Currently we are using the ALT & SHIFT & CONTROLS.
         *
         *           RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
         *           RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
         *           SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
         *           CAPSLOCK_ON, ENHANCED_KEY
         *
         * are all valid bit masks to use on shiftstate.
         * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
         *      left control key was pressed with the keystroke.
         */
        if (iskeypad(scan)) {
            kpad = iflags.num_pad ? numpad : keypad;
            if (shiftstate & SHIFT_PRESSED) {
                ch = kpad[scan - KEYPADLO].shift;
            }
            else if (shiftstate & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
                ch = kpad[scan - KEYPADLO].cntrl;
            }
            else {
                ch = kpad[scan - KEYPADLO].normal;
            }
        }
        else if (altseq) { /* ALT sequence */
            altseq = 0;
            if (!ch && inmap(scan)) ch = scanmap[scan - SCANLO];
            if (index(extendedlist, tolower(ch)) != 0) ch = M(tolower(ch));
                else if (scan == (SCANLO + SIZE(scanmap)) - 1) ch = M('?');
        }
        return (ch == '\r') ? '\n' : ch;
}

int
tgetch()
{
	int count;
	int valid = 0;
	int ch;
	valid = 0;
	while (!valid)
	{
	   ReadConsoleInput(hConIn,&ir,1,&count);
	   if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
		ch = process_keystroke(&ir, &valid);
	}
	return ch;
}

int
ntposkey(x, y, mod)
int *x, *y, *mod;
{
	int count;
	unsigned short int scan;
	unsigned char ch;
	unsigned long shiftstate;
	int altseq;
	int done = 0;
	int valid = 0;
	while (!done)
	{
	    count = 0;
	    ReadConsoleInput(hConIn,&ir,1,&count);
	    if (count > 0) {
		ch    = ir.Event.KeyEvent.uChar.AsciiChar;
		scan  = ir.Event.KeyEvent.wVirtualScanCode;
		shiftstate = ir.Event.KeyEvent.dwControlKeyState;
		altseq=(shiftstate & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED) && (ch || inmap(scan)));
		if (((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown) &&
		     (ch || (iskeypad(scan)) || altseq)) {
		     	*mod = 0;
			return process_keystroke(&ir, &valid);
		} else if ((ir.EventType == MOUSE_EVENT &&
		  (ir.Event.MouseEvent.dwButtonState & MOUSEMASK))) {
#if 0
		  	*x = ir.Event.MouseEvent.dwMousePosition.X - csbi.srWindow.Left;
		  	*y = ir.Event.MouseEvent.dwMousePosition.Y - csbi.srWindow.Top;
#endif
		  	*x = ir.Event.MouseEvent.dwMousePosition.X + 1;
		  	*y = ir.Event.MouseEvent.dwMousePosition.Y - 1;

		  	if (ir.Event.MouseEvent.dwButtonState & LEFTBUTTON)
		  	       		*mod = CLICK_1;
		  	else if (ir.Event.MouseEvent.dwButtonState & RIGHTBUTTON)
					*mod = CLICK_2;
#if 0	/* middle button */			       
			else if (ir.Event.MouseEvent.dwButtonState & MIDBUTTON)
			      		*mod = CLICK_3;
#endif 
			       return 0;
		  
		}
	    }
	}
	/* Not Reached */
	return '\032';
}

int
nttty_kbhit()
{
	int done = 0;	/* true =  "stop searching"        */
	int retval;	/* true =  "we had a match"        */
	int count;
	unsigned short int scan;
	unsigned char ch;
	unsigned long shiftstate;
	int altseq;
	done = 0;
	retval = 0;
	while (!done)
	{
	    count = 0;
	    PeekConsoleInput(hConIn,&ir,1,&count);
	    if (count > 0) {
		ch    = ir.Event.KeyEvent.uChar.AsciiChar;
		scan  = ir.Event.KeyEvent.wVirtualScanCode;
		shiftstate = ir.Event.KeyEvent.dwControlKeyState;
		altseq=(shiftstate & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED) && (ch || inmap(scan)));
		if (((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown) &&
		     (ch || (iskeypad(scan)) || altseq)) {
			done = 1;	    /* Stop looking         */
			retval = 1;         /* Found what we sought */
		}
		
		 else if ((ir.EventType == MOUSE_EVENT &&
		  (ir.Event.MouseEvent.dwButtonState & MOUSEMASK))) {
			done = 1;
			retval = 1;
		}

		else /* Discard it, its an insignificant event */
			ReadConsoleInput(hConIn,&ir,1,&count);
		} else  /* There are no events in console event queue */ {
		done = 1;	  /* Stop looking               */
		retval = 0;
	    }
	}
	return retval;
}

void
get_scr_size()
{
	if (GetConsoleScreenBufferInfo(hConOut,&csbi))
	{
	    int tmpx, tmpy, ccnt;
	    COORD newcoord;
	    
	    newcoord.X = 0;
	    newcoord.Y = 0;
	    FillConsoleOutputCharacter(hConOut,' ',
			csbi.dwSize.X * csbi.dwSize.Y,
			newcoord, &ccnt);

	    tmpy = csbi.dwSize.Y;
	    tmpx = csbi.dwSize.X;
	    if ((tmpy < 25) || (tmpx < 80)) {
	    	newcoord.Y = 25;
	    	newcoord.X = 80;
	    	SetConsoleScreenBufferSize(hConOut, newcoord);
	    }
	}
	LI = 25;
	CO = 80;
}


void
tty_startup(wid, hgt)
    int *wid, *hgt;
{

	*wid = CO;
	*hgt = LI;
}

void
tty_number_pad(state)
int state;
{
}

void
tty_start_screen()
{
	if (iflags.num_pad) tty_number_pad(1);	/* make keypad send digits */
}

void
tty_end_screen()
{
	clear_screen();
	if (GetConsoleScreenBufferInfo(hConOut,&csbi))
	{
	    int ccnt;
	    COORD newcoord;
	    
	    newcoord.X = 0;
	    newcoord.Y = 0;
	    FillConsoleOutputAttribute(hConOut,
	    		FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
	    		csbi.dwSize.X * csbi.dwSize.Y,
	    		newcoord, &ccnt);
	    FillConsoleOutputCharacter(hConOut,' ',
			csbi.dwSize.X * csbi.dwSize.Y,
			newcoord, &ccnt);
	}
}

void
nocmov(x, y)
int x,y;
{
	ntcoord.X = x;
	ntcoord.Y = y;
	SetConsoleCursorPosition(hConOut,ntcoord);
}

void
cmov(x, y)
register int x, y;
{
	ntcoord.X = x;
	ntcoord.Y = y;
	SetConsoleCursorPosition(hConOut,ntcoord);
	ttyDisplay->cury = y;
	ttyDisplay->curx = x;
}

void
xputc(c)
char c;
{
	int count;

	WriteConsole(hConOut,&c,1,&count,0);
}

void
xputs(s)
const char *s;
{
	int count;
	
	WriteConsole(hConOut,s,strlen(s),&count,0);
}

void
cl_end()
{
		int count;

		ntcoord.X = ttyDisplay->curx;
		ntcoord.Y = ttyDisplay->cury;
		FillConsoleOutputCharacter(hConOut,' ',
			CO - ntcoord.X,ntcoord,&count);
		tty_curs(BASE_WINDOW, (int)ttyDisplay->curx+1,
						(int)ttyDisplay->cury);
}


void
clear_screen()
{
#ifdef USE_DIRECTX_SYSCALLS
#else
	int count;

	ntcoord.X = 0;
	ntcoord.Y = 0;
	FillConsoleOutputCharacter(hConOut,' ',CO * LI,
		ntcoord, &count);
	home();
#endif
}


void
home()
{
	tty_curs(BASE_WINDOW, 1, 0);
	ttyDisplay->curx = ttyDisplay->cury = 0;
}


void
backsp()
{
	DWORD count;

	GetConsoleScreenBufferInfo(hConOut,&csbi);
	if (csbi.dwCursorPosition.X > 0)
		ntcoord.X = csbi.dwCursorPosition.X-1;
	ntcoord.Y = csbi.dwCursorPosition.Y;
	SetConsoleCursorPosition(hConOut,ntcoord);	
	WriteConsole(hConOut," ",1,&count,0);
	SetConsoleCursorPosition(hConOut,ntcoord);	
}

void
tty_nhbell()
{
	if (flags.silent) return;
	Beep(8000,500);
}


void
tty_delay_output()
{
	/* delay 50 ms - uses ANSI C clock() function now */
	clock_t goal;

	goal = 50 + clock();
	while (goal > clock()) {
	    /* Do nothing */
	}
}

void
cl_eos()
{

		register int cy = ttyDisplay->cury+1;
		while(cy <= LI-2) {
			cl_end();
			xputc('\n');
			cy++;
		}
		cl_end();
		tty_curs(BASE_WINDOW, (int)ttyDisplay->curx+1,
						(int)ttyDisplay->cury);
}


# ifdef TEXTCOLOR
/*
 * CLR_BLACK		0
 * CLR_RED		1
 * CLR_GREEN		2
 * CLR_BROWN		3	low-intensity yellow
 * CLR_BLUE		4
 * CLR_MAGENTA 		5
 * CLR_CYAN		6
 * CLR_GRAY		7	low-intensity white
 * NO_COLOR		8
 * CLR_ORANGE		9
 * CLR_BRIGHT_GREEN	10
 * CLR_YELLOW		11
 * CLR_BRIGHT_BLUE	12
 * CLR_BRIGHT_MAGENTA  	13
 * CLR_BRIGHT_CYAN	14
 * CLR_WHITE		15
 * CLR_MAX		16
 * BRIGHT		8
 */

static void
init_ttycolor()
{
	ttycolors[CLR_BLACK] = FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_RED;
	ttycolors[CLR_RED] = FOREGROUND_RED;
	ttycolors[CLR_GREEN] = FOREGROUND_GREEN;
	ttycolors[CLR_BROWN] = FOREGROUND_GREEN|FOREGROUND_RED;
	ttycolors[CLR_BLUE] = FOREGROUND_BLUE|FOREGROUND_INTENSITY;
	ttycolors[CLR_MAGENTA] = FOREGROUND_BLUE|FOREGROUND_RED;
	ttycolors[CLR_CYAN] = FOREGROUND_GREEN|FOREGROUND_BLUE;
	ttycolors[CLR_GRAY] = FOREGROUND_GREEN|FOREGROUND_RED|FOREGROUND_BLUE;
	ttycolors[BRIGHT] = FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_RED|\
						FOREGROUND_INTENSITY;
	ttycolors[CLR_ORANGE] = FOREGROUND_RED|FOREGROUND_INTENSITY;
	ttycolors[CLR_BRIGHT_GREEN] = FOREGROUND_GREEN|FOREGROUND_INTENSITY;
	ttycolors[CLR_YELLOW] = FOREGROUND_GREEN|FOREGROUND_RED|\
						FOREGROUND_INTENSITY;
	ttycolors[CLR_BRIGHT_BLUE] = FOREGROUND_BLUE|FOREGROUND_INTENSITY;
	ttycolors[CLR_BRIGHT_MAGENTA] = FOREGROUND_BLUE|FOREGROUND_RED|\
						FOREGROUND_INTENSITY;
	ttycolors[CLR_BRIGHT_CYAN] = FOREGROUND_GREEN|FOREGROUND_BLUE;
	ttycolors[CLR_WHITE] = FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_RED|\
						FOREGROUND_INTENSITY;
}

# endif /* TEXTCOLOR */

int
has_color(int color)
{
# ifdef TEXTCOLOR
    return 1;
# else
    return 0;
# endif 
}

void
term_end_attr(int attr)
{
	/* Mix all three colors for white on NT console */
	SetConsoleTextAttribute(hConOut,
		FOREGROUND_RED|FOREGROUND_BLUE|
		FOREGROUND_GREEN);    
}

void
term_start_attr(int attr)
{
    switch(attr){

        case ATR_ULINE:
        case ATR_BOLD:
    	        /* Mix all three colors for white on NT console */
	        SetConsoleTextAttribute(hConOut,
		    FOREGROUND_RED|FOREGROUND_BLUE|
		    FOREGROUND_GREEN|FOREGROUND_INTENSITY );
                break;
        case ATR_BLINK:
        case ATR_INVERSE:
        default:
                term_end_attr(0);
                break;
    }                
}

void
term_end_raw_bold(void)
{
    standoutend();    
}

void
term_start_raw_bold(void)
{
    standoutbeg();
}

void
term_start_color(int color)
{
# ifdef TEXTCOLOR
	WORD attr;

        if (color >= 0 && color < CLR_MAX) {
            attr = (WORD)ttycolors[color];
	    SetConsoleTextAttribute(hConOut,attr);
	}
# endif
}

void
term_end_color(void)
{
# ifdef TEXTCOLOR
	SetConsoleTextAttribute(hConOut,
		FOREGROUND_RED|FOREGROUND_BLUE|
		FOREGROUND_GREEN);
# endif       
}


void
standoutbeg()
{
	/* Mix all three colors for white on NT console */
	SetConsoleTextAttribute(hConOut,
		FOREGROUND_RED|FOREGROUND_BLUE|
		FOREGROUND_GREEN|FOREGROUND_INTENSITY );
}


void
standoutend()
{
	/* Mix all three colors for white on NT console */
	SetConsoleTextAttribute(hConOut,
		FOREGROUND_RED|FOREGROUND_BLUE|
		FOREGROUND_GREEN);
}

#endif /* WIN32CON */
