/* NetHack 3.6	mhmap.c	$NHDT-Date: 1432512799 2015/05/25 00:13:19 $  $NHDT-Branch: master $:$NHDT-Revision: 1.40 $ */
/* Copyright (C) 2001 by Alex Kompel 	 */
/* NetHack may be freely redistributed.  See license for details. */

#include "winMS.h"
#include "mhmap.h"
#include "mhmsg.h"
#include "mhinput.h"
#include "mhfont.h"

#include "patchlevel.h"

#define NHMAP_FONT_NAME TEXT("Terminal")
#define MAXWINDOWTEXT 255

extern short glyph2tile[];

/* map window data */
typedef struct mswin_nethack_map_window {
    int map[COLNO][ROWNO]; /* glyph map */

    int mapMode;              /* current map mode */
    boolean bAsciiMode;       /* switch ASCII/tiled mode */
    boolean bFitToScreenMode; /* switch Fit map to screen mode on/off */
    int xPos, yPos;           /* scroll position */
    int xPageSize, yPageSize; /* scroll page size */
    int xCur, yCur;           /* position of the cursor */
    int xScrTile, yScrTile;   /* size of display tile */
    POINT map_orig;           /* map origin point */
    HFONT hMapFont;           /* font for ASCII mode */
    int xLastMouseClick, yLastMouseClick; /* last mouse click */
} NHMapWindow, *PNHMapWindow;

static TCHAR szNHMapWindowClass[] = TEXT("MSNethackMapWndClass");
LRESULT CALLBACK MapWndProc(HWND, UINT, WPARAM, LPARAM);
static void register_map_window_class(void);
static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
static void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
static void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
static void onPaint(HWND hWnd);
static void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
static void nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut);
#if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
static void nhglyph2charcolor(short glyph, uchar *ch, int *color);
#endif
static COLORREF nhcolor_to_RGB(int c);

HWND
mswin_init_map_window()
{
    static int run_once = 0;
    HWND ret;
    DWORD styles;

    if (!run_once) {
        register_map_window_class();
        run_once = 1;
    }

    styles = WS_CHILD | WS_CLIPSIBLINGS;
    if (!GetNHApp()->bHideScrollBars)
        styles |= WS_HSCROLL | WS_VSCROLL;
    ret = CreateWindow(
        szNHMapWindowClass, /* registered class name */
        NULL,               /* window name */
        styles,             /* window style */
        0,                  /* horizontal position of window - set it later */
        0,                  /* vertical position of window - set it later */
        0,                  /* window width - set it later */
        0,                  /* window height - set it later*/
        GetNHApp()->hMainWnd, /* handle to parent or owner window */
        NULL,                 /* menu handle or child identifier */
        GetNHApp()->hApp,     /* handle to application instance */
        NULL);                /* window-creation data */
    if (!ret) {
        panic("Cannot create map window");
    }
    return ret;
}

