/* NetHack 3.6	nttty.c	$NHDT-Date: 1554215932 2019/04/02 14:38:52 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.99 $ */
/* 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	1993/01/31
 * Switch to low level console output routines	M. Allison	2003/10/01
 * Restrict cursor movement until input pending	M. Lehotay	2003/10/02
 * Call Unicode version of output API on NT     R. Chason   2005/10/28
 * Use of back buffer to improve performance    B. House    2018/05/06
 *
 */

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

extern boolean getreturn_enabled; /* from sys/share/pcsys.c */
extern int redirect_stdout;

#ifdef TTY_GRAPHICS
/*
 * Console Buffer Flipping Support
 *
 * To minimize the number of calls into the WriteConsoleOutputXXX methods,
 * we implement a notion of a console back buffer which keeps the next frame
 * of console output as it is being composed.  When ready to show the new
 * frame, we compare this next frame to what is currently being output and
 * only call WriteConsoleOutputXXX for those console values that need to
 * change.
 *
 */

#define CONSOLE_CLEAR_ATTRIBUTE (FOREGROUND_RED | FOREGROUND_GREEN \
                                                | FOREGROUND_BLUE)
#define CONSOLE_CLEAR_CHARACTER (' ')

#define CONSOLE_UNDEFINED_ATTRIBUTE (0)
#define CONSOLE_UNDEFINED_CHARACTER ('\0')

typedef struct {
    WCHAR   character;
    WORD    attribute;
} cell_t;

cell_t clear_cell = { CONSOLE_CLEAR_CHARACTER, CONSOLE_CLEAR_ATTRIBUTE };
cell_t undefined_cell = { CONSOLE_UNDEFINED_CHARACTER,
                          CONSOLE_UNDEFINED_ATTRIBUTE };

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

static BOOL FDECL(CtrlHandler, (DWORD));
static void FDECL(xputc_core, (char));
void FDECL(cmov, (int, int));
void FDECL(nocmov, (int, int));
int FDECL(process_keystroke,
          (INPUT_RECORD *, boolean *, BOOLEAN_P numberpad, int portdebug));
static void NDECL(init_ttycolor);
static void NDECL(really_move_cursor);
static void NDECL(check_and_set_font);
static boolean NDECL(check_font_widths);
static void NDECL(set_known_good_console_font);
static void NDECL(restore_original_console_font);
extern void NDECL(safe_routines);

/* Win32 Screen buffer,coordinate,console I/O information */
COORD ntcoord;
INPUT_RECORD ir;
static boolean orig_QuickEdit;

/* Support for changing console font if existing glyph widths are too wide */

/* 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 = FALSE;
/* Flag for whether unicode is supported */
static boolean init_ttycolor_completed;
#ifdef PORT_DEBUG
static boolean display_cursor_info = FALSE;
#endif
#ifdef CHANGE_COLOR
static void NDECL(adjust_palette);
static int FDECL(match_color_name, (const char *));
typedef HWND(WINAPI *GETCONSOLEWINDOW)();
static HWND GetConsoleHandle(void);
static HWND GetConsoleHwnd(void);
static boolean altered_palette;
static COLORREF UserDefinedColors[CLR_MAX];
static COLORREF NetHackColors[CLR_MAX] = {
    0x00000000, 0x00c80000, 0x0000c850, 0x00b4b432, 0x000000d2, 0x00800080,
    0x000064b4, 0x00c0c0c0, 0x00646464, 0x00f06464, 0x0000ff00, 0x00ffff00,
    0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff
};
static COLORREF DefaultColors[CLR_MAX] = {
    0x00000000, 0x00800000, 0x00008000, 0x00808000, 0x00000080, 0x00800080,
    0x00008080, 0x00c0c0c0, 0x00808080, 0x00ff0000, 0x0000ff00, 0x00ffff00,
    0x000000ff, 0x00ff00ff, 0x0000ffff, 0x00ffffff
};
#endif
struct console_t {
    WORD background;
    WORD foreground;
    WORD attr;
    int current_nhcolor;
    int current_nhattr[ATR_INVERSE+1];
    COORD cursor;
    HANDLE hConOut;
    HANDLE hConIn;
    CONSOLE_SCREEN_BUFFER_INFO origcsbi;
    int width;
    int height;
    boolean has_unicode;
    int buffer_size;
    cell_t * front_buffer;
    cell_t * back_buffer;
    WCHAR cpMap[256];
    boolean font_changed;
    CONSOLE_FONT_INFOEX original_font_info;
    UINT original_code_page;
} console = {
    0,
    (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED),
    (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED),
    NO_COLOR,
    {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE},
    {0, 0},
    NULL,
    NULL,
    { 0 },
    0,
    0,
    FALSE,
    0,
    NULL,
    NULL,
    { 0 },
    FALSE,
    { 0 },
    0
};

static DWORD ccount, acount;
#ifndef CLR_MAX
#define CLR_MAX 16
#endif

int ttycolors[CLR_MAX];
int ttycolors_inv[CLR_MAX];

#define MAX_OVERRIDES 256
unsigned char key_overrides[MAX_OVERRIDES];
static char nullstr[] = "";
char erase_char, kill_char;
#define DEFTEXTCOLOR ttycolors[7]

/* dynamic keystroke handling .DLL support */
typedef int(__stdcall *PROCESS_KEYSTROKE)(HANDLE, INPUT_RECORD *, boolean *,
                                          BOOLEAN_P, int);

typedef int(__stdcall *NHKBHIT)(HANDLE, INPUT_RECORD *);

typedef int(__stdcall *CHECKINPUT)(HANDLE, INPUT_RECORD *, DWORD *, BOOLEAN_P,
                                   int, int *, coord *);

typedef int(__stdcall *SOURCEWHERE)(char **);

typedef int(__stdcall *SOURCEAUTHOR)(char **);

typedef int(__stdcall *KEYHANDLERNAME)(char **, int);

typedef struct {
    char *              name;       // name without DLL extension
    HANDLE              hLibrary;
    PROCESS_KEYSTROKE   pProcessKeystroke;
    NHKBHIT             pNHkbhit;
    CHECKINPUT          pCheckInput;
    SOURCEWHERE         pSourceWhere;
    SOURCEAUTHOR        pSourceAuthor;
    KEYHANDLERNAME      pKeyHandlerName;
} keyboard_handler_t;

keyboard_handler_t keyboard_handler;


/* Console buffer flipping support */

static void back_buffer_flip()
{
    cell_t * back = console.back_buffer;
    cell_t * front = console.front_buffer;
    COORD pos;
    DWORD unused;

    for (pos.Y = 0; pos.Y < console.height; pos.Y++) {
        for (pos.X = 0; pos.X < console.width; pos.X++) {
            if (back->attribute != front->attribute) {
                WriteConsoleOutputAttribute(console.hConOut, &back->attribute,
                                            1, pos, &unused);
                front->attribute = back->attribute;
            }
            if (back->character != front->character) {
                if (console.has_unicode) {
                    WriteConsoleOutputCharacterW(console.hConOut,
                        &back->character, 1, pos, &unused);
                } else {
                    char ch = (char)back->character;
                    WriteConsoleOutputCharacterA(console.hConOut, &ch, 1, pos,
                                                    &unused);
                }
                *front = *back;
            }
            back++;
            front++;
        }
    }
}

