/* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
/* NetHack 5.0 curswins.c */
/* Copyright (c) Karl Garrison, 2010. */
/* NetHack may be freely redistributed.  See license for details. */

#if defined(CURSES_UNICODE) && !defined(_XOPEN_SOURCE_EXTENDED)
#define _XOPEN_SOURCE_EXTENDED 1
#endif
#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "cursinit.h"
#include "cursmisc.h"
#include "curswins.h"
#include "cursstat.h"

/* Window handling for curses interface */

/* Private declarations */

typedef struct nhw {
    winid nhwin;                /* NetHack window id */
    WINDOW *curwin;             /* Curses window pointer */
    int width;                  /* Usable width not counting border */
    int height;                 /* Usable height not counting border */
    int x;                      /* start of window on terminal (left) */
    int y;                      /* start of window on terminal (top) */
    int orientation;            /* Placement of window relative to map */
    boolean clr_inited;         /* fg/bg/colorpair inited? */
    int fg, bg;                 /* foreground, background color index */
    int colorpair;              /* color pair of fg, bg */
    boolean border;             /* Whether window has a visible border */
} nethack_window;

typedef struct nhwd {
    winid nhwid;                /* NetHack window id */
    struct nhwd *prev_wid;      /* Pointer to previous entry */
    struct nhwd *next_wid;      /* Pointer to next entry */
} nethack_wid;

typedef struct nhchar {
    int ch;                     /* character */
    int color;                  /* color info for character */
    int framecolor;                /* background color info for character */
    int attr;                   /* attributes of character */
    struct unicode_representation *unicode_representation;
} nethack_char;

static boolean map_clipped;     /* Map window smaller than 80x21 */
static nethack_window nhwins[NHWIN_MAX];        /* NetHack window array */
static nethack_char map[ROWNO][COLNO];  /* Map window contents */
static nethack_wid *nhwids = NULL;      /* NetHack wid array */

static boolean is_main_window(winid wid);
static void write_char(WINDOW * win, int x, int y, nethack_char ch);
static void clear_map(void);

/* Create a window with the specified size and orientation */

WINDOW *
curses_create_window(int wid, int width, int height, orient orientation)
{
    int mapx = 0, mapy = 0, maph = 0, mapw = 0;
    int startx = 0;
    int starty = 0;
    WINDOW *win;
    boolean map_border = FALSE;
    int mapb_offset = 0;

    if ((orientation == UP) || (orientation == DOWN) ||
        (orientation == LEFT) || (orientation == RIGHT)) {
        if (svm.moves > 0) {
            map_border = curses_window_has_border(MAP_WIN);
            curses_get_window_xy(MAP_WIN, &mapx, &mapy);
            curses_get_window_size(MAP_WIN, &maph, &mapw);
        } else {
            map_border = TRUE;
            mapx = 0;
            mapy = 0;
            maph = term_rows;
            mapw = term_cols;
        }
    }

    if (map_border) {
        mapb_offset = 1;
    }

    width += 2;                 /* leave room for bounding box */
    height += 2;

    if ((width > term_cols) || (height > term_rows)) {
        impossible(
                "curses_create_window: Terminal too small for dialog window");
        width = term_cols;
        height = term_rows;
    }
    switch (orientation) {
    default:
        impossible("curses_create_window: Bad orientation");
        FALLTHROUGH;
        /*FALLTHRU*/
    case CENTER:
        startx = (term_cols / 2) - (width / 2);
        starty = (term_rows / 2) - (height / 2);
        break;
    case UP:
        if (svm.moves > 0) {
            startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
        } else {
            startx = 0;
        }

        starty = mapy + mapb_offset;
        break;
    case DOWN:
        if (svm.moves > 0) {
            startx = (mapw / 2) - (width / 2) + mapx + mapb_offset;
        } else {
            startx = 0;
        }

        starty = height - mapy - 1 - mapb_offset;
        break;
    case LEFT:
        if (map_border && (width < term_cols))
            startx = 1;
        else
            startx = 0;
        starty = term_rows - height;
        break;
    case RIGHT:
        if (svm.moves > 0) {
            startx = (mapw + mapx + (mapb_offset * 2)) - width;
        } else {
            startx = term_cols - width;
        }

        starty = 0;
        break;
    }

    if (startx < 0) {
        startx = 0;
    }

    if (starty < 0) {
        starty = 0;
    }

    win = newwin(height, width, starty, startx);

    if (curses_is_text(wid))
        wid = TEXT_WIN;
    else if (curses_is_menu(wid))
        wid = MENU_WIN;

    if (nhwins[wid].clr_inited < 1)
        curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
    box(win, 0, 0);
    if (nhwins[wid].clr_inited < 1)
        curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
    return win;
}