void
mswin_map_stretch(HWND hWnd, LPSIZE lpsz, BOOL redraw)
{
    PNHMapWindow data;
    RECT client_rt;
    SCROLLINFO si;
    SIZE wnd_size;
    LOGFONT lgfnt;

    /* check arguments */
    if (!IsWindow(hWnd) || !lpsz || lpsz->cx <= 0 || lpsz->cy <= 0)
        return;

    /* calculate window size */
    GetClientRect(hWnd, &client_rt);
    wnd_size.cx = client_rt.right - client_rt.left;
    wnd_size.cy = client_rt.bottom - client_rt.top;

    /* set new screen tile size */
    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);
    data->xScrTile =
        max(1, (data->bFitToScreenMode ? wnd_size.cx : lpsz->cx) / COLNO);
    data->yScrTile =
        max(1, (data->bFitToScreenMode ? wnd_size.cy : lpsz->cy) / ROWNO);

    /* set map origin point */
    data->map_orig.x =
        max(0, client_rt.left + (wnd_size.cx - data->xScrTile * COLNO) / 2);
    data->map_orig.y =
        max(0, client_rt.top + (wnd_size.cy - data->yScrTile * ROWNO) / 2);

    data->map_orig.x -= data->map_orig.x % data->xScrTile;
    data->map_orig.y -= data->map_orig.y % data->yScrTile;

    /* adjust horizontal scroll bar */
    if (data->bFitToScreenMode)
        data->xPageSize = COLNO + 1; /* disable scroll bar */
    else
        data->xPageSize = wnd_size.cx / data->xScrTile;

    if (data->xPageSize >= COLNO) {
        data->xPos = 0;
        GetNHApp()->bNoHScroll = TRUE;
    } else {
        GetNHApp()->bNoHScroll = FALSE;
        data->xPos = max(
            0, min(COLNO - data->xPageSize + 1, u.ux - data->xPageSize / 2));
    }

    if (!GetNHApp()->bHideScrollBars) {
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
        si.nMin = 0;
        si.nMax = COLNO;
        si.nPage = data->xPageSize;
        si.nPos = data->xPos;
        SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
    }

    /* adjust vertical scroll bar */
    if (data->bFitToScreenMode)
        data->yPageSize = ROWNO + 1; /* disable scroll bar */
    else
        data->yPageSize = wnd_size.cy / data->yScrTile;

    if (data->yPageSize >= ROWNO) {
        data->yPos = 0;
        GetNHApp()->bNoVScroll = TRUE;
    } else {
        GetNHApp()->bNoVScroll = FALSE;
        data->yPos = max(
            0, min(ROWNO - data->yPageSize + 1, u.uy - data->yPageSize / 2));
    }

    if (!GetNHApp()->bHideScrollBars) {
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
        si.nMin = 0;
        si.nMax = ROWNO;
        si.nPage = data->yPageSize;
        si.nPos = data->yPos;
        SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
    }

    /* create font */
    if (data->hMapFont)
        DeleteObject(data->hMapFont);
    ZeroMemory(&lgfnt, sizeof(lgfnt));
    lgfnt.lfHeight = -data->yScrTile;          // height of font
    lgfnt.lfWidth = -data->xScrTile;           // average character width
    lgfnt.lfEscapement = 0;                    // angle of escapement
    lgfnt.lfOrientation = 0;                   // base-line orientation angle
    lgfnt.lfWeight = FW_NORMAL;                // font weight
    lgfnt.lfItalic = FALSE;                    // italic attribute option
    lgfnt.lfUnderline = FALSE;                 // underline attribute option
    lgfnt.lfStrikeOut = FALSE;                 // strikeout attribute option
    lgfnt.lfCharSet = mswin_charset();         // character set identifier
    lgfnt.lfOutPrecision = OUT_DEFAULT_PRECIS; // output precision
    lgfnt.lfClipPrecision = CLIP_DEFAULT_PRECIS; // clipping precision
    lgfnt.lfQuality = DEFAULT_QUALITY;           // output quality
    if (iflags.wc_font_map && *iflags.wc_font_map) {
        lgfnt.lfPitchAndFamily = DEFAULT_PITCH; // pitch and family
        NH_A2W(iflags.wc_font_map, lgfnt.lfFaceName, LF_FACESIZE);
    } else {
        lgfnt.lfPitchAndFamily = FIXED_PITCH; // pitch and family
        _tcsncpy(lgfnt.lfFaceName, NHMAP_FONT_NAME, LF_FACESIZE);
    }
    data->hMapFont = CreateFontIndirect(&lgfnt);

    mswin_cliparound(data->xCur, data->yCur);

    if (redraw)
        InvalidateRect(hWnd, NULL, TRUE);
}