void buffer_fill_to_end(cell_t * buffer, cell_t * fill, int x, int y)
{
    nhassert(x >= 0 && x < console.width);
    nhassert(y >= 0 && ((y < console.height) || (y == console.height && 
                                                 x == 0)));

    cell_t * dst = buffer + console.width * y + x;
    cell_t * sentinel = buffer + console.buffer_size;
    while (dst != sentinel)
        *dst++ = *fill;

    if (iflags.debug.immediateflips && buffer == console.back_buffer)
        back_buffer_flip();
}

static void buffer_clear_to_end_of_line(cell_t * buffer, int x, int y)
{
    nhassert(x >= 0 && x < console.width);
    nhassert(y >= 0 && ((y < console.height) || (y == console.height && 
                                                 x == 0)));
    cell_t * dst = buffer + console.width * y + x;
    cell_t *sentinel = buffer + console.width * (y + 1);

    while (dst != sentinel)
        *dst++ = clear_cell;

    if (iflags.debug.immediateflips)
        back_buffer_flip();
}

void buffer_write(cell_t * buffer, cell_t * cell, COORD pos)
{
    nhassert(pos.X >= 0 && pos.X < console.width);
    nhassert(pos.Y >= 0 && pos.Y < console.height);

    cell_t * dst = buffer + (console.width * pos.Y) + pos.X;
    *dst = *cell;

    if (iflags.debug.immediateflips && buffer == console.back_buffer)
        back_buffer_flip();
}

/*
 * Called after returning from ! or ^Z
 */
void
gettty()
{
#ifndef TEXTCOLOR
    int k;
#endif
    erase_char = '\b';
    kill_char = 21; /* cntl-U */
    iflags.cbreak = TRUE;
#ifdef TEXTCOLOR
    init_ttycolor();
#else
    for (k = 0; k < CLR_MAX; ++k)
        ttycolors[k] = NO_COLOR;
#endif
}

/* reset terminal to original state */
void
settty(s)
const char *s;
{
    cmov(ttyDisplay->curx, ttyDisplay->cury);
    end_screen();
    if (s)
        raw_print(s);
    restore_original_console_font();
    if (orig_QuickEdit) {
        DWORD cmode;

        GetConsoleMode(console.hConIn, &cmode);
        cmode |= (ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
        SetConsoleMode(console.hConIn, cmode);
    }
}

/* called by init_nhwindows() and resume_nhwindows() */
void
setftty()
{
#ifdef CHANGE_COLOR
    if (altered_palette)
        adjust_palette();
#endif
    start_screen();
}

void
tty_startup(wid, hgt)
int *wid, *hgt;
{
    *wid = console.width;
    *hgt = console.height;
    set_option_mod_status("mouse_support", SET_IN_GAME);
}

void
tty_number_pad(state)
int state;
{
    // do nothing
}

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

void
tty_end_screen()
{
    clear_screen();
    really_move_cursor();
    buffer_fill_to_end(console.back_buffer, &clear_cell, 0, 0);
    back_buffer_flip();
    FlushConsoleInputBuffer(console.hConIn);
}

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:
        getreturn_enabled = FALSE;
#ifndef NOSAVEONHANGUP
        hangup(0);
#endif
#if defined(SAFERHANGUP)
        CloseHandle(console.hConIn); /* trigger WAIT_FAILED */
        return TRUE;
#endif
    default:
        return FALSE;
    }
}

/* called by pcmain() and process_options() */
void
nttty_open(mode)
int mode; // unused
{
    DWORD cmode;

    /* Initialize the function pointer that points to
     * the kbhit() equivalent, in this TTY case nttty_kbhit()
     */
    nt_kbhit = nttty_kbhit;

    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE)) {
        /* Unable to set control handler */
        cmode = 0; /* just to have a statement to break on for debugger */
    }

    LI = console.height;
    CO = console.width;

    really_move_cursor();
}

void
nttty_exit()
{
    /* go back to using the safe routines */
    safe_routines();
}

int
process_keystroke(ir, valid, numberpad, portdebug)
INPUT_RECORD *ir;
boolean *valid;
boolean numberpad;
int portdebug;
{
    int ch;

#ifdef QWERTZ_SUPPORT
    if (Cmd.swap_yz)
        numberpad |= 0x10;
#endif
    ch = keyboard_handler.pProcessKeystroke(
                    console.hConIn, ir, valid, numberpad, portdebug);
#ifdef QWERTZ_SUPPORT
    numberpad &= ~0x10;
#endif
    /* check for override */
    if (ch && ch < MAX_OVERRIDES && key_overrides[ch])
        ch = key_overrides[ch];
    return ch;
}

int
nttty_kbhit()
{
    return keyboard_handler.pNHkbhit(console.hConIn, &ir);
}

int
tgetch()
{
    int mod;
    coord cc;
    DWORD count;
    boolean numpad = iflags.num_pad;

    really_move_cursor();
    if (iflags.debug_fuzzer)
        return randomkey();
#ifdef QWERTZ_SUPPORT
    if (Cmd.swap_yz)
        numpad |= 0x10;
#endif

    return (program_state.done_hup)
               ? '\033'
               : keyboard_handler.pCheckInput(
                   console.hConIn, &ir, &count, numpad, 0, &mod, &cc);
}

int
ntposkey(x, y, mod)
int *x, *y, *mod;
{
    int ch;
    coord cc;
    DWORD count;
    boolean numpad = iflags.num_pad;

    really_move_cursor();
    if (iflags.debug_fuzzer)
        return randomkey();
#ifdef QWERTZ_SUPPORT
    if (Cmd.swap_yz)
        numpad |= 0x10;
#endif
    ch = (program_state.done_hup)
             ? '\033'
             : keyboard_handler.pCheckInput(
                   console.hConIn, &ir, &count, numpad, 1, mod, &cc);
#ifdef QWERTZ_SUPPORT
    numpad &= ~0x10;
#endif
    if (!ch) {
        *x = cc.x;
        *y = cc.y;
    }
    return ch;
}

static void set_console_cursor(int x, int y)
{
    nhassert(x >= 0 && x < console.width);
    nhassert(y >= 0 && y < console.height);

    console.cursor.X = max(0, min(console.width - 1, x));
    console.cursor.Y = max(0, min(console.height - 1, y));
}

static void
really_move_cursor()
{
#ifdef PORT_DEBUG
    char oldtitle[BUFSZ], newtitle[BUFSZ];
    if (display_cursor_info && wizard) {
        oldtitle[0] = '\0';
        if (GetConsoleTitle(oldtitle, BUFSZ)) {
            oldtitle[39] = '\0';
        }
        Sprintf(newtitle, "%-55s tty=(%02d,%02d) nttty=(%02d,%02d)", oldtitle,
                ttyDisplay->curx, ttyDisplay->cury,
                console.cursor.X, console.cursor.Y);
        (void) SetConsoleTitle(newtitle);
    }
#endif
    if (ttyDisplay)
        set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);

    back_buffer_flip();
    SetConsoleCursorPosition(console.hConOut, console.cursor);
}

void
cmov(x, y)
register int x, y;
{
    ttyDisplay->cury = y;
    ttyDisplay->curx = x;

    set_console_cursor(x, y);
}