int
curses_win_clr_inited(int wid)
{
    if (curses_is_text(wid)) {
        wid = TEXT_WIN;
    } else if (curses_is_menu(wid)) {
        wid = MENU_WIN;
    }
    return nhwins[wid].clr_inited;
}

void
curses_set_wid_colors(int wid, WINDOW *win)
{
    if (wid == TEXT_WIN || curses_is_text(wid)) {
        wid = TEXT_WIN;
        if (!nhwins[wid].clr_inited)
            curses_parse_wid_colors(wid, iflags.wcolors[wcolor_text].fg,
                                    iflags.wcolors[wcolor_text].bg);
    } else if (wid == MENU_WIN || curses_is_menu(wid)) {
        wid = MENU_WIN;
        if (!nhwins[wid].clr_inited)
            curses_parse_wid_colors(wid, iflags.wcolors[wcolor_menu].fg,
                                    iflags.wcolors[wcolor_menu].bg);
    }
    /* FIXME: colors and nhwins[] entry for perm invent window */
    if (nhwins[wid].clr_inited > 0) {
        wbkgd(win ? win : nhwins[wid].curwin,
              COLOR_PAIR(nhwins[wid].colorpair));
    }
}

/* Erase and delete curses window, and refresh standard windows */

void
curses_destroy_win(WINDOW *win)
{
    int mapwidth = 0, winwidth, dummyht;

    /*
     * In case map is narrower than the space alloted for it, if we
     * are destroying a popup window and it is wider than the map,
     * erase the popup first.  It probably has overwritten some of
     * the next-to-map empty space.  If we don't clear that now, the
     * base window will remember it and redisplay it during refreshes.
     *
     * Note: since we almost never destroy non-popups, we don't really
     * need to determine whether 'win' is one.  Overhead for unnecessary
     * erasure is negligible.
     */
    getmaxyx(win, dummyht, winwidth); /* macro, assigns to its args */
    if (mapwin)
        getmaxyx(mapwin, dummyht, mapwidth);
    if (winwidth > mapwidth) {
        werase(win);
        wnoutrefresh(win);
    }

    delwin(win);
    if (win == activemenu)
        activemenu = NULL;
    /* during shutdown, RIP window could still be active after mapwin goes
       away; so, avoid 'if (mapwin)' above when deleting RIP window later */
    if (win == mapwin)
        win = mapwin = NULL;
    curses_refresh_nethack_windows();
    nhUse(dummyht);
}


/* Refresh nethack windows if they exist, or base window if not */

void
curses_refresh_nethack_windows(void)
{
    WINDOW *status_window, *message_window, *map_window, *inv_window;

    status_window = curses_get_nhwin(STATUS_WIN);
    message_window = curses_get_nhwin(MESSAGE_WIN);
    map_window = curses_get_nhwin(MAP_WIN);
    inv_window = curses_get_nhwin(INV_WIN);

    if (!iflags.window_inited) {
        return;
    }

    if (svm.moves == 0) {
        /* Main windows not yet displayed; refresh base window instead */
        touchwin(stdscr);
        refresh();
    } else {
        if (status_window != NULL) {
            curses_set_wid_colors(STATUS_WIN, NULL);
            touchwin(status_window);
            wnoutrefresh(status_window);
        }
        if (map_window != NULL) {
            touchwin(map_window);
            wnoutrefresh(map_window);
        }
        if (message_window != NULL) {
            curses_set_wid_colors(MESSAGE_WIN, NULL);
            touchwin(message_window);
            wnoutrefresh(message_window);
        }
        if (inv_window) {
            touchwin(inv_window);
            wnoutrefresh(inv_window);
        }
        doupdate();
    }
}


/* Return curses window pointer for given NetHack winid */