/* set map mode */
int
mswin_map_mode(HWND hWnd, int mode)
{
    PNHMapWindow data;
    int oldMode;
    SIZE mapSize;

    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);
    if (mode == data->mapMode)
        return mode;

    oldMode = data->mapMode;
    data->mapMode = mode;

    switch (data->mapMode) {
    case MAP_MODE_ASCII4x6:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 4 * COLNO;
        mapSize.cy = 6 * ROWNO;
        break;

    case MAP_MODE_ASCII6x8:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 6 * COLNO;
        mapSize.cy = 8 * ROWNO;
        break;

    case MAP_MODE_ASCII8x8:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 8 * COLNO;
        mapSize.cy = 8 * ROWNO;
        break;

    case MAP_MODE_ASCII16x8:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 16 * COLNO;
        mapSize.cy = 8 * ROWNO;
        break;

    case MAP_MODE_ASCII7x12:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 7 * COLNO;
        mapSize.cy = 12 * ROWNO;
        break;

    case MAP_MODE_ASCII8x12:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 8 * COLNO;
        mapSize.cy = 12 * ROWNO;
        break;

    case MAP_MODE_ASCII16x12:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 16 * COLNO;
        mapSize.cy = 12 * ROWNO;
        break;

    case MAP_MODE_ASCII12x16:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 12 * COLNO;
        mapSize.cy = 16 * ROWNO;
        break;

    case MAP_MODE_ASCII10x18:
        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = 10 * COLNO;
        mapSize.cy = 18 * ROWNO;
        break;

    case MAP_MODE_ASCII_FIT_TO_SCREEN: {
        RECT client_rt;
        GetClientRect(hWnd, &client_rt);
        mapSize.cx = client_rt.right - client_rt.left;
        mapSize.cy = client_rt.bottom - client_rt.top;

        data->bAsciiMode = TRUE;
        data->bFitToScreenMode = TRUE;
    } break;

    case MAP_MODE_TILES_FIT_TO_SCREEN: {
        RECT client_rt;
        GetClientRect(hWnd, &client_rt);
        mapSize.cx = client_rt.right - client_rt.left;
        mapSize.cy = client_rt.bottom - client_rt.top;

        data->bAsciiMode = FALSE;
        data->bFitToScreenMode = TRUE;
    } break;

    case MAP_MODE_TILES:
    default:
        data->bAsciiMode = FALSE;
        data->bFitToScreenMode = FALSE;
        mapSize.cx = GetNHApp()->mapTile_X * COLNO;
        mapSize.cy = GetNHApp()->mapTile_Y * ROWNO;
        break;
    }

    mswin_map_stretch(hWnd, &mapSize, TRUE);

    return oldMode;
}

/* retrieve cursor position */
void
mswin_map_get_cursor(HWND hWnd, int *x, int *y)
{
    PNHMapWindow data;

    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);
    if (!data)
        panic("mswin_map_get_cursor: no window data");
    if (x)
        *x = data->xCur;
    if (y)
        *y = data->yCur;
}

/* register window class for map window */
void
register_map_window_class()
{
    WNDCLASS wcex;
    ZeroMemory(&wcex, sizeof(wcex));

    /* window class */
    wcex.style = CS_NOCLOSE | CS_DBLCLKS;
    wcex.lpfnWndProc = (WNDPROC) MapWndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = GetNHApp()->hApp;
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground =
        CreateSolidBrush(RGB(0, 0, 0)); /* set backgroup here */
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szNHMapWindowClass;

    if (!RegisterClass(&wcex)) {
        panic("cannot register Map window class");
    }
}