void
nocmov(x, y)
int x, y;
{
    ttyDisplay->curx = x;
    ttyDisplay->cury = y;

    set_console_cursor(x, y);
}

/* same signature as 'putchar()' with potential failure result ignored */
int
xputc(ch)
int ch;
{
    set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);
    xputc_core((char) ch);
    return 0;
}

void
xputs(s)
const char *s;
{
    int k;
    int slen = (int) strlen(s);

    if (ttyDisplay)
        set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);

    if (s) {
        for (k = 0; k < slen && s[k]; ++k)
            xputc_core(s[k]);
    }
}

/* xputc_core() and g_putch() are the only
 * two routines that actually place output
 * on the display.
 */
void
xputc_core(ch)
char ch;
{
    nhassert(console.cursor.X >= 0 && console.cursor.X < console.width);
    nhassert(console.cursor.Y >= 0 && console.cursor.Y < console.height);

    boolean inverse = FALSE;
    cell_t cell;

    switch (ch) {
    case '\n':
        if (console.cursor.Y < console.height - 1)
            console.cursor.Y++;
    /* fall through */
    case '\r':
        console.cursor.X = 1;
        break;
    case '\b':
        if (console.cursor.X > 1) {
            console.cursor.X--;
        } else if(console.cursor.Y > 0) {
            console.cursor.X = console.width - 1;
            console.cursor.Y--;
        }
        break;
    default:

        inverse = (console.current_nhattr[ATR_INVERSE] && iflags.wc_inverse);
        console.attr = (inverse) ?
                        ttycolors_inv[console.current_nhcolor] :
                        ttycolors[console.current_nhcolor];
        if (console.current_nhattr[ATR_BOLD])
                console.attr |= (inverse) ?
                                BACKGROUND_INTENSITY : FOREGROUND_INTENSITY;

        cell.attribute = console.attr;
        cell.character = (console.has_unicode ? console.cpMap[ch] : ch);

        buffer_write(console.back_buffer, &cell, console.cursor);

        if (console.cursor.X == console.width - 1) {
            if (console.cursor.Y < console.height - 1) {
                console.cursor.X = 1;
                console.cursor.Y++;
            }
        } else {
            console.cursor.X++;
        }
    }

    nhassert(console.cursor.X >= 0 && console.cursor.X < console.width);
    nhassert(console.cursor.Y >= 0 && console.cursor.Y < console.height);
}

/*
 * Overrides wintty.c function of the same name
 * for win32. It is used for glyphs only, not text.
 */

void
g_putch(in_ch)
int in_ch;
{
    boolean inverse = FALSE;
    unsigned char ch = (unsigned char) in_ch;

    set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);

    inverse = (console.current_nhattr[ATR_INVERSE] && iflags.wc_inverse);
    console.attr = (console.current_nhattr[ATR_INVERSE] && iflags.wc_inverse) ?
                    ttycolors_inv[console.current_nhcolor] :
                    ttycolors[console.current_nhcolor];
    if (console.current_nhattr[ATR_BOLD])
        console.attr |= (inverse) ? BACKGROUND_INTENSITY : FOREGROUND_INTENSITY;

    cell_t cell;

    cell.attribute = console.attr;
    cell.character = (console.has_unicode ? cp437[ch] : ch);

    buffer_write(console.back_buffer, &cell, console.cursor);
}

void
cl_end()
{
    set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);
    buffer_clear_to_end_of_line(console.back_buffer, console.cursor.X,
                                console.cursor.Y);
    tty_curs(BASE_WINDOW, (int) ttyDisplay->curx + 1, (int) ttyDisplay->cury);
}

void
raw_clear_screen()
{
    if (WINDOWPORT("tty")) {
        cell_t * back = console.back_buffer;
        cell_t * front = console.front_buffer;
        COORD pos;
        DWORD unused;

        for (pos.Y = 0; pos.Y < console.height; pos.Y++) {
            for (pos.X = 0; pos.X < console.width; pos.X++) {
                 WriteConsoleOutputAttribute(console.hConOut, &back->attribute,
                                             1, pos, &unused);
                 front->attribute = back->attribute;
                 if (console.has_unicode) {
                     WriteConsoleOutputCharacterW(console.hConOut,
                             &back->character, 1, pos, &unused);
                 } else {
                     char ch = (char)back->character;
                     WriteConsoleOutputCharacterA(console.hConOut, &ch, 1, pos,
                                                         &unused);
                 }
                 *front = *back;
                 back++;
                 front++;
            }
        }
    }
}

void
clear_screen()
{
    buffer_fill_to_end(console.back_buffer, &clear_cell, 0, 0);    
    home();
}

void
home()
{
    ttyDisplay->curx = ttyDisplay->cury = 0;
    set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);
}

void
backsp()
{
    set_console_cursor(ttyDisplay->curx, ttyDisplay->cury);
    xputc_core('\b');
}

void
cl_eos()
{
    buffer_fill_to_end(console.back_buffer, &clear_cell, ttyDisplay->curx,
                        ttyDisplay->cury);
    tty_curs(BASE_WINDOW, (int) ttyDisplay->curx + 1, (int) ttyDisplay->cury);
}

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

volatile int junk; /* prevent optimizer from eliminating loop below */

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

    goal = 50 + clock();
    back_buffer_flip();
    if (iflags.debug_fuzzer)
        return;

    while (goal > clock()) {
        k = junk; /* Do nothing */
    }
}

/*
 * 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()
{
#ifdef TEXTCOLOR
    ttycolors[CLR_BLACK]        = FOREGROUND_INTENSITY; /* fix by Quietust */
    ttycolors[CLR_RED]          = FOREGROUND_RED;
    ttycolors[CLR_GREEN]        = FOREGROUND_GREEN;
    ttycolors[CLR_BROWN]        = FOREGROUND_GREEN | FOREGROUND_RED;
    ttycolors[CLR_BLUE]         = FOREGROUND_BLUE;
    ttycolors[CLR_MAGENTA]      = FOREGROUND_BLUE | FOREGROUND_RED;
    ttycolors[CLR_CYAN]         = FOREGROUND_GREEN | FOREGROUND_BLUE;
    ttycolors[CLR_GRAY]         = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE;
    ttycolors[NO_COLOR]         = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED;
    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 | FOREGROUND_INTENSITY;
    ttycolors[CLR_WHITE]        = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED
                                    | FOREGROUND_INTENSITY;

    ttycolors_inv[CLR_BLACK]       = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED
                                        | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_RED]         = BACKGROUND_RED | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_GREEN]       = BACKGROUND_GREEN;
    ttycolors_inv[CLR_BROWN]       = BACKGROUND_GREEN | BACKGROUND_RED;
    ttycolors_inv[CLR_BLUE]        = BACKGROUND_BLUE | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_MAGENTA]     = BACKGROUND_BLUE | BACKGROUND_RED;
    ttycolors_inv[CLR_CYAN]        = BACKGROUND_GREEN | BACKGROUND_BLUE;
    ttycolors_inv[CLR_GRAY]        = BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE;
    ttycolors_inv[NO_COLOR]        = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED;
    ttycolors_inv[CLR_ORANGE]      = BACKGROUND_RED | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_BRIGHT_GREEN]= BACKGROUND_GREEN | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_YELLOW]      = BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_BRIGHT_BLUE] = BACKGROUND_BLUE | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_BRIGHT_MAGENTA] =BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_BRIGHT_CYAN] = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY;
    ttycolors_inv[CLR_WHITE]       = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED
                                       | BACKGROUND_INTENSITY;