WINDOW *
curses_get_nhwin(winid wid)
{
    if (!is_main_window(wid)) {
        impossible("curses_get_nhwin: wid %d out of range. Not a main window.",
                   wid);
        return NULL;
    }

    return nhwins[wid].curwin;
}

boolean
parse_hexstr(char *colorbuf, int *red, int *green, int *blue)
{
    int len = colorbuf ? strlen(colorbuf) : 0;

    if (len == 7 && colorbuf[0] == '#') {
        char tmpbuf[16];

        Sprintf(tmpbuf, "0x%c%c", colorbuf[1], colorbuf[2]);
        *red = strtol(tmpbuf, NULL, 0);
        Sprintf(tmpbuf, "0x%c%c", colorbuf[3], colorbuf[4]);
        *green = strtol(tmpbuf, NULL, 0);
        Sprintf(tmpbuf, "0x%c%c", colorbuf[5], colorbuf[6]);
        *blue = strtol(tmpbuf, NULL, 0);
        return TRUE;
    }
    return FALSE;
}

void
curses_parse_wid_colors(int wid, char *fg, char *bg)
{

    if (curses_is_text(wid)) {
        wid = TEXT_WIN;
    } else if (curses_is_menu(wid)) {
        wid = MENU_WIN;
    }

    if (nhwins[wid].clr_inited)
        return;

    int nh_fg = fg ? match_str2clr(fg, TRUE) : CLR_MAX;
    int nh_bg = bg ? match_str2clr(bg, TRUE) : CLR_MAX;
    int r, g, b;

    if (nh_fg == CLR_MAX) {
        if (fg && parse_hexstr(fg, &r, &g, &b)) {
            nh_fg = curses_init_rgb(r, g, b);
        } else {
            nh_fg = -1;
        }
    }
    if (nh_bg == CLR_MAX) {
        if (bg && parse_hexstr(bg, &r, &g, &b)) {
            nh_bg = curses_init_rgb(r, g, b);
        } else {
            nh_bg = -1;
        }
    }

    nhwins[wid].fg = nh_fg;
    nhwins[wid].bg = nh_bg;
    if (nh_fg == -1 || nh_bg == -1) {
        nhwins[wid].clr_inited = -1;
    } else {
        nhwins[wid].colorpair = curses_init_pair(nh_fg, nh_bg);
        nhwins[wid].clr_inited = 1;
    }
}


/* Add curses window pointer and window info to list for given NetHack winid */

void
curses_add_nhwin(
    winid wid, int height, int width, int y, int x,
    orient orientation, boolean border)
{
    WINDOW *win;
    int real_width = width;
    int real_height = height;

    if (!is_main_window(wid)) {
        impossible("curses_add_nhwin: wid %d out of range. Not a main window.",
                   wid);
        return;
    }

    nhwins[wid].nhwin = wid;
    nhwins[wid].border = border;
    nhwins[wid].width = width;
    nhwins[wid].height = height;
    nhwins[wid].x = x;
    nhwins[wid].y = y;
    nhwins[wid].orientation = orientation;
    nhwins[wid].fg = nhwins[wid].bg = 0;
    nhwins[wid].colorpair = -1;
    nhwins[wid].clr_inited = 0;

    if (border) {
        real_width += 2;        /* leave room for bounding box */
        real_height += 2;
    }

    win = newwin(real_height, real_width, y, x);
    nhwins[wid].curwin = win;

    switch (wid) {
    case MESSAGE_WIN:
        messagewin = win;
        curses_parse_wid_colors(wid, iflags.wcolors[wcolor_message].fg,
                                iflags.wcolors[wcolor_message].bg);
        curses_set_wid_colors(wid, NULL);
        break;
    case STATUS_WIN:
        statuswin = win;
        curses_parse_wid_colors(wid, iflags.wcolors[wcolor_status].fg,
                                iflags.wcolors[wcolor_status].bg);
        curses_set_wid_colors(wid, NULL);
        break;
    case MAP_WIN:
        mapwin = win;
        map_clipped = (width < COLNO || height < ROWNO);
        break;
    }

    if (border) {
        box(win, 0, 0);
    }

}


/* Add wid to list of known window IDs */