/* map window procedure */
LRESULT CALLBACK
MapWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PNHMapWindow data;
    int x, y;

    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);
    switch (message) {
    case WM_CREATE:
        onCreate(hWnd, wParam, lParam);
        break;

    case WM_MSNH_COMMAND:
        onMSNHCommand(hWnd, wParam, lParam);
        break;

    case WM_PAINT:
        onPaint(hWnd);
        break;

    case WM_SETFOCUS:
        /* transfer focus back to the main window */
        SetFocus(GetNHApp()->hMainWnd);
        break;

    case WM_HSCROLL:
        onMSNH_HScroll(hWnd, wParam, lParam);
        break;

    case WM_VSCROLL:
        onMSNH_VScroll(hWnd, wParam, lParam);
        break;

    case WM_SIZE: {
        SIZE size;

        if (data->bFitToScreenMode) {
            size.cx = LOWORD(lParam);
            size.cy = HIWORD(lParam);
        } else {
            /* mapping factor is unchaged we just need to adjust scroll bars
             */
            size.cx = data->xScrTile * COLNO;
            size.cy = data->yScrTile * ROWNO;
        }
        mswin_map_stretch(hWnd, &size, TRUE);
    } break;

    case WM_LBUTTONDOWN:
        x = max(0, min(COLNO, data->xPos
                                  + (LOWORD(lParam) - data->map_orig.x)
                                        / data->xScrTile));
        y = max(0, min(ROWNO, data->yPos
                                  + (HIWORD(lParam) - data->map_orig.y)
                                        / data->yScrTile));

        NHEVENT_MS(CLICK_1, x, y);

        data->xLastMouseClick = x;
        data->yLastMouseClick = y;
        return 0;

    case WM_LBUTTONDBLCLK:
        x = max(0, min(COLNO, data->xPos
                                  + (LOWORD(lParam) - data->map_orig.x)
                                        / data->xScrTile));
        y = max(0, min(ROWNO, data->yPos
                                  + (HIWORD(lParam) - data->map_orig.y)
                                        / data->yScrTile));

        /* if map has scrolled since the last mouse click - ignore
         * double-click message */
        if (data->xLastMouseClick == x && data->yLastMouseClick == y) {
            NHEVENT_MS(CLICK_1, x, y);
        }
        return 0;

    case WM_DESTROY:
        if (data->hMapFont)
            DeleteObject(data->hMapFont);
        free(data);
        SetWindowLong(hWnd, GWL_USERDATA, (LONG) 0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

/* on WM_COMMAND */
void
onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    PNHMapWindow data;
    RECT rt;

    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);
    switch (wParam) {
    case MSNH_MSG_PRINT_GLYPH: {
        PMSNHMsgPrintGlyph msg_data = (PMSNHMsgPrintGlyph) lParam;
        data->map[msg_data->x][msg_data->y] = msg_data->glyph;

        /* invalidate the update area */
        nhcoord2display(data, msg_data->x, msg_data->y, &rt);
        InvalidateRect(hWnd, &rt, TRUE);
    } break;

    case MSNH_MSG_CLIPAROUND: {
        PMSNHMsgClipAround msg_data = (PMSNHMsgClipAround) lParam;
        int x, y;
        BOOL scroll_x, scroll_y;
        int mcam = iflags.wc_scroll_margin;

        /* calculate if you should clip around */
        scroll_x =
            !GetNHApp()->bNoHScroll
            && (msg_data->x < (data->xPos + mcam)
                || msg_data->x > (data->xPos + data->xPageSize - mcam));
        scroll_y =
            !GetNHApp()->bNoVScroll
            && (msg_data->y < (data->yPos + mcam)
                || msg_data->y > (data->yPos + data->yPageSize - mcam));

        mcam += iflags.wc_scroll_amount - 1;
        /* get page size and center horizontally on x-position */
        if (scroll_x) {
            if (data->xPageSize <= 2 * mcam) {
                x = max(0, min(COLNO, msg_data->x - data->xPageSize / 2));
            } else if (msg_data->x < data->xPos + data->xPageSize / 2) {
                x = max(0, min(COLNO, msg_data->x - mcam));
            } else {
                x = max(0, min(COLNO, msg_data->x - data->xPageSize + mcam));
            }
            SendMessage(hWnd, WM_HSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, x),
                        (LPARAM) NULL);
        }

        /* get page size and center vertically on y-position */
        if (scroll_y) {
            if (data->yPageSize <= 2 * mcam) {
                y = max(0, min(ROWNO, msg_data->y - data->yPageSize / 2));
            } else if (msg_data->y < data->yPos + data->yPageSize / 2) {
                y = max(0, min(ROWNO, msg_data->y - mcam));
            } else {
                y = max(0, min(ROWNO, msg_data->y - data->yPageSize + mcam));
            }
            SendMessage(hWnd, WM_VSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, y),
                        (LPARAM) NULL);
        }
    } break;

    case MSNH_MSG_CLEAR_WINDOW: {
        int i, j;
        for (i = 0; i < COLNO; i++)
            for (j = 0; j < ROWNO; j++) {
                data->map[i][j] = -1;
            }
        InvalidateRect(hWnd, NULL, TRUE);
    } break;

    case MSNH_MSG_CURSOR: {
        PMSNHMsgCursor msg_data = (PMSNHMsgCursor) lParam;
        HDC hdc;
        RECT rt;

        /* move focus rectangle at the cursor postion */
        hdc = GetDC(hWnd);

        nhcoord2display(data, data->xCur, data->yCur, &rt);
        if (data->bAsciiMode) {
            PatBlt(hdc, rt.left, rt.top, rt.right - rt.left,
                   rt.bottom - rt.top, DSTINVERT);
        } else {
            DrawFocusRect(hdc, &rt);
        }

        data->xCur = msg_data->x;
        data->yCur = msg_data->y;

        nhcoord2display(data, data->xCur, data->yCur, &rt);
        if (data->bAsciiMode) {
            PatBlt(hdc, rt.left, rt.top, rt.right - rt.left,
                   rt.bottom - rt.top, DSTINVERT);
        } else {
            DrawFocusRect(hdc, &rt);
        }

        ReleaseDC(hWnd, hdc);
    } break;
    }
}