#else
    int k;
    ttycolors[0] = FOREGROUND_INTENSITY;
    ttycolors_inv[0] = BACKGROUND_INTENSITY;
    for (k = 1; k < SIZE(ttycolors); ++k) {
        ttycolors[k] = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED;
        ttycolors_inv[k] = BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED;
    }
#endif
    init_ttycolor_completed = TRUE;
}

#if 0
int
has_color(int color)        /* this function is commented out */
{
#ifdef TEXTCOLOR
    if ((color >= 0) && (color < CLR_MAX))
        return 1;
#else
    if ((color == CLR_BLACK) || (color == CLR_WHITE) || (color == NO_COLOR))
        return 1;
#endif
    else
        return 0;
}
#endif

int
term_attr_fixup(int attrmask)
{
    return attrmask;
}

void
term_start_attr(int attrib)
{
    console.current_nhattr[attrib] = TRUE;
    if (attrib) console.current_nhattr[ATR_NONE] = FALSE;
}

void
term_end_attr(int attrib)
{
    int k;

    switch (attrib) {
    case ATR_INVERSE:
    case ATR_ULINE:
    case ATR_BLINK:
    case ATR_BOLD:
        break;
    }
    console.current_nhattr[attrib] = FALSE;
    console.current_nhattr[ATR_NONE] = TRUE;
    /* re-evaluate all attr now for performance at output time */
    for (k=ATR_NONE; k <= ATR_INVERSE; ++k) {
        if (console.current_nhattr[k])
            console.current_nhattr[ATR_NONE] = FALSE;
    }
}

void
term_end_raw_bold(void)
{
    term_end_attr(ATR_BOLD);
}

void
term_start_raw_bold(void)
{
    term_start_attr(ATR_BOLD);
}

void
term_start_color(int color)
{
#ifdef TEXTCOLOR
    if (color >= 0 && color < CLR_MAX) {
        console.current_nhcolor = color;
    } else
#endif
    console.current_nhcolor = NO_COLOR;
}

void
term_end_color(void)
{
#ifdef TEXTCOLOR
    console.foreground = DEFTEXTCOLOR;
#endif
    console.attr = (console.foreground | console.background);
    console.current_nhcolor = NO_COLOR;
}

void
standoutbeg()
{
    term_start_attr(ATR_BOLD);
}

void
standoutend()
{
    term_end_attr(ATR_BOLD);
}