void
curses_add_wid(winid wid)
{
    nethack_wid *new_wid;
    nethack_wid *widptr = nhwids;

    new_wid = (nethack_wid *) alloc((unsigned) sizeof (nethack_wid));
    new_wid->nhwid = wid;

    new_wid->next_wid = NULL;

    if (widptr == NULL) {
        new_wid->prev_wid = NULL;
        nhwids = new_wid;
    } else {
        while (widptr->next_wid != NULL) {
            widptr = widptr->next_wid;
        }
        new_wid->prev_wid = widptr;
        widptr->next_wid = new_wid;
    }
}


/* refresh a curses window via given nethack winid */

void
curses_refresh_nhwin(winid wid)
{
    curses_set_wid_colors(wid, NULL);
    wnoutrefresh(curses_get_nhwin(wid));
    doupdate();
}


/* Delete curses window via given NetHack winid and remove entry from list */

void
curses_del_nhwin(winid wid)
{
    if (curses_is_menu(wid) || curses_is_text(wid)) {
        curses_del_menu(wid, TRUE);
        return;
    } else if (wid == INV_WIN) {
        curses_del_menu(wid, TRUE);
        /* don't return yet */
    }

    if (!is_main_window(wid)) {
        impossible("curses_del_nhwin: wid %d out of range. Not a main window.",
                   wid);
        return;
    }
    if (nhwins[wid].curwin != NULL) {
        delwin(nhwins[wid].curwin);
        nhwins[wid].curwin = NULL;
    }
    nhwins[wid].nhwin = -1;
}


/* Delete wid from list of known window IDs */

void
curses_del_wid(winid wid)
{
    nethack_wid *tmpwid;
    nethack_wid *widptr;

    if (curses_is_menu(wid) || curses_is_text(wid)) {
        curses_del_menu(wid, FALSE);
    }

    for (widptr = nhwids; widptr; widptr = widptr->next_wid) {
        if (widptr->nhwid == wid) {
            if ((tmpwid = widptr->prev_wid) != NULL) {
                tmpwid->next_wid = widptr->next_wid;
            } else {
                nhwids = widptr->next_wid;      /* New head mode, or NULL */
            }
            if ((tmpwid = widptr->next_wid) != NULL) {
                tmpwid->prev_wid = widptr->prev_wid;
            }
            free(widptr);
            break;
        }
    }
}

/* called by destroy_nhwindows() prior to exit */
void
curs_destroy_all_wins(void)
{
    curses_count_window((char *) 0); /* clean up orphan */

#if 0   /* this works but confuses the static analyzer (from llvm-19,
         * which reports "warning: Use of memory after it is freed") */
    while (nhwids)
        curses_del_wid(nhwids->nhwid);
#else
    while (nhwids) {
        nethack_wid *tmpptr = nhwids;

        curses_del_wid(tmpptr->nhwid);
    }
#endif
}

/* Print a single character in the given window at the given coordinates */

void
curses_putch(winid wid, int x, int y, int ch,
#ifdef ENHANCED_SYMBOLS
             struct unicode_representation *unicode_representation,
#endif
             int color, int framecolor, int attr)
{
    static boolean map_initted = FALSE;
    int sx, sy, ex, ey;
    boolean border = curses_window_has_border(wid);
    nethack_char nch;
/*
    if (wid == STATUS_WIN) {
        curses_update_stats();
    }
*/
    if (wid != MAP_WIN) {
        return;
    }

    if (!map_initted) {
        clear_map();
        map_initted = TRUE;
    }

    --x; /* map column [0] is not used; draw column [1] in first screen col */
    map[y][x].ch = ch;
    map[y][x].color = color;
    map[y][x].framecolor = framecolor;
    map[y][x].attr = attr;
#ifdef ENHANCED_SYMBOLS
    map[y][x].unicode_representation = unicode_representation;
#endif
    nch = map[y][x];

    (void) curses_map_borders(&sx, &sy, &ex, &ey, -1, -1);

    if ((x >= sx) && (x <= ex) && (y >= sy) && (y <= ey)) {
        if (border) {
            x++;
            y++;
        }

        write_char(mapwin, x - sx, y - sy, nch);
    }
    /* refresh after every character?
     * Fair go, mate! Some of us are playing from Australia! */
    /* wrefresh(mapwin); */
}


/* Get x, y coordinates of curses window on the physical terminal window */