/* on WM_CREATE */
void
onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    PNHMapWindow data;
    int i, j;

    /* set window data */
    data = (PNHMapWindow) malloc(sizeof(NHMapWindow));
    if (!data)
        panic("out of memory");

    ZeroMemory(data, sizeof(NHMapWindow));
    for (i = 0; i < COLNO; i++)
        for (j = 0; j < ROWNO; j++) {
            data->map[i][j] = -1;
        }

    data->bAsciiMode = FALSE;

    data->xScrTile = GetNHApp()->mapTile_X;
    data->yScrTile = GetNHApp()->mapTile_Y;

    data->xLastMouseClick = data->yLastMouseClick = -1;

    SetWindowLong(hWnd, GWL_USERDATA, (LONG) data);
}

/* on WM_PAINT */
void
onPaint(HWND hWnd)
{
    PNHMapWindow data;
    PAINTSTRUCT ps;
    HDC hDC;
    HDC tileDC;
    HGDIOBJ saveBmp;
    RECT paint_rt;
    int i, j;

    /* get window data */
    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);

    hDC = BeginPaint(hWnd, &ps);

    /* calculate paint rectangle */
    if (!IsRectEmpty(&ps.rcPaint)) {
        /* calculate paint rectangle */
        paint_rt.left =
            max(data->xPos
                    + (ps.rcPaint.left - data->map_orig.x) / data->xScrTile,
                0);
        paint_rt.top = max(
            data->yPos + (ps.rcPaint.top - data->map_orig.y) / data->yScrTile,
            0);
        paint_rt.right = min(
            data->xPos
                + (ps.rcPaint.right - data->map_orig.x) / data->xScrTile + 1,
            COLNO);
        paint_rt.bottom = min(
            data->yPos
                + (ps.rcPaint.bottom - data->map_orig.y) / data->yScrTile + 1,
            ROWNO);

        if (data->bAsciiMode || Is_rogue_level(&u.uz)) {
            /* You enter a VERY primitive world! */
            HGDIOBJ oldFont;

            oldFont = SelectObject(hDC, data->hMapFont);
            SetBkMode(hDC, TRANSPARENT);

            /* draw the map */
            for (i = paint_rt.left; i < paint_rt.right; i++)
                for (j = paint_rt.top; j < paint_rt.bottom; j++)
                    if (data->map[i][j] >= 0) {
                        char ch;
                        TCHAR wch;
                        RECT glyph_rect;
                        int color;
                        unsigned special;
                        int mgch;
                        HBRUSH back_brush;
                        COLORREF OldFg;

                        nhcoord2display(data, i, j, &glyph_rect);

#if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
                        nhglyph2charcolor(data->map[i][j], &ch, &color);
                        OldFg = SetTextColor(hDC, nhcolor_to_RGB(color));
#else
                        /* rely on NetHack core helper routine */
                        (void) mapglyph(data->map[i][j], &mgch, &color,
                                        &special, i, j, 0);
                        ch = (char) mgch;
                        if (((special & MG_PET) && iflags.hilite_pet)
                            || ((special & MG_DETECT)
                                && iflags.use_inverse)) {
                            back_brush =
                                CreateSolidBrush(nhcolor_to_RGB(CLR_GRAY));
                            FillRect(hDC, &glyph_rect, back_brush);
                            DeleteObject(back_brush);
                            switch (color) {
                            case CLR_GRAY:
                            case CLR_WHITE:
                                OldFg = SetTextColor(
                                    hDC, nhcolor_to_RGB(CLR_BLACK));
                                break;
                            default:
                                OldFg =
                                    SetTextColor(hDC, nhcolor_to_RGB(color));
                            }
                        } else {
                            OldFg = SetTextColor(hDC, nhcolor_to_RGB(color));
                        }
#endif

                        DrawText(hDC, NH_A2W(&ch, &wch, 1), 1, &glyph_rect,
                                 DT_CENTER | DT_VCENTER | DT_NOPREFIX);
                        SetTextColor(hDC, OldFg);
                    }
            SelectObject(hDC, oldFont);
        } else {
            /* prepare tiles DC for mapping */
            tileDC = CreateCompatibleDC(hDC);
            saveBmp = SelectObject(tileDC, GetNHApp()->bmpMapTiles);

            /* draw the map */
            for (i = paint_rt.left; i < paint_rt.right; i++)
                for (j = paint_rt.top; j < paint_rt.bottom; j++)
                    if (data->map[i][j] >= 0) {
                        short ntile;
                        int t_x, t_y;
                        RECT glyph_rect;

                        ntile = glyph2tile[data->map[i][j]];
                        t_x = (ntile % GetNHApp()->mapTilesPerLine)
                              * GetNHApp()->mapTile_X;
                        t_y = (ntile / GetNHApp()->mapTilesPerLine)
                              * GetNHApp()->mapTile_Y;

                        nhcoord2display(data, i, j, &glyph_rect);

                        StretchBlt(hDC, glyph_rect.left, glyph_rect.top,
                                   data->xScrTile, data->yScrTile, tileDC,
                                   t_x, t_y, GetNHApp()->mapTile_X,
                                   GetNHApp()->mapTile_Y, SRCCOPY);
                        if (glyph_is_pet(data->map[i][j])
                            && iflags.wc_hilite_pet) {
                            /* apply pet mark transparently over
                               pet image */
                            HDC hdcPetMark;
                            HBITMAP bmPetMarkOld;

                            /* this is DC for petmark bitmap */
                            hdcPetMark = CreateCompatibleDC(hDC);
                            bmPetMarkOld = SelectObject(
                                hdcPetMark, GetNHApp()->bmpPetMark);

                            nhapply_image_transparent(
                                hDC, glyph_rect.left, glyph_rect.top,
                                data->xScrTile, data->yScrTile, hdcPetMark, 0,
                                0, TILE_X, TILE_Y, TILE_BK_COLOR);
                            SelectObject(hdcPetMark, bmPetMarkOld);
                            DeleteDC(hdcPetMark);
                        }
                    }
            SelectObject(tileDC, saveBmp);
            DeleteDC(tileDC);
        }

        /* draw focus rect */
        nhcoord2display(data, data->xCur, data->yCur, &paint_rt);
        if (data->bAsciiMode) {
            PatBlt(hDC, paint_rt.left, paint_rt.top,
                   paint_rt.right - paint_rt.left,
                   paint_rt.bottom - paint_rt.top, DSTINVERT);
        } else {
            DrawFocusRect(hDC, &paint_rt);
        }
    }
    EndPaint(hWnd, &ps);
}