#ifndef NO_MOUSE_ALLOWED
void
toggle_mouse_support()
{
    static int qeinit = 0;
    DWORD cmode;

    GetConsoleMode(console.hConIn, &cmode);
    if (!qeinit) {
        qeinit = 1;
        orig_QuickEdit = ((cmode & ENABLE_QUICK_EDIT_MODE) != 0);
    }
    switch(iflags.wc_mouse_support) {
        case 2:
                cmode |= ENABLE_MOUSE_INPUT;
                break;
        case 1:
                cmode |= ENABLE_MOUSE_INPUT;
                cmode &= ~ENABLE_QUICK_EDIT_MODE;
                cmode |= ENABLE_EXTENDED_FLAGS;
                break;
        case 0:
                /*FALLTHRU*/
        default:
                cmode &= ~ENABLE_MOUSE_INPUT;
                if (orig_QuickEdit)
                    cmode |= (ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
    }
    SetConsoleMode(console.hConIn, cmode);
}
#endif

/* handle tty options updates here */
void
nttty_preference_update(pref)
const char *pref;
{
    if (stricmp(pref, "mouse_support") == 0) {
#ifndef NO_MOUSE_ALLOWED
        toggle_mouse_support();
#endif
    }
    if (stricmp(pref, "symset") == 0)
        check_and_set_font();
    return;
}

#ifdef PORT_DEBUG
void
win32con_debug_keystrokes()
{
    DWORD count;
    boolean valid = 0;
    int ch;
    xputs("\n");
    while (!valid || ch != 27) {
        nocmov(ttyDisplay->curx, ttyDisplay->cury);
        ReadConsoleInput(console.hConIn, &ir, 1, &count);
        if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
            ch = process_keystroke(&ir, &valid, iflags.num_pad, 1);
    }
    (void) doredraw();
}
void
win32con_handler_info()
{
    char *buf;
    int ci;
    if (!keyboard_handler.pSourceAuthor && !keyboard_handler.pSourceWhere)
        pline("Keyboard handler source info and author unavailable.");
    else {
        if (keyboard_handler.pKeyHandlerName &&
            keyboard_handler.pKeyHandlerName(&buf, 1)) {
            xputs("\n");
            xputs("Keystroke handler loaded: \n    ");
            xputs(buf);
        }
        if (keyboard_handler.pSourceAuthor &&
            keyboard_handler.pSourceAuthor(&buf)) {
            xputs("\n");
            xputs("Keystroke handler Author: \n    ");
            xputs(buf);
        }
        if (keyboard_handler.pSourceWhere &&
            keyboard_handler.pSourceWhere(&buf)) {
            xputs("\n");
            xputs("Keystroke handler source code available at:\n    ");
            xputs(buf);
        }
        xputs("\nPress any key to resume.");
        ci = nhgetch();
        (void) doredraw();
    }
}

void
win32con_toggle_cursor_info()
{
    display_cursor_info = !display_cursor_info;
}
#endif

void
map_subkeyvalue(op)
register char *op;
{
    char digits[] = "0123456789";
    int length, i, idx, val;
    char *kp;

    idx = -1;
    val = -1;
    kp = index(op, '/');
    if (kp) {
        *kp = '\0';
        kp++;
        length = strlen(kp);
        if (length < 1 || length > 3)
            return;
        for (i = 0; i < length; i++)
            if (!index(digits, kp[i]))
                return;
        val = atoi(kp);
        length = strlen(op);
        if (length < 1 || length > 3)
            return;
        for (i = 0; i < length; i++)
            if (!index(digits, op[i]))
                return;
        idx = atoi(op);
    }
    if (idx >= MAX_OVERRIDES || idx < 0 || val >= MAX_OVERRIDES || val < 1)
        return;
    key_overrides[idx] = val;
}

void unload_keyboard_handler()
{
    nhassert(keyboard_handler.hLibrary != NULL);

    FreeLibrary(keyboard_handler.hLibrary);
    memset(&keyboard_handler, 0, sizeof(keyboard_handler_t));
}

boolean
load_keyboard_handler(const char * inName)
{
    char path[MAX_ALTKEYHANDLER + 4];
    strcpy(path, inName);
    strcat(path, ".dll");

    HANDLE hLibrary = LoadLibrary(path);

    if (hLibrary == NULL)
        return FALSE;

    PROCESS_KEYSTROKE pProcessKeystroke = (PROCESS_KEYSTROKE) GetProcAddress(
        hLibrary, TEXT("ProcessKeystroke"));
    NHKBHIT pNHkbhit = (NHKBHIT) GetProcAddress(
        hLibrary, TEXT("NHkbhit"));
    CHECKINPUT pCheckInput =
        (CHECKINPUT) GetProcAddress(hLibrary, TEXT("CheckInput"));

    if (!pProcessKeystroke || !pNHkbhit || !pCheckInput)
    {
        return FALSE;
    } else {
        if (keyboard_handler.hLibrary != NULL)
            unload_keyboard_handler();

        keyboard_handler.hLibrary = hLibrary;

        keyboard_handler.pProcessKeystroke = pProcessKeystroke;
        keyboard_handler.pNHkbhit = pNHkbhit;
        keyboard_handler.pCheckInput = pCheckInput;

        keyboard_handler.pSourceWhere =
            (SOURCEWHERE) GetProcAddress(hLibrary, TEXT("SourceWhere"));
        keyboard_handler.pSourceAuthor =
            (SOURCEAUTHOR) GetProcAddress(hLibrary, TEXT("SourceAuthor"));
        keyboard_handler.pKeyHandlerName = (KEYHANDLERNAME) GetProcAddress(
            hLibrary, TEXT("KeyHandlerName"));
    }

    return TRUE;
}

void set_altkeyhandler(const char * inName)
{
    if (strlen(inName) >= MAX_ALTKEYHANDLER) {
        config_error_add("altkeyhandler name '%s' is too long", inName);
        return;
    }

    char name[MAX_ALTKEYHANDLER];
    strcpy(name, inName);

    /* We support caller mistakenly giving name with '.dll' extension */
    char * ext = strchr(name, '.');
    if (ext != NULL) *ext = '\0';

    if (load_keyboard_handler(name))
        strcpy(iflags.altkeyhandler, name);
    else {
        config_error_add("unable to load altkeyhandler '%s'", name);
        return;
    }

    return;
}


/* fatal error */
/*VARARGS1*/
void nttty_error
VA_DECL(const char *, s)
{
    char buf[BUFSZ];
    VA_START(s);
    VA_INIT(s, const char *);
    /* error() may get called before tty is initialized */
    if (iflags.window_inited)
        end_screen();
    buf[0] = '\n';
    (void) vsnprintf(&buf[1], sizeof buf - 1, s, VA_ARGS);
    msmsg(buf);
    really_move_cursor();
    VA_END();
    exit(EXIT_FAILURE);
}

void
synch_cursor()
{
    really_move_cursor();
}

#ifdef CHANGE_COLOR
void
tty_change_color(color_number, rgb, reverse)
int color_number, reverse;
long rgb;
{
    /* Map NetHack color index to NT Console palette index */
    int idx, win32_color_number[] = {
        0,  /* CLR_BLACK           0 */
        4,  /* CLR_RED             1 */
        2,  /* CLR_GREEN           2 */
        6,  /* CLR_BROWN           3 */
        1,  /* CLR_BLUE            4 */
        5,  /* CLR_MAGENTA         5 */
        3,  /* CLR_CYAN            6 */
        7,  /* CLR_GRAY            7 */
        8,  /* NO_COLOR            8 */
        12, /* CLR_ORANGE          9 */
        10, /* CLR_BRIGHT_GREEN   10 */
        14, /* CLR_YELLOW         11 */
        9,  /* CLR_BRIGHT_BLUE    12 */
        13, /* CLR_BRIGHT_MAGENTA 13 */
        11, /* CLR_BRIGHT_CYAN    14 */
        15  /* CLR_WHITE          15 */
    };
    int k;
    if (color_number < 0) { /* indicates OPTIONS=palette with no value */
        /* copy the NetHack palette into UserDefinedColors */
        for (k = 0; k < CLR_MAX; k++)
            UserDefinedColors[k] = NetHackColors[k];
    } else if (color_number >= 0 && color_number < CLR_MAX) {
        if (!altered_palette) {
            /* make sure a full suite is available */
            for (k = 0; k < CLR_MAX; k++)
                UserDefinedColors[k] = DefaultColors[k];
        }
        idx = win32_color_number[color_number];
        UserDefinedColors[idx] = rgb;
    }
    altered_palette = TRUE;
}

char *
tty_get_color_string()
{
    return "";
}

int
match_color_name(c)
const char *c;
{
    const struct others {
        int idx;
        const char *colorname;
    } othernames[] = {
        { CLR_MAGENTA, "purple" },
        { CLR_BRIGHT_MAGENTA, "bright purple" },
        { NO_COLOR, "dark gray" },
        { NO_COLOR, "dark grey" },
        { CLR_GRAY, "grey" },
    };

    int cnt;
    for (cnt = 0; cnt < CLR_MAX; ++cnt) {
        if (!strcmpi(c, c_obj_colors[cnt]))
            return cnt;
    }
    for (cnt = 0; cnt < SIZE(othernames); ++cnt) {
        if (!strcmpi(c, othernames[cnt].colorname))
            return othernames[cnt].idx;
    }
    return -1;
}

/*
 * Returns 0 if badoption syntax
 */
int
alternative_palette(op)
char *op;
{
    /*
     *	palette:color-R-G-B
     *	OPTIONS=palette:green-4-3-1, palette:0-0-0-0
     */
    int fieldcnt, color_number, rgb, red, green, blue;
    char *fields[4], *cp;

    if (!op) {
        change_color(-1, 0, 0); /* indicates palette option with
                                   no value meaning "load an entire
                                   hard-coded NetHack palette." */
        return 1;
    }

    cp = fields[0] = op;
    for (fieldcnt = 1; fieldcnt < 4; ++fieldcnt) {
        cp = index(cp, '-');
        if (!cp)
            return 0;
        fields[fieldcnt] = cp;
        cp++;
    }
    for (fieldcnt = 1; fieldcnt < 4; ++fieldcnt) {
        *(fields[fieldcnt]) = '\0';
        ++fields[fieldcnt];
    }
    rgb = 0;
    for (fieldcnt = 0; fieldcnt < 4; ++fieldcnt) {
        if (fieldcnt == 0 && isalpha(*(fields[0]))) {
            color_number = match_color_name(fields[0]);
            if (color_number == -1)
                return 0;
        } else {
            int dcount = 0, cval = 0;
            cp = fields[fieldcnt];
            if (*cp == '\\' && index("0123456789xXoO", cp[1])) {
                const char *dp, *hex = "00112233445566778899aAbBcCdDeEfF";

                cp++;
                if (*cp == 'x' || *cp == 'X')
                    for (++cp; (dp = index(hex, *cp)) && (dcount++ < 2); cp++)
                        cval = (int) ((cval * 16) + (dp - hex) / 2);
                else if (*cp == 'o' || *cp == 'O')
                    for (++cp; (index("01234567", *cp)) && (dcount++ < 3);
                         cp++)
                        cval = (cval * 8) + (*cp - '0');
                else
                    return 0;
            } else {
                for (; *cp && (index("0123456789", *cp)) && (dcount++ < 3);
                     cp++)
                    cval = (cval * 10) + (*cp - '0');
            }
            switch (fieldcnt) {
            case 0:
                color_number = cval;
                break;
            case 1:
                red = cval;
                break;
            case 2:
                green = cval;
                break;
            case 3:
                blue = cval;
                break;
            }
        }
    }
    rgb = RGB(red, green, blue);
    if (color_number >= 0 && color_number < CLR_MAX)
        change_color(color_number, rgb, 0);
    return 1;
}

/*
 *  This uses an undocumented method to set console attributes
 *  at runtime including console palette
 *
 * 	VOID WINAPI SetConsolePalette(COLORREF palette[16])
 *
 *  Author: James Brown at www.catch22.net
 *
 *  Set palette of current console.
 *  Palette should be of the form:
 *
 * 	COLORREF DefaultColors[CLR_MAX] =
 * 	{
 *		0x00000000, 0x00800000, 0x00008000, 0x00808000,
 *		0x00000080, 0x00800080, 0x00008080, 0x00c0c0c0,
 *		0x00808080, 0x00ff0000, 0x0000ff00, 0x00ffff00,
 *		0x000000ff, 0x00ff00ff,	0x0000ffff, 0x00ffffff
 *	 };
 */

#pragma pack(push, 1)

/*
 *	Structure to send console via WM_SETCONSOLEINFO
 */
typedef struct _CONSOLE_INFO {
    ULONG Length;
    COORD ScreenBufferSize;
    COORD WindowSize;
    ULONG WindowPosX;
    ULONG WindowPosY;

    COORD FontSize;
    ULONG FontFamily;
    ULONG FontWeight;
    WCHAR FaceName[32];

    ULONG CursorSize;
    ULONG FullScreen;
    ULONG QuickEdit;
    ULONG AutoPosition;
    ULONG InsertMode;

    USHORT ScreenColors;
    USHORT PopupColors;
    ULONG HistoryNoDup;
    ULONG HistoryBufferSize;
    ULONG NumberOfHistoryBuffers;

    COLORREF ColorTable[16];

    ULONG CodePage;
    HWND Hwnd;

    WCHAR ConsoleTitle[0x100];
} CONSOLE_INFO;

#pragma pack(pop)

BOOL SetConsoleInfo(HWND hwndConsole, CONSOLE_INFO *pci);
static void GetConsoleSizeInfo(CONSOLE_INFO *pci);
VOID WINAPI SetConsolePalette(COLORREF crPalette[16]);

void
adjust_palette(VOID_ARGS)
{
    SetConsolePalette(UserDefinedColors);
    altered_palette = 0;
}

/*
/* only in Win2k+  (use FindWindow for NT4) */
/* HWND WINAPI GetConsoleWindow(); */

/*  Undocumented console message */
#define WM_SETCONSOLEINFO (WM_USER + 201)

VOID WINAPI
SetConsolePalette(COLORREF palette[16])
{
    CONSOLE_INFO ci = { sizeof(ci) };
    int i;
    HWND hwndConsole = GetConsoleHandle();

    /* get current size/position settings rather than using defaults.. */
    GetConsoleSizeInfo(&ci);

    /* set these to zero to keep current settings */
    ci.FontSize.X = 0; /* def = 8  */
    ci.FontSize.Y = 0; /* def = 12 */
    ci.FontFamily = 0; /* def = 0x30 = FF_MODERN|FIXED_PITCH */
    ci.FontWeight = 0; /* 0x400;   */
    /* lstrcpyW(ci.FaceName, L"Terminal"); */
    ci.FaceName[0] = L'\0';

    ci.CursorSize = 25;
    ci.FullScreen = FALSE;
    ci.QuickEdit = TRUE;
    ci.AutoPosition = 0x10000;
    ci.InsertMode = TRUE;
    ci.ScreenColors = MAKEWORD(0x7, 0x0);
    ci.PopupColors = MAKEWORD(0x5, 0xf);

    ci.HistoryNoDup = FALSE;
    ci.HistoryBufferSize = 50;
    ci.NumberOfHistoryBuffers = 4;

    // colour table
    for (i = 0; i < 16; i++)
        ci.ColorTable[i] = palette[i];

    ci.CodePage = GetConsoleOutputCP();
    ci.Hwnd = hwndConsole;

    lstrcpyW(ci.ConsoleTitle, L"");

    SetConsoleInfo(hwndConsole, &ci);
}

/*
 *  Wrapper around WM_SETCONSOLEINFO. We need to create the
 *  necessary section (file-mapping) object in the context of the
 *  process which owns the console, before posting the message
 */
BOOL
SetConsoleInfo(HWND hwndConsole, CONSOLE_INFO *pci)
{
    DWORD dwConsoleOwnerPid;
    HANDLE hProcess;
    HANDLE hSection, hDupSection;
    PVOID ptrView = 0;
    HANDLE hThread;

    /*
     *	Open the process which "owns" the console
     */
    GetWindowThreadProcessId(hwndConsole, &dwConsoleOwnerPid);
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwConsoleOwnerPid);

    /*
     * Create a SECTION object backed by page-file, then map a view of
     * this section into the owner process so we can write the contents
     * of the CONSOLE_INFO buffer into it
     */
    hSection = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0,
                                 pci->Length, 0);

    /*
     *	Copy our console structure into the section-object
     */
    ptrView = MapViewOfFile(hSection, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0,
                            pci->Length);
    memcpy(ptrView, pci, pci->Length);
    UnmapViewOfFile(ptrView);

    /*
     *	Map the memory into owner process
     */
    DuplicateHandle(GetCurrentProcess(), hSection, hProcess, &hDupSection, 0,
                    FALSE, DUPLICATE_SAME_ACCESS);

    /*  Send console window the "update" message */
    SendMessage(hwndConsole, WM_SETCONSOLEINFO, (WPARAM) hDupSection, 0);

    /*
     * clean up
     */
    hThread = CreateRemoteThread(hProcess, 0, 0,
                                 (LPTHREAD_START_ROUTINE) CloseHandle,
                                 hDupSection, 0, 0);

    CloseHandle(hThread);
    CloseHandle(hSection);
    CloseHandle(hProcess);

    return TRUE;
}