void
curses_get_window_xy(winid wid, int *x, int *y)
{
    if (!is_main_window(wid)) {
        impossible(
              "curses_get_window_xy: wid %d out of range. Not a main window.",
                   wid);
        *x = 0;
        *y = 0;
        return;
    }

    *x = nhwins[wid].x;
    *y = nhwins[wid].y;
}


/* Get usable width and height curses window on the physical terminal window */

void
curses_get_window_size(winid wid, int *height, int *width)
{
    *height = nhwins[wid].height;
    *width = nhwins[wid].width;
}


/* Determine if given window has a visible border */

boolean
curses_window_has_border(winid wid)
{
    if (curses_is_menu(wid))
        wid = MENU_WIN;
    else if (curses_is_text(wid))
        wid = TEXT_WIN;
    return nhwins[wid].border;
}


/* Determine if window for given winid exists */

boolean
curses_window_exists(winid wid)
{
    nethack_wid *widptr;

    for (widptr = nhwids; widptr; widptr = widptr->next_wid)
        if (widptr->nhwid == wid)
            return TRUE;

    return FALSE;
}


/* Return the orientation of the specified window */

int
curses_get_window_orientation(winid wid)
{
    if (!is_main_window(wid)) {
        impossible(
     "curses_get_window_orientation: wid %d out of range. Not a main window.",
                   wid);
        return CENTER;
    }

    return nhwins[wid].orientation;
}


/* Output a line of text to specified NetHack window with given coordinates
   and text attributes */

void
curses_puts(winid wid, int attr, const char *text)
{
    anything Id;
    WINDOW *win = NULL;

    if (is_main_window(wid)) {
        win = curses_get_nhwin(wid);
    }

    if (wid == MESSAGE_WIN) {
        /* if a no-history message is being shown, remove it */
        if (counting)
            curses_count_window((char *) 0);

        curses_message_win_puts(text, FALSE);
        return;
    }

#if 0
    if (wid == STATUS_WIN) {
        curses_update_stats();     /* We will do the write ourselves */
        return;
    }
#endif

    if (curses_is_menu(wid) || curses_is_text(wid)) {
        if (!curses_menu_exists(wid)) {
            impossible(
                     "curses_puts: Attempted write to nonexistent window %d!",
                       wid);
            return;
        }
        Id = cg.zeroany;
        curses_add_nhmenu_item(wid, &nul_glyphinfo, &Id, 0, 0,
                               attr, NO_COLOR, text, MENU_ITEMFLAGS_NONE);
    } else {
        curses_set_wid_colors(wid, NULL);
        waddstr(win, text);
        wnoutrefresh(win);
    }
}


/* Clear the contents of a window via the given NetHack winid */

void
curses_clear_nhwin(winid wid)
{
    WINDOW *win = curses_get_nhwin(wid);
    boolean border = curses_window_has_border(wid);

    if (wid == MAP_WIN) {
        clearok(win, TRUE);     /* Redraw entire screen when refreshed */
        clear_map();
    }

    curses_set_wid_colors(wid, NULL);
    werase(win);

    if (border) {
        box(win, 0, 0);
    }
}

/* Change colour of window border to alert player to something */
void
curses_alert_win_border(winid wid, boolean onoff)
{
    WINDOW *win = curses_get_nhwin(wid);

    if (!win || !curses_window_has_border(wid))
        return;
    curses_set_wid_colors(wid, NULL);
    if (onoff)
        curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, ON);
    box(win, 0, 0);
    if (onoff)
        curses_toggle_color_attr(win, ALERT_BORDER_COLOR, NONE, OFF);
    wnoutrefresh(win);
}


void
curses_alert_main_borders(boolean onoff)
{
    curses_alert_win_border(MAP_WIN, onoff);
    curses_alert_win_border(MESSAGE_WIN, onoff);
    curses_alert_win_border(STATUS_WIN, onoff);
    curses_alert_win_border(INV_WIN, onoff);
}

/* Return true if given wid is a main NetHack window */

static boolean
is_main_window(winid wid)
{
    if (wid == MESSAGE_WIN || wid == MAP_WIN
        || wid == STATUS_WIN || wid == INV_WIN)
        return TRUE;

    return FALSE;
}


/* Unconditionally write a single character to a window at the given
coordinates without a refresh.  Currently only used for the map. */