/* on WM_VSCROLL */
void
onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    PNHMapWindow data;
    SCROLLINFO si;
    int yNewPos;
    int yDelta;

    /* get window data */
    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);

    switch (LOWORD(wParam)) {
    /* User clicked shaft left of the scroll box. */
    case SB_PAGEUP:
        yNewPos = data->yPos - data->yPageSize;
        break;

    /* User clicked shaft right of the scroll box. */
    case SB_PAGEDOWN:
        yNewPos = data->yPos + data->yPageSize;
        break;

    /* User clicked the left arrow. */
    case SB_LINEUP:
        yNewPos = data->yPos - 1;
        break;

    /* User clicked the right arrow. */
    case SB_LINEDOWN:
        yNewPos = data->yPos + 1;
        break;

    /* User dragged the scroll box. */
    case SB_THUMBTRACK:
        yNewPos = HIWORD(wParam);
        break;

    default:
        yNewPos = data->yPos;
    }

    yNewPos = max(0, min(ROWNO - data->yPageSize + 1, yNewPos));
    if (yNewPos == data->yPos)
        return;

    yDelta = yNewPos - data->yPos;
    data->yPos = yNewPos;

    ScrollWindowEx(hWnd, 0, -data->yScrTile * yDelta, (CONST RECT *) NULL,
                   (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
                   SW_INVALIDATE | SW_ERASE);

    if (!GetNHApp()->bHideScrollBars) {
        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        si.nPos = data->yPos;
        SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
    }
}