/*
 *  Fill the CONSOLE_INFO structure with information
 *  about the current console window
 */
static void
GetConsoleSizeInfo(CONSOLE_INFO *pci)
{
    CONSOLE_SCREEN_BUFFER_INFO csbi;

    HANDLE hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);

    GetConsoleScreenBufferInfo(hConsoleOut, &csbi);

    pci->ScreenBufferSize = csbi.dwSize;
    pci->WindowSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1;
    pci->WindowSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
    pci->WindowPosX = csbi.srWindow.Left;
    pci->WindowPosY = csbi.srWindow.Top;
}

static HWND
GetConsoleHandle(void)
{
    HMODULE hMod = GetModuleHandle("kernel32.dll");
    GETCONSOLEWINDOW pfnGetConsoleWindow =
        (GETCONSOLEWINDOW) GetProcAddress(hMod, "GetConsoleWindow");
    if (pfnGetConsoleWindow)
        return pfnGetConsoleWindow();
    else
        return GetConsoleHwnd();
}

static HWND
GetConsoleHwnd(void)
{
    int iterations = 0;
    HWND hwndFound = 0;
    char OldTitle[1024], NewTitle[1024], TestTitle[1024];

    /* Get current window title */
    GetConsoleTitle(OldTitle, sizeof OldTitle);

    (void) sprintf(NewTitle, "NETHACK%d/%d", GetTickCount(),
                   GetCurrentProcessId());
    SetConsoleTitle(NewTitle);

    GetConsoleTitle(TestTitle, sizeof TestTitle);
    while (strcmp(TestTitle, NewTitle) != 0) {
        iterations++;
        /* sleep(0); */
        GetConsoleTitle(TestTitle, sizeof TestTitle);
    }
    hwndFound = FindWindow(NULL, NewTitle);
    SetConsoleTitle(OldTitle);
    /*       printf("%d iterations\n", iterations); */
    return hwndFound;
}
#endif /*CHANGE_COLOR*/