/* convert nhcolor (fg) and framecolor (bg) to curses colorpair */
int
get_framecolor(int nhcolor, int framecolor)
{
    /* curses_toggle_color_attr() adds the +1 and takes care of COLORS < 16 */
    return (16 * (framecolor % 8)) + (nhcolor % 16);
}

static void
write_char(WINDOW * win, int x, int y, nethack_char nch)
{
    int curscolor = nch.color, cursattr = nch.attr;

    if (nch.framecolor != NO_COLOR) {
        curscolor = get_framecolor(nch.color, nch.framecolor);
        if (nch.attr == A_REVERSE)
            cursattr = A_NORMAL; /* hilited pet looks odd otherwise */
    }
    curses_toggle_color_attr(win, curscolor, cursattr, ON);
#if defined(CURSES_UNICODE) && defined(ENHANCED_SYMBOLS)
    if ((nch.unicode_representation && nch.unicode_representation->utf8str)
        || SYMHANDLING(H_IBM)) {
        /* CP437 to Unicode mapping according to the Unicode Consortium */
        static const uint16 cp437[256] = {
            0x0020, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
            0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
            0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
            0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
            0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
            0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
            0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
            0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
            0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
            0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
            0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
            0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
            0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
            0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
            0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
            0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
            0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
            0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
            0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
            0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
            0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
            0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
            0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
            0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
            0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
            0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
            0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
            0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
            0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
            0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
            0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
            0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
        };
        attr_t attr;
        short pair;
        wchar_t wch[3];
        uint32 utf32ch;
        cchar_t cch;

        if (SYMHANDLING(H_UTF8)) {
            utf32ch = nch.unicode_representation->utf32ch;
        } else if (SYMHANDLING(H_IBM)) {
            utf32ch = cp437[(uint8)nch.ch];
        } else {
            utf32ch = (uint8)nch.ch;
        }
        if (sizeof(wchar_t) == 2 && utf32ch >= 0x10000) {
            /* UTF-16 surrogate pair */
            wch[0] = (wchar_t)((utf32ch >> 10) + 0xD7C0);
            wch[1] = (wchar_t)((utf32ch & 0x3FF) + 0xDC00);
            wch[2] = L'\0';
        } else {
            wch[0] = (wchar_t)utf32ch;
            wch[1] = L'\0';
        }
        wmove(win, y, x);
        wattr_get(win, &attr, &pair, NULL);
        setcchar(&cch, wch, attr, pair, NULL);
        mvwadd_wch(win, y, x, &cch);
    } else
#endif
#ifdef PDCURSES
        mvwaddrawch(win, y, x, nch.ch);
#else
        mvwaddch(win, y, x, nch.ch);
#endif
    curses_toggle_color_attr(win, curscolor, cursattr, OFF);
}


/* Draw the entire visible map onto the screen given the visible map
boundaries */

void
curses_draw_map(int sx, int sy, int ex, int ey)
{
    int curx, cury;
    int bspace = 0;

#ifdef MAP_SCROLLBARS
    int sbsx, sbsy, sbex, sbey, count;
    nethack_char hsb_back, hsb_bar, vsb_back, vsb_bar;
#endif

    if (curses_window_has_border(MAP_WIN)) {
        bspace++;
    }
#ifdef MAP_SCROLLBARS
    hsb_back.ch = '-';
    hsb_back.color = SCROLLBAR_BACK_COLOR;
    hsb_back.framecolor = NO_COLOR;
    hsb_back.attr = A_NORMAL;
    hsb_back.unicode_representation = NULL;
    hsb_bar.ch = '*';
    hsb_bar.color = SCROLLBAR_COLOR;
    hsb_bar.framecolor = NO_COLOR;
    hsb_bar.attr = A_NORMAL;
    hsb_bar.unicode_representation = NULL;
    vsb_back.ch = '|';
    vsb_back.color = SCROLLBAR_BACK_COLOR;
    vsb_back.framecolor = NO_COLOR;
    vsb_back.attr = A_NORMAL;
    vsb_back.unicode_representation = NULL;
    vsb_bar.ch = '*';
    vsb_bar.color = SCROLLBAR_COLOR;
    vsb_bar.framecolor = NO_COLOR;
    vsb_bar.attr = A_NORMAL;
    vsb_bar.unicode_representation = NULL;

    /* Horizontal scrollbar */
    if (sx > 0 || ex < (COLNO - 1)) {
         sbsx = (int) (((long) sx * (long) (ex - sx + 1)) / (long) COLNO);
         sbex = (int) (((long) ex * (long) (ex - sx + 1)) / (long) COLNO);

        if (sx > 0 && sbsx == 0)
            ++sbsx;
        if (ex < COLNO - 1 && sbex == COLNO - 1)
            --sbex;

        for (count = 0; count < sbsx; count++) {
            write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
        }
        for (count = sbsx; count <= sbex; count++) {
            write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_bar);
        }
        for (count = sbex + 1; count <= (ex - sx); count++) {
            write_char(mapwin, count + bspace, ey - sy + 1 + bspace, hsb_back);
        }
    }

    /* Vertical scrollbar */
    if (sy > 0 || ey < (ROWNO - 1)) {
        sbsy = (int) (((long) sy * (long) (ey - sy + 1)) / (long) ROWNO);
        sbey = (int) (((long) ey * (long) (ey - sy + 1)) / (long) ROWNO);

        if (sy > 0 && sbsy == 0)
            ++sbsy;
        if (ey < ROWNO - 1 && sbey == ROWNO - 1)
            --sbey;

        for (count = 0; count < sbsy; count++) {
            write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
        }
        for (count = sbsy; count <= sbey; count++) {
            write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_bar);
        }
        for (count = sbey + 1; count <= (ey - sy); count++) {
            write_char(mapwin, ex - sx + 1 + bspace, count + bspace, vsb_back);
        }
    }