/* on WM_HSCROLL */
void
onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    PNHMapWindow data;
    SCROLLINFO si;
    int xNewPos;
    int xDelta;

    /* get window data */
    data = (PNHMapWindow) GetWindowLong(hWnd, GWL_USERDATA);

    switch (LOWORD(wParam)) {
    /* User clicked shaft left of the scroll box. */
    case SB_PAGEUP:
        xNewPos = data->xPos - data->xPageSize;
        break;

    /* User clicked shaft right of the scroll box. */
    case SB_PAGEDOWN:
        xNewPos = data->xPos + data->xPageSize;
        break;

    /* User clicked the left arrow. */
    case SB_LINEUP:
        xNewPos = data->xPos - 1;
        break;

    /* User clicked the right arrow. */
    case SB_LINEDOWN:
        xNewPos = data->xPos + 1;
        break;

    /* User dragged the scroll box. */
    case SB_THUMBTRACK:
        xNewPos = HIWORD(wParam);
        break;

    default:
        xNewPos = data->xPos;
    }

    xNewPos = max(0, min(COLNO - data->xPageSize + 1, xNewPos));
    if (xNewPos == data->xPos)
        return;

    xDelta = xNewPos - data->xPos;
    data->xPos = xNewPos;

    ScrollWindowEx(hWnd, -data->xScrTile * xDelta, 0, (CONST RECT *) NULL,
                   (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
                   SW_INVALIDATE | SW_ERASE);

    if (!GetNHApp()->bHideScrollBars) {
        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        si.nPos = data->xPos;
        SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
    }
}

/* map nethack map coordinates to the screen location */
void
nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut)
{
    lpOut->left = (x - data->xPos) * data->xScrTile + data->map_orig.x;
    lpOut->top = (y - data->yPos) * data->yScrTile + data->map_orig.y;
    lpOut->right = lpOut->left + data->xScrTile;
    lpOut->bottom = lpOut->top + data->yScrTile;
}