static int CALLBACK EnumFontCallback(
    const LOGFONTW * lf, const TEXTMETRICW * tm, DWORD fontType, LPARAM lParam)
{
    LOGFONTW * lf_ptr = (LOGFONTW *) lParam;
    *lf_ptr = *lf;
    return 0;
}

/* check_and_set_font ensures that the current font will render the symbols
 * that are currently being used correctly.  If they will not be rendered
 * correctly, then it will change the font to a known good font.
 */
void
check_and_set_font()
{
    if (!check_font_widths()) {
        raw_print("WARNING: glyphs too wide in console font."
                  "  Changing code page to 437 and font to Consolas\n");
        set_known_good_console_font();
    }
}

/* check_font_widths returns TRUE if all glyphs in current console font
 * fit within the width of a single console cell.
 */
boolean
check_font_widths()
{
    CONSOLE_FONT_INFOEX console_font_info;
    console_font_info.cbSize = sizeof(console_font_info);
    BOOL success = GetCurrentConsoleFontEx(console.hConOut, FALSE,
                                            &console_font_info);

    /* get console window and DC
     * NOTE: the DC from the console window does not have the correct
     *       font selected at this point.
     */
    HWND hWnd = GetConsoleWindow();
    HDC hDC = GetDC(hWnd);

    LOGFONTW logical_font;
    logical_font.lfCharSet = DEFAULT_CHARSET;
    wcscpy(logical_font.lfFaceName, console_font_info.FaceName);
    logical_font.lfPitchAndFamily = 0;

    /* getting matching console font */
    LOGFONTW matching_log_font = { 0 };
    EnumFontFamiliesExW(hDC, &logical_font, EnumFontCallback,
                                        (LPARAM) &matching_log_font, 0);

    if (matching_log_font.lfHeight == 0) {
        raw_print("Unable to enumerate system fonts\n");
        return FALSE;
    }

    /* create font matching console font */
    LOGFONTW console_font_log_font = matching_log_font;
    console_font_log_font.lfWeight = console_font_info.FontWeight;
    console_font_log_font.lfHeight = console_font_info.dwFontSize.Y;
    console_font_log_font.lfWidth = console_font_info.dwFontSize.X;
    HFONT console_font = CreateFontIndirectW(&console_font_log_font);

    if (console_font == NULL) {
        raw_print("Unable to create console font\n");
        return FALSE;
    }

    /* select font */
    HGDIOBJ saved_font = SelectObject(hDC, console_font);

    /* determine whether it is a true type font */
    TEXTMETRICA tm;
    success = GetTextMetricsA(hDC, &tm);

    if (!success) {
        raw_print("Unable to get console font text metrics\n");
        goto clean_up;
    }

    boolean isTrueType = (tm.tmPitchAndFamily & TMPF_TRUETYPE) != 0;

    /* determine which glyphs are used */
    boolean used[256];
    memset(used, 0, sizeof(used));
    for (int i = 0; i < SYM_MAX; i++) {
        used[primary_syms[i]] = TRUE;
        used[rogue_syms[i]] = TRUE;
    }

    int wcUsedCount = 0;
    wchar_t wcUsed[256];
    for (int i = 0; i < sizeof(used); i++)
        if (used[i])
            wcUsed[wcUsedCount++] = cp437[i];

    /* measure the set of used glyphs to ensure they fit */
    boolean all_glyphs_fit = TRUE;

    for (int i = 0; i < wcUsedCount; i++) {
        int width;
        if (isTrueType) {
            ABC abc;
            success = GetCharABCWidthsW(hDC, wcUsed[i], wcUsed[i], &abc);
            width = abc.abcA + abc.abcB + abc.abcC;
        } else {
            success = GetCharWidthW(hDC, wcUsed[i], wcUsed[i], &width);
        }

        if (success && width > console_font_info.dwFontSize.X) {
            all_glyphs_fit = FALSE;
            break;
        }
    }

clean_up:

    SelectObject(hDC, saved_font);
    DeleteObject(console_font);

    return all_glyphs_fit;
}

/* set_known_good_console_font sets the code page and font used by the console
 * to settings know to work well with NetHack.  It also saves the original
 * settings so that they can be restored prior to NetHack exit.
 */
void
set_known_good_console_font()
{
    CONSOLE_FONT_INFOEX console_font_info;
    console_font_info.cbSize = sizeof(console_font_info);
    BOOL success = GetCurrentConsoleFontEx(console.hConOut, FALSE,
                                            &console_font_info);

    console.font_changed = TRUE;
    console.original_font_info = console_font_info;
    console.original_code_page = GetConsoleOutputCP();

    wcscpy_s(console_font_info.FaceName,
        sizeof(console_font_info.FaceName)
            / sizeof(console_font_info.FaceName[0]),
        L"Consolas");

    success = SetConsoleOutputCP(437);
    nhassert(success);

    success = SetCurrentConsoleFontEx(console.hConOut, FALSE, &console_font_info);
    nhassert(success);
}

/* restore_original_console_font will restore the console font and code page
 * settings to what they were when NetHack was launched.
 */
void
restore_original_console_font()
{
    if (console.font_changed) {
        BOOL success;
        raw_print("Restoring original font and code page\n");
        success = SetConsoleOutputCP(console.original_code_page);
        if (!success)
            raw_print("Unable to restore original code page\n");

        success = SetCurrentConsoleFontEx(console.hConOut, FALSE,
                                            &console.original_font_info);
        if (!success)
            raw_print("Unable to restore original font\n");

        console.font_changed = FALSE;
    }
}

/* set_cp_map() creates a mapping of every possible character of a code
 * page to its corresponding WCHAR.  This is necessary due to the high
 * cost of making calls to MultiByteToWideChar() for every character we
 * wish to print to the console.
 */

void set_cp_map()
{
    if (console.has_unicode) {
        UINT codePage = GetConsoleOutputCP();

        if (codePage == 437) {
            memcpy(console.cpMap, cp437, sizeof(console.cpMap));
        } else {
            for (int i = 0; i < 256; i++) {
                char c = (char)i;
                int count = MultiByteToWideChar(codePage, 0, &c, 1,
                                                &console.cpMap[i], 1);
                nhassert(count == 1);

                // If a character was mapped to unicode control codes,
                // remap to the appropriate unicode character per our
                // code page 437 mappings.
                if (console.cpMap[i] < 32)
                    console.cpMap[i] = cp437[console.cpMap[i]];
            }        
        }

    }
}

#if 0
/* early_raw_print() is used during early game intialization prior to the
 * setting up of the windowing system.  This allows early errors and panics
 * to have there messages displayed.
 *
 * early_raw_print() eventually gets replaced by tty_raw_print().
 *
 */