#endif /* MAP_SCROLLBARS */

    for (curx = sx; curx <= ex; curx++) {
        for (cury = sy; cury <= ey; cury++) {
            write_char(mapwin, curx - sx + bspace, cury - sy + bspace,
                       map[cury][curx]);
        }
    }
}


/* Init map array to blanks */

static void
clear_map(void)
{
    int x, y;

    for (x = 0; x < COLNO; x++) {
        for (y = 0; y < ROWNO; y++) {
            map[y][x].ch = ' ';
            map[y][x].color = NO_COLOR;
            map[y][x].attr = A_NORMAL;
            map[y][x].unicode_representation = NULL;
        }
    }
}


/* Determine visible boundaries of map, and determine if it needs to be
based on the location of the player. */

boolean
curses_map_borders(int *sx, int *sy, int *ex, int *ey, int ux, int uy)
{
    static int width = 0;
    static int height = 0;
    static int osx = 0;
    static int osy = 0;
    static int oex = 0;
    static int oey = 0;
    static int oux = -1;
    static int ouy = -1;

    if ((oux == -1) || (ouy == -1)) {
        oux = u.ux;
        ouy = u.uy;
    }

    if (ux == -1) {
        ux = oux;
    } else {
        oux = ux;
    }

    if (uy == -1) {
        uy = ouy;
    } else {
        ouy = uy;
    }

    curses_get_window_size(MAP_WIN, &height, &width);

#ifdef MAP_SCROLLBARS
    if (width < COLNO) {
        height--;               /* room for horizontal scrollbar */
    }

    if (height < ROWNO) {
        width--;                /* room for vertical scrollbar */

        if (width == COLNO) {
            height--;
        }
    }
#endif /* MAP_SCROLLBARS */

    if (width >= COLNO) {
        *sx = 0;
        *ex = COLNO - 1;
    } else {
        *ex = (width / 2) + ux;
        *sx = *ex - (width - 1);

        if (*ex >= COLNO) {
            *sx = COLNO - width;
            *ex = COLNO - 1;
        } else if (*sx < 0) {
            *sx = 0;
            *ex = width - 1;
        }
    }

    if (height >= ROWNO) {
        *sy = 0;
        *ey = ROWNO - 1;
    } else {
        *ey = (height / 2) + uy;
        *sy = *ey - (height - 1);

        if (*ey >= ROWNO) {
            *sy = ROWNO - height;
            *ey = ROWNO - 1;
        } else if (*sy < 0) {
            *sy = 0;
            *ey = height - 1;
        }
    }

    if ((*sx != osx) || (*sy != osy) || (*ex != oex) || (*ey != oey) ||
        map_clipped) {
        osx = *sx;
        osy = *sy;
        oex = *ex;
        oey = *ey;
        return TRUE;
    }

    return FALSE;
}