#if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
/* map glyph to character/color combination */
void
nhglyph2charcolor(short g, uchar *ch, int *color)
{
    int offset;
#define zap_color(n) *color = iflags.use_color ? zapcolors[n] : NO_COLOR
#define cmap_color(n) *color = iflags.use_color ? defsyms[n].color : NO_COLOR
#define obj_color(n) \
    *color = iflags.use_color ? objects[n].oc_color : NO_COLOR
#define mon_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR
#define pet_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR
#define warn_color(n) \
    *color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR

    if ((offset = (g - GLYPH_WARNING_OFF)) >= 0) { /* a warning flash */
        *ch = showsyms[offset + SYM_OFF_W];
        warn_color(offset);
    } else if ((offset = (g - GLYPH_SWALLOW_OFF)) >= 0) { /* swallow */
        /* see swallow_to_glyph() in display.c */
        *ch = (uchar) showsyms[(S_sw_tl + (offset & 0x7)) + SYM_OFF_P];
        mon_color(offset >> 3);
    } else if ((offset = (g - GLYPH_ZAP_OFF)) >= 0) { /* zap beam */
        /* see zapdir_to_glyph() in display.c */
        *ch = showsyms[(S_vbeam + (offset & 0x3)) + SYM_OFF_P];
        zap_color((offset >> 2));
    } else if ((offset = (g - GLYPH_CMAP_OFF)) >= 0) { /* cmap */
        *ch = showsyms[offset + SYM_OFF_P];
        cmap_color(offset);
    } else if ((offset = (g - GLYPH_OBJ_OFF)) >= 0) { /* object */
        *ch = showsyms[(int) objects[offset].oc_class + SYM_OFF_O];
        obj_color(offset);
    } else if ((offset = (g - GLYPH_BODY_OFF)) >= 0) { /* a corpse */
        *ch = showsyms[(int) objects[CORPSE].oc_class + SYM_OFF_O];
        mon_color(offset);
    } else if ((offset = (g - GLYPH_PET_OFF)) >= 0) { /* a pet */
        *ch = showsyms[(int) mons[offset].mlet + SYM_OFF_M];
        pet_color(offset);
    } else { /* a monster */
        *ch = showsyms[(int) mons[g].mlet + SYM_OFF_M];
        mon_color(g);
    }
    // end of wintty code
}
#endif

/* map nethack color to RGB */
COLORREF
nhcolor_to_RGB(int c)
{
    switch (c) {
    case CLR_BLACK:
        return RGB(0x55, 0x55, 0x55);
    case CLR_RED:
        return RGB(0xFF, 0x00, 0x00);
    case CLR_GREEN:
        return RGB(0x00, 0x80, 0x00);
    case CLR_BROWN:
        return RGB(0xA5, 0x2A, 0x2A);
    case CLR_BLUE:
        return RGB(0x00, 0x00, 0xFF);
    case CLR_MAGENTA:
        return RGB(0xFF, 0x00, 0xFF);
    case CLR_CYAN:
        return RGB(0x00, 0xFF, 0xFF);
    case CLR_GRAY:
        return RGB(0xC0, 0xC0, 0xC0);
    case NO_COLOR:
        return RGB(0xFF, 0xFF, 0xFF);
    case CLR_ORANGE:
        return RGB(0xFF, 0xA5, 0x00);
    case CLR_BRIGHT_GREEN:
        return RGB(0x00, 0xFF, 0x00);
    case CLR_YELLOW:
        return RGB(0xFF, 0xFF, 0x00);
    case CLR_BRIGHT_BLUE:
        return RGB(0x00, 0xC0, 0xFF);
    case CLR_BRIGHT_MAGENTA:
        return RGB(0xFF, 0x80, 0xFF);
    case CLR_BRIGHT_CYAN:
        return RGB(0x80, 0xFF, 0xFF); /* something close to aquamarine */
    case CLR_WHITE:
        return RGB(0xFF, 0xFF, 0xFF);
    default:
        return RGB(0x00, 0x00, 0x00); /* black */
    }
}

/* apply bitmap pointed by sourceDc transparently over
   bitmap pointed by hDC */
void
nhapply_image_transparent(HDC hDC, int x, int y, int width, int height,
                          HDC sourceDC, int s_x, int s_y, int s_width,
                          int s_height, COLORREF cTransparent)
{
    TransparentImage(hDC, x, y, width, height, sourceDC, s_x, s_y, s_width,
                     s_height, cTransparent);
}