void early_raw_print(const char *s)
{
    if (console.hConOut == NULL)
        return;

    nhassert(console.cursor.X >= 0 && console.cursor.X < console.width);
    nhassert(console.cursor.Y >= 0 && console.cursor.Y < console.height);

    WORD attribute = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE;
    DWORD unused;

    while (*s != '\0') {
        switch (*s) {
        case '\n':
            if (console.cursor.Y < console.height - 1)
                console.cursor.Y++;
        /* fall through */
        case '\r':
            console.cursor.X = 1;
            break;
        case '\b':
            if (console.cursor.X > 1) {
                console.cursor.X--;
            } else if(console.cursor.Y > 0) {
                console.cursor.X = console.width - 1;
                console.cursor.Y--;
            }
            break;
        default:
            WriteConsoleOutputAttribute(console.hConOut, &attribute,
                                            1, console.cursor, &unused);
            WriteConsoleOutputCharacterA(console.hConOut, s,
                                            1, console.cursor, &unused);
            if (console.cursor.X == console.width - 1) {
                if (console.cursor.Y < console.height - 1) {
                    console.cursor.X = 1;
                    console.cursor.Y++;
                }
            } else {
                console.cursor.X++;
            }
        }
        s++;
    }

    nhassert(console.cursor.X >= 0 && console.cursor.X < console.width);
    nhassert(console.cursor.Y >= 0 && console.cursor.Y < console.height);

    SetConsoleCursorPosition(console.hConOut, console.cursor);

}
#endif

/* nethack_enter_nttty() is the first thing that is called from main
 * once the tty port is confirmed.
 *
 * We initialize all console state to support rendering to the console
 * through out flipping support at this time.  This allows us to support
 * raw_print prior to our returning.
 *
 * During this early initialization, we also determine the width and
 * height of the console that will be used.  This width and height will
 * not later change.
 *
 * We also check and set the console font to a font that we know will work
 * well with nethack.
 *
 * The intent of this early initialization is to get all state that is
 * not dependent upon game options initialized allowing us to simplify
 * any additional initialization that might be needed when we are actually
 * asked to open.
 *
 * Other then the call below which clears the entire console buffer, no
 * other code outputs directly to the console other then the code that
 * handles flipping the back buffer.
 *
 */

void nethack_enter_nttty()
{
#if 0
    /* set up state needed by early_raw_print() */
    windowprocs.win_raw_print = early_raw_print;
#endif
    console.hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
    nhassert(console.hConOut != NULL); // NOTE: this assert will not print

    GetConsoleScreenBufferInfo(console.hConOut, &console.origcsbi);

    /* Testing of widths != COLNO has not turned up any problems.  Need
     * to do a bit more testing and then we are likely to enable having
     * console width match window width.
     */
#if 0
    console.width = console.origcsbi.srWindow.Right -
                     console.origcsbi.srWindow.Left + 1;
    console.Width = max(console.Width, COLNO);
#else
    console.width = COLNO;
#endif

    console.height = console.origcsbi.srWindow.Bottom -
                     console.origcsbi.srWindow.Top + 1;
    console.height = max(console.height, ROWNO + 3);

    console.buffer_size = console.width * console.height;


    /* clear the entire console buffer */
    int size = console.origcsbi.dwSize.X * console.origcsbi.dwSize.Y;
    DWORD unused;
    set_console_cursor(0, 0);
    FillConsoleOutputAttribute(
        console.hConOut, CONSOLE_CLEAR_ATTRIBUTE,
        size, console.cursor, &unused);

    FillConsoleOutputCharacter(
        console.hConOut, CONSOLE_CLEAR_CHARACTER,
        size, console.cursor, &unused);

    set_console_cursor(1, 0);
    SetConsoleCursorPosition(console.hConOut, console.cursor);

    /* At this point early_raw_print will work */

    console.hConIn = GetStdHandle(STD_INPUT_HANDLE);
    nhassert(console.hConIn  != NULL);

    /* grow the size of the console buffer if it is not wide enough */
    if (console.origcsbi.dwSize.X < console.width) {
        COORD size = {
            size.Y = console.origcsbi.dwSize.Y,
            size.X = console.width
        };

        SetConsoleScreenBufferSize(console.hConOut, size);
    }

    /* setup front and back buffers */
    int buffer_size_bytes = sizeof(cell_t) * console.buffer_size;

    console.front_buffer = (cell_t *)malloc(buffer_size_bytes);
    buffer_fill_to_end(console.front_buffer, &undefined_cell, 0, 0);

    console.back_buffer = (cell_t *)malloc(buffer_size_bytes);
    buffer_fill_to_end(console.back_buffer, &clear_cell, 0, 0);

    /* determine whether OS version has unicode support */
    console.has_unicode = ((GetVersion() & 0x80000000) == 0);

    /* check the font before we capture the code page map */
    check_and_set_font();
    set_cp_map();

    /* Set console mode */
    DWORD cmode, mask;
    GetConsoleMode(console.hConIn, &cmode);
#ifdef NO_MOUSE_ALLOWED
    mask = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT
           | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT;
#else
    mask = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT
           | ENABLE_WINDOW_INPUT;
#endif
    /* Turn OFF the settings specified in the mask */
    cmode &= ~mask;
#ifndef NO_MOUSE_ALLOWED
    cmode |= ENABLE_MOUSE_INPUT;
#endif
    SetConsoleMode(console.hConIn, cmode);

    /* load default keyboard handler */
    HKL keyboard_layout = GetKeyboardLayout(0);
    DWORD primary_language = (UINT_PTR) keyboard_layout & 0x3f;

    /* This was overriding the handler that had already
       been loaded during options parsing. Needs to
       check first */
    if (!iflags.altkeyhandler[0]) {
        if (primary_language == LANG_ENGLISH) {
            if (!load_keyboard_handler("nhdefkey"))
                error("Unable to load nhdefkey.dll");
        } else {
            if (!load_keyboard_handler("nhraykey"))
                error("Unable to load nhraykey.dll");
        }
    }
}
#endif /* TTY_GRAPHICS */

/* this is used as a printf() replacement when the window
 * system isn't initialized yet
 */
void msmsg
VA_DECL(const char *, fmt)
{
    char buf[ROWNO * COLNO]; /* worst case scenario */
    VA_START(fmt);
    VA_INIT(fmt, const char *);
    (void) vsnprintf(buf, sizeof buf, fmt, VA_ARGS);
    if (redirect_stdout)
        fprintf(stdout, "%s", buf);
    else {
#ifdef TTY_GRAPHICS
        if(!init_ttycolor_completed)
            init_ttycolor();
        /* if we have generated too many messages ... ask the user to
         * confirm and then clear.
         */
        if (console.cursor.Y > console.height - 4) {
            xputs("Hit <Enter> to continue.");
            while (pgetchar() != '\n')
                ;
            raw_clear_screen();
            set_console_cursor(1, 0);
        }
        xputs(buf);
        if (ttyDisplay)
            curs(BASE_WINDOW, console.cursor.X + 1, console.cursor.Y);
#else
        fprintf(stdout, "%s", buf);
#endif
    }
    VA_END();
    return;
}

#endif /* WIN32 */
