/* 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 3.6 cursdial.c */
/* Copyright (c) Karl Garrison, 2010. */
/* NetHack may be freely redistributed.  See license for details. */

#include "curses.h"
#include "hack.h"
#include "wincurs.h"
#include "cursdial.h"
#include "func_tab.h"
#include <ctype.h>

#if defined(FILENAME_CMP) && !defined(strcasecmp)
#define strcasecmp FILENAME_CMP
#endif
#if defined(STRNCMPI) && !defined(strncasecmp)
#define strncasecmp strncmpi
#endif

/*
 * Note:
 *
 *  Prototypes need to use the widened/unwidened type macros (CHAR_P, &c)
 *  in order to match fields of the window_procs struct (see winprocs.h).
 *  But for a standard-conforming compiler, we'll end up with the widened
 *  types necessary to match the mixed prototype/old-style function
 *  definition environment as used by nethack's core.  Prototype
int func(CHAR_P);
 *  becomes
int func(int);
 *  after expansion, which matches the definition
int func(arg) char arg; { ... }
 *  according to the rules of the C standard.  But the use of new-style
 *  definitions
int func(char arg) { ... }
 *  by the curses interface turns that into a conflict.  No widening takes
 *  place so it ought to be 'int func(char);' instead.  Unfortunately that
 *  would be incompatible for functions assigned to window_procs.
 *
 *  So, the code here (also cursmain.c and cursinvt.c) is mis-using the
 *  widening macros for variable types
int func(CHAR_P arg) { ... }
 *  (no doubt modelling it after the C++ code in win/Qt where the option
 *  to switch the applicable definitions to old-style isn't available).
 *  Other alternatives aren't significantly better so just live with it.
 *  [Redoing the windowing interface to avoid narrow arguments would be
 *  better since that would fix Qt's usage too.]
 */

/* Dialog windows for curses interface */


/* Private declarations */

typedef struct nhmi {
    winid wid;                  /* NetHack window id */
    int glyph;                  /* Menu glyphs */
    anything identifier;        /* Value returned if item selected */
    CHAR_P accelerator;         /* Character used to select item from menu */
    CHAR_P group_accel;         /* Group accelerator for menu item, if any */
    int attr;                   /* Text attributes for item */
    const char *str;            /* Text of menu item */
    BOOLEAN_P presel;           /* Whether menu item should be preselected */
    boolean selected;           /* Whether item is currently selected */
    int page_num;               /* Display page number for entry */
    int line_num;               /* Line number on page where entry begins */
    int num_lines;              /* Number of lines entry uses on page */
    int count;                  /* Count for selected item */
    struct nhmi *prev_item;     /* Pointer to previous entry */
    struct nhmi *next_item;     /* Pointer to next entry */
} nhmenu_item;

typedef struct nhm {
    winid wid;                  /* NetHack window id */
    const char *prompt;         /* Menu prompt text */
    nhmenu_item *entries;       /* Menu entries */
    int num_entries;            /* Number of menu entries */
    int num_pages;              /* Number of display pages for entry */
    int height;                 /* Window height of menu */
    int width;                  /* Window width of menu */
    boolean reuse_accels;       /* Non-unique accelerators per page */
    struct nhm *prev_menu;      /* Pointer to previous entry */
    struct nhm *next_menu;      /* Pointer to next entry */
} nhmenu;

typedef enum menu_op_type {
    SELECT,
    DESELECT,
    INVERT
} menu_op;

static nhmenu *get_menu(winid wid);
static char menu_get_accel(boolean first);
static void menu_determine_pages(nhmenu *menu);
static boolean menu_is_multipage(nhmenu *menu, int width, int height);
static void menu_win_size(nhmenu *menu);
static void menu_display_page(nhmenu *menu, WINDOW * win, int page_num);
static int menu_get_selections(WINDOW * win, nhmenu *menu, int how);
static void menu_select_deselect(WINDOW * win, nhmenu_item *item,
                                 menu_op operation);
static int menu_operation(WINDOW * win, nhmenu *menu, menu_op operation,
                          int page_num);
static void menu_clear_selections(nhmenu *menu);
static int menu_max_height(void);

static nhmenu *nhmenus = NULL;  /* NetHack menu array */


/* Get a line of text from the player, such as asking for a character name
   or a wish */

void
curses_line_input_dialog(const char *prompt, char *answer, int buffer)
{
    int map_height, map_width, maxwidth, remaining_buf, winx, winy, count;
    WINDOW *askwin, *bwin;
    char *tmpstr;
    int prompt_width = strlen(prompt) + buffer + 1;
    int prompt_height = 1;
    int height = prompt_height;
    char input[BUFSZ];

    if (buffer >= (int) sizeof input)
         buffer = (int) sizeof input - 1;
    maxwidth = term_cols - 2;

    if (iflags.window_inited) {
        if (!iflags.wc_popup_dialog) {
            curses_message_win_getline(prompt, answer, buffer);
            return;
        }
        curses_get_window_size(MAP_WIN, &map_height, &map_width);
        if ((prompt_width + 2) > map_width)
            maxwidth = map_width - 2;
    }

    if (prompt_width > maxwidth) {
        prompt_height = curses_num_lines(prompt, maxwidth);
        height = prompt_height;
        prompt_width = maxwidth;
        tmpstr = curses_break_str(prompt, maxwidth, prompt_height);
        remaining_buf = buffer - (strlen(tmpstr) - 1);
        if (remaining_buf > 0) {
            height += (remaining_buf / prompt_width);
            if ((remaining_buf % prompt_width) > 0) {
                height++;
            }
        }
        free(tmpstr);
    }

    if (iflags.window_inited) {
        bwin = curses_create_window(prompt_width, height, UP);
        wrefresh(bwin);
        getbegyx(bwin, winy, winx);
        askwin = newwin(height, prompt_width, winy + 1, winx + 1);
    } else {
        bwin = curses_create_window(prompt_width, height, CENTER);
        wrefresh(bwin);
        getbegyx(bwin, winy, winx);
        askwin = newwin(height, prompt_width, winy + 1, winx + 1);
    }
    for (count = 0; count < prompt_height; count++) {
        tmpstr = curses_break_str(prompt, maxwidth, count + 1);
        if (count == (prompt_height - 1)) { /* Last line */
            mvwprintw(askwin, count, 0, "%s ", tmpstr);
        } else {
            mvwaddstr(askwin, count, 0, tmpstr);
        }
        free(tmpstr);
    }

    echo();
    curs_set(1);
    wgetnstr(askwin, input, buffer - 1);
    curs_set(0);
    strcpy(answer, input);
    werase(bwin);
    delwin(bwin);
    curses_destroy_win(askwin);
    noecho();
}


/* Get a single character response from the player, such as a y/n prompt */

int
curses_character_input_dialog(const char *prompt, const char *choices,
                              CHAR_P def)
{
    WINDOW *askwin = NULL;
#ifdef PDCURSES
    WINDOW *message_window;
#endif
    int answer, count, maxwidth, map_height, map_width;
    char *linestr;
    char askstr[BUFSZ + QBUFSZ];
    char choicestr[QBUFSZ];
    int prompt_width = strlen(prompt);
    int prompt_height = 1;
    boolean any_choice = FALSE;
    boolean accept_count = FALSE;

    if (g.invent || (g.moves > 1)) {
        curses_get_window_size(MAP_WIN, &map_height, &map_width);
    } else {
        map_height = term_rows;
        map_width = term_cols;
    }

#ifdef PDCURSES
    message_window = curses_get_nhwin(MESSAGE_WIN);
#endif
    maxwidth = map_width - 2;

    if (choices != NULL) {
        for (count = 0; choices[count] != '\0'; count++) {
            if (choices[count] == '#') { /* Accept a count */
                accept_count = TRUE;
            }
        }
        choicestr[0] = ' ';
        choicestr[1] = '[';
        for (count = 0; choices[count] != '\0'; count++) {
            if (choices[count] == '\033') { /* Escape */
                break;
            }
            choicestr[count + 2] = choices[count];
        }
        choicestr[count + 2] = ']';
        if (((def >= 'A') && (def <= 'Z')) || ((def >= 'a') && (def <= 'z'))) {
            choicestr[count + 3] = ' ';
            choicestr[count + 4] = '(';
            choicestr[count + 5] = def;
            choicestr[count + 6] = ')';
            choicestr[count + 7] = '\0';
        } else {                /* No usable default choice */

            choicestr[count + 3] = '\0';
            def = '\0';         /* Mark as no default */
        }
        strcpy(askstr, prompt);
        strcat(askstr, choicestr);
    } else {
        strcpy(askstr, prompt);
        any_choice = TRUE;
    }

    prompt_width = strlen(askstr);

    if ((prompt_width + 2) > maxwidth) {
        prompt_height = curses_num_lines(askstr, maxwidth);
        prompt_width = map_width - 2;
    }

    if (iflags.wc_popup_dialog /*|| curses_stupid_hack*/) {
        askwin = curses_create_window(prompt_width, prompt_height, UP);
        for (count = 0; count < prompt_height; count++) {
            linestr = curses_break_str(askstr, maxwidth, count + 1);
            mvwaddstr(askwin, count + 1, 1, linestr);
            free(linestr);
        }

        wrefresh(askwin);
    } else {
        pline("%s", askstr);
        curs_set(1);
    }

    /*curses_stupid_hack = 0; */

    while (1) {
#ifdef PDCURSES
        answer = wgetch(message_window);
#else
        answer = getch();
#endif
        if (answer == ERR) {
            answer = def;
            break;
        }

        answer = curses_convert_keys(answer);

        if (answer == KEY_ESC) {
            if (choices == NULL) {
                break;
            }
            answer = def;
            for (count = 0; choices[count] != '\0'; count++) {
                if (choices[count] == 'q') {    /* q is preferred over n */
                    answer = 'q';
                } else if ((choices[count] == 'n') && answer != 'q') {
                    answer = 'n';
                }
            }
            break;
        } else if ((answer == '\n') || (answer == '\r') || (answer == ' ')) {
            if ((choices != NULL) && (def != '\0')) {
                answer = def;
            }
            break;
        }

        if (digit(answer)) {
            if (accept_count) {
                if (answer != '0') {
                    yn_number = curses_get_count(answer - '0');
                    touchwin(askwin);
                    refresh();
                }

                answer = '#';
                break;
            }
        }

        if (any_choice) {
            break;
        }

        if (choices != NULL) {
            for (count = 0; (size_t) count < strlen(choices); count++) {
                if (choices[count] == answer) {
                    break;
                }
            }
            if (choices[count] == answer) {
                break;
            }
        }
    }

    if (iflags.wc_popup_dialog) {
        /* Kludge to make prompt visible after window is dismissed
           when inputting a number */
        if (digit(answer)) {
            pline("%s", askstr);
            curs_set(1);
        }

        curses_destroy_win(askwin);
    } else {
        curses_clear_unhighlight_message_window();
        curs_set(0);
    }

    return answer;
}


/* Return an extended command from the user */

int
curses_ext_cmd()
{
    int count, letter, prompt_width, startx, starty, winx, winy;
    int messageh, messagew, maxlen = BUFSZ - 1;
    int ret = -1;
    char cur_choice[BUFSZ];
    int matches = 0;
    WINDOW *extwin = NULL, *extwin2 = NULL;

    if (iflags.extmenu) {
        return extcmd_via_menu();
    }

    startx = 0;
    starty = 0;
    if (iflags.wc_popup_dialog) { /* Prompt in popup window */
        int x0, y0, w, h; /* bounding coords of popup */
        extwin2 = curses_create_window(25, 1, UP);
        wrefresh(extwin2);
        /* create window inside window to prevent overwriting of border */
        getbegyx(extwin2, y0, x0);
        getmaxyx(extwin2, h, w);
        extwin = newwin(1, w - 2, y0 + 1, x0 + 1);
        if (w - 4 < maxlen) maxlen = w - 4;
    } else {
        curses_get_window_xy(MESSAGE_WIN, &winx, &winy);
        curses_get_window_size(MESSAGE_WIN, &messageh, &messagew);

        if (curses_window_has_border(MESSAGE_WIN)) {
            winx++;
            winy++;
        }

        winy += messageh - 1;
        extwin = newwin(1, messagew - 2, winy, winx);
        if (messagew - 4 < maxlen)
            maxlen = messagew - 4;
        pline("#");
    }

    cur_choice[0] = '\0';

    while (1) {
        wmove(extwin, starty, startx);
        waddstr(extwin, "# ");
        wmove(extwin, starty, startx + 2);
        waddstr(extwin, cur_choice);
        wmove(extwin, starty, strlen(cur_choice) + startx + 2);
        wprintw(extwin, "             ");

        /* if we have an autocomplete command, AND it matches uniquely */
        if (matches == 1) {
            curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, ON);
            wmove(extwin, starty, strlen(cur_choice) + startx + 2);
            wprintw(extwin, "%s", extcmdlist[ret].ef_txt + strlen(cur_choice));
            curses_toggle_color_attr(extwin, NONE, A_UNDERLINE, OFF);
            mvwprintw(extwin, starty,
                      strlen(extcmdlist[ret].ef_txt) + 2, "          ");
        }

        wrefresh(extwin);
        letter = getch();
        prompt_width = strlen(cur_choice);
        matches = 0;

        if (letter == '\033' || letter == ERR) {
            ret = -1;
            break;
        }

        if (letter == '\r' || letter == '\n') {
            if (ret == -1) {
                for (count = 0; extcmdlist[count].ef_txt; count++) {
                    if (!strcasecmp(cur_choice, extcmdlist[count].ef_txt)) {
                        ret = count;
                        break;
                    }
                }
            }
            break;
        }

        if (letter == '\177') /* DEL/Rubout */
             letter = '\b';
        if (letter == '\b' || letter == KEY_BACKSPACE) {
            if (prompt_width == 0) {
                ret = -1;
                break;
            } else {
                cur_choice[prompt_width - 1] = '\0';
                letter = '*';
                prompt_width--;
            }
        }
        if (letter != '*' && prompt_width < maxlen) {
            cur_choice[prompt_width] = letter;
            cur_choice[prompt_width + 1] = '\0';
            ret = -1;
        }
        for (count = 0; extcmdlist[count].ef_txt; count++) {
            if (!wizard && (extcmdlist[count].flags & WIZMODECMD))
                continue;
            if (!(extcmdlist[count].flags & AUTOCOMPLETE))
                continue;
            if (strlen(extcmdlist[count].ef_txt) > (size_t) prompt_width) {
                if (strncasecmp(cur_choice, extcmdlist[count].ef_txt,
                                prompt_width) == 0) {
                    if ((extcmdlist[count].ef_txt[prompt_width] ==
                         lowc(letter)) || letter == '*') {
                        if (matches == 0) {
                            ret = count;
                        }

                        matches++;
                    }
                }
            }
        }
    }

    curses_destroy_win(extwin);
    if (extwin2) curses_destroy_win(extwin2);
    return ret;
}


/* Initialize a menu from given NetHack winid */

void
curses_create_nhmenu(winid wid)
{
    nhmenu *new_menu = NULL;
    nhmenu *menuptr = nhmenus;
    nhmenu_item *menu_item_ptr = NULL;
    nhmenu_item *tmp_menu_item = NULL;

    new_menu = get_menu(wid);

    if (new_menu != NULL) {
        /* Reuse existing menu, clearing out current entries */
        menu_item_ptr = new_menu->entries;

        if (menu_item_ptr != NULL) {
            while (menu_item_ptr->next_item != NULL) {
                tmp_menu_item = menu_item_ptr->next_item;
                free((genericptr_t) menu_item_ptr->str);
                free(menu_item_ptr);
                menu_item_ptr = tmp_menu_item;
            }
            free((genericptr_t) menu_item_ptr->str);
            free(menu_item_ptr);        /* Last entry */
            new_menu->entries = NULL;
        }
        if (new_menu->prompt != NULL) { /* Reusing existing menu */
            free((genericptr_t) new_menu->prompt);
        }
        return;
    }

    new_menu = (nhmenu *) alloc((signed) sizeof (nhmenu));
    new_menu->wid = wid;
    new_menu->prompt = NULL;
    new_menu->entries = NULL;
    new_menu->num_pages = 0;
    new_menu->height = 0;
    new_menu->width = 0;
    new_menu->reuse_accels = FALSE;
    new_menu->next_menu = NULL;

    if (nhmenus == NULL) {      /* no menus in memory yet */
        new_menu->prev_menu = NULL;
        nhmenus = new_menu;
    } else {
        while (menuptr->next_menu != NULL) {
            menuptr = menuptr->next_menu;
        }
        new_menu->prev_menu = menuptr;
        menuptr->next_menu = new_menu;
    }
}


/* Add a menu item to the given menu window */

void
curses_add_nhmenu_item(winid wid, int glyph, const ANY_P * identifier,
                       CHAR_P accelerator, CHAR_P group_accel, int attr,
                       const char *str, BOOLEAN_P presel)
{
    char *new_str;
    nhmenu_item *new_item, *current_items, *menu_item_ptr;
    nhmenu *current_menu = get_menu(wid);

    if (current_menu == NULL) {
        impossible(
           "curses_add_nhmenu_item: attempt to add item to nonexistent menu");
        return;
    }

    if (str == NULL) {
        return;
    }

    new_str = curses_copy_of(str);
    curses_rtrim((char *) new_str);
    new_item = (nhmenu_item *) alloc((unsigned) sizeof (nhmenu_item));
    new_item->wid = wid;
    new_item->glyph = glyph;
    new_item->identifier = *identifier;
    new_item->accelerator = accelerator;
    new_item->group_accel = group_accel;
    new_item->attr = attr;
    new_item->str = new_str;
    new_item->presel = presel;
    new_item->selected = FALSE;
    new_item->page_num = 0;
    new_item->line_num = 0;
    new_item->num_lines = 0;
    new_item->count = -1;
    new_item->next_item = NULL;

    current_items = current_menu->entries;
    menu_item_ptr = current_items;

    if (current_items == NULL) {
        new_item->prev_item = NULL;
        current_menu->entries = new_item;
    } else {
        while (menu_item_ptr->next_item != NULL) {
            menu_item_ptr = menu_item_ptr->next_item;
        }
        new_item->prev_item = menu_item_ptr;
        menu_item_ptr->next_item = new_item;
    }
}


/* No more entries are to be added to menu, so details of the menu can be
 finalized in memory */

void
curses_finalize_nhmenu(winid wid, const char *prompt)
{
    int count = 0;
    nhmenu_item *menu_item_ptr;
    nhmenu *current_menu = get_menu(wid);

    if (current_menu == NULL) {
        impossible(
              "curses_finalize_nhmenu: attempt to finalize nonexistent menu");
        return;
    }
    for (menu_item_ptr = current_menu->entries;
         menu_item_ptr != NULL; menu_item_ptr = menu_item_ptr->next_item)
        count++;

    current_menu->num_entries = count;
    current_menu->prompt = curses_copy_of(prompt);
}


/* Display a nethack menu, and return a selection, if applicable */

int
curses_display_nhmenu(winid wid, int how, MENU_ITEM_P ** _selected)
{
    nhmenu *current_menu = get_menu(wid);
    nhmenu_item *menu_item_ptr;
    int num_chosen, count;
    WINDOW *win;
    MENU_ITEM_P *selected = NULL;

    *_selected = NULL;

    if (current_menu == NULL) {
        impossible(
                "curses_display_nhmenu: attempt to display nonexistent menu");
        return '\033';
    }

    menu_item_ptr = current_menu->entries;

    if (menu_item_ptr == NULL) {
        impossible("curses_display_nhmenu: attempt to display empty menu");
        return '\033';
    }

    /* Reset items to unselected to clear out selections from previous
       invocations of this menu, and preselect appropriate items */
    while (menu_item_ptr != NULL) {
        menu_item_ptr->selected = menu_item_ptr->presel;
        menu_item_ptr = menu_item_ptr->next_item;
    }

    menu_win_size(current_menu);
    menu_determine_pages(current_menu);

    /* Display pre and post-game menus centered */
    if (((g.moves <= 1) && !g.invent) || g.program_state.gameover) {
        win = curses_create_window(current_menu->width,
                                   current_menu->height, CENTER);
    } else { /* Display during-game menus on the right out of the way */

        win = curses_create_window(current_menu->width,
                                   current_menu->height, RIGHT);
    }

    num_chosen = menu_get_selections(win, current_menu, how);
    curses_destroy_win(win);

    if (num_chosen > 0) {
        selected = (MENU_ITEM_P *) alloc((unsigned)
                                         (num_chosen * sizeof (MENU_ITEM_P)));
        count = 0;

        menu_item_ptr = current_menu->entries;

        while (menu_item_ptr != NULL) {
            if (menu_item_ptr->selected) {
                if (count == num_chosen) {
                    impossible("curses_display_nhmenu: Selected items "
                          "exceeds expected number");
                     break;
                }
                selected[count].item = menu_item_ptr->identifier;
                selected[count].count = menu_item_ptr->count;
                count++;
            }
            menu_item_ptr = menu_item_ptr->next_item;
        }

        if (count != num_chosen) {
            impossible("curses_display_nhmenu: Selected items less than "
                  "expected number");
        }
    }

    *_selected = selected;

    return num_chosen;
}


boolean
curses_menu_exists(winid wid)
{
    if (get_menu(wid) != NULL) {
        return TRUE;
    } else {
        return FALSE;
    }
}

/* Delete the menu associated with the given NetHack winid from memory */

void
curses_del_menu(winid wid)
{
    nhmenu_item *tmp_menu_item;
    nhmenu_item *menu_item_ptr;
    nhmenu *tmpmenu;
    nhmenu *current_menu = get_menu(wid);

    if (current_menu == NULL) {
        return;
    }

    menu_item_ptr = current_menu->entries;

    /* First free entries associated with this menu from memory */
    if (menu_item_ptr != NULL) {
        while (menu_item_ptr->next_item != NULL) {
            tmp_menu_item = menu_item_ptr->next_item;
            free((genericptr_t) menu_item_ptr->str);
            free(menu_item_ptr);
            menu_item_ptr = tmp_menu_item;
        }
        free((genericptr_t) menu_item_ptr->str);
        free(menu_item_ptr);    /* Last entry */
        current_menu->entries = NULL;
    }

    /* Now unlink the menu from the list and free it as well */
    if (current_menu->prev_menu != NULL) {
        tmpmenu = current_menu->prev_menu;
        tmpmenu->next_menu = current_menu->next_menu;
    } else {
        nhmenus = current_menu->next_menu;      /* New head mode or NULL */
    }
    if (current_menu->next_menu != NULL) {
        tmpmenu = current_menu->next_menu;
        tmpmenu->prev_menu = current_menu->prev_menu;
    }

    free((genericptr_t) current_menu->prompt);
    free(current_menu);

    curses_del_wid(wid);
}


/* return a pointer to the menu associated with the given NetHack winid */

static nhmenu *
get_menu(winid wid)
{
    nhmenu *menuptr = nhmenus;

    while (menuptr != NULL) {
        if (menuptr->wid == wid) {
            return menuptr;
        }
        menuptr = menuptr->next_menu;
    }

    return NULL;                /* Not found */
}


static char
menu_get_accel(boolean first)
{
    char ret;
    static char next_letter = 'a';

    if (first) {
        next_letter = 'a';
    }

    ret = next_letter;

    if ((next_letter < 'z' && next_letter >= 'a')
        || (next_letter < 'Z' && next_letter >= 'A')
        || (next_letter < '9' && next_letter >= '0')) {
        next_letter++;
    } else if (next_letter == 'z') {
        next_letter = 'A';
    } else if (next_letter == 'Z') {
        next_letter = '0';
    }

    return ret;
}


/* Determine if menu will require multiple pages to display */

static boolean
menu_is_multipage(nhmenu *menu, int width, int height)
{
    int num_lines;
    int curline = 0;
    nhmenu_item *menu_item_ptr = menu->entries;

    if (strlen(menu->prompt) > 0) {
        curline += curses_num_lines(menu->prompt, width) + 1;
    }

    if (menu->num_entries <= (height - curline)) {
        while (menu_item_ptr != NULL) {
            menu_item_ptr->line_num = curline;
            if (menu_item_ptr->identifier.a_void == NULL) {
                num_lines = curses_num_lines(menu_item_ptr->str, width);
            } else {
                /* Add space for accelerator */
                num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
            }
            menu_item_ptr->num_lines = num_lines;
            curline += num_lines;
            menu_item_ptr = menu_item_ptr->next_item;
            if (curline > height
                || (curline > height - 2 && height == menu_max_height())) {
                break;
            }
        }
        if (menu_item_ptr == NULL) {
            return FALSE;
        }
    }
    return TRUE;
}


/* Determine which entries go on which page, and total number of pages */

static void
menu_determine_pages(nhmenu *menu)
{
    int tmpline, num_lines;
    int curline = 0;
    int page_num = 1;
    nhmenu_item *menu_item_ptr = menu->entries;
    int width = menu->width;
    int height = menu->height;
    int page_end = height;


    if (strlen(menu->prompt) > 0) {
        curline += curses_num_lines(menu->prompt, width) + 1;
    }

    tmpline = curline;

    if (menu_is_multipage(menu, width, height)) {
        page_end -= 2;          /* Room to display current page number */
    }

    /* Determine what entries belong on which page */
    menu_item_ptr = menu->entries;

    while (menu_item_ptr != NULL) {
        menu_item_ptr->page_num = page_num;
        menu_item_ptr->line_num = curline;
        if (menu_item_ptr->identifier.a_void == NULL) {
            num_lines = curses_num_lines(menu_item_ptr->str, width);
        } else {
            /* Add space for accelerator */
            num_lines = curses_num_lines(menu_item_ptr->str, width - 4);
        }
        menu_item_ptr->num_lines = num_lines;
        curline += num_lines;
        if (curline > page_end) {
            page_num++;
            curline = tmpline;
            /* Move ptr back so entry will be reprocessed on new page */
            menu_item_ptr = menu_item_ptr->prev_item;
        }
        menu_item_ptr = menu_item_ptr->next_item;
    }

    menu->num_pages = page_num;
}


/* Determine dimensions of menu window based on term size and entries */

static void
menu_win_size(nhmenu *menu)
{
    int width, height, maxwidth, maxheight, curentrywidth, lastline;
    int maxentrywidth = strlen(menu->prompt);
    int maxheaderwidth = 0;
    nhmenu_item *menu_item_ptr = menu->entries;

    maxwidth = 38;              /* Reasonable minimum usable width */

    if ((term_cols / 2) > maxwidth) {
        maxwidth = (term_cols / 2);     /* Half the screen */
    }

    maxheight = menu_max_height();

    /* First, determine the width of the longest menu entry */
    while (menu_item_ptr != NULL)
    {
        if (menu_item_ptr->identifier.a_void == NULL) {
            curentrywidth = strlen(menu_item_ptr->str);

            if (curentrywidth > maxheaderwidth) {
                maxheaderwidth = curentrywidth;
            }
        } else {
            /* Add space for accelerator */
            curentrywidth = strlen(menu_item_ptr->str) + 4;
#if 0 /* FIXME: menu glyphs */
            if (menu_item_ptr->glyph != NO_GLYPH
                        && iflags.use_menu_glyphs)
                curentrywidth += 2;
#endif
        }
        if (curentrywidth > maxentrywidth) {
            maxentrywidth = curentrywidth;
        }
        menu_item_ptr = menu_item_ptr->next_item;
    }

    /* If widest entry is smaller than maxwidth, reduce maxwidth accordingly */
    if (maxentrywidth < maxwidth) {
        maxwidth = maxentrywidth;
    }

    /* Try not to wrap headers/normal text lines if possible.  We can
       go wider than half the screen for this purpose if need be */

    if ((maxheaderwidth > maxwidth) && (maxheaderwidth < (term_cols - 2))) {
        maxwidth = maxheaderwidth;
    }

    width = maxwidth;

    /* Possibly reduce height if only 1 page */
    if (!menu_is_multipage(menu, maxwidth, maxheight)) {
        menu_item_ptr = menu->entries;

        while (menu_item_ptr->next_item != NULL) {
            menu_item_ptr = menu_item_ptr->next_item;
        }

        lastline = (menu_item_ptr->line_num) + menu_item_ptr->num_lines;

        if (lastline < maxheight) {
            maxheight = lastline;
        }
    } else { /* If multipage, make sure we have enough width for page footer */

        if (width < 20) {
            width = 20;
        }
    }

    height = maxheight;
    menu->width = width;
    menu->height = height;
}


/* Displays menu selections in the given window */

static void
menu_display_page(nhmenu *menu, WINDOW * win, int page_num)
{
    nhmenu_item *menu_item_ptr;
    int count, curletter, entry_cols, start_col, num_lines, footer_x;
    char *tmpstr;
    boolean first_accel = TRUE;
    int color = NO_COLOR;
    int attr = A_NORMAL;
    boolean menu_color = FALSE;

    /* Cycle through entries until we are on the correct page */

    menu_item_ptr = menu->entries;

    while (menu_item_ptr != NULL) {
        if (menu_item_ptr->page_num == page_num) {
            break;
        }
        menu_item_ptr = menu_item_ptr->next_item;
    }

    if (menu_item_ptr == NULL) {        /* Page not found */
        impossible("menu_display_page: attempt to display nonexistent page");
        return;
    }

    werase(win);

    if (menu->prompt && *menu->prompt) {
        num_lines = curses_num_lines(menu->prompt, menu->width);

        for (count = 0; count < num_lines; count++) {
            tmpstr = curses_break_str(menu->prompt, menu->width, count + 1);
            mvwprintw(win, count + 1, 1, "%s", tmpstr);
            free(tmpstr);
        }
    }

    /* Display items for current page */

    while (menu_item_ptr != NULL) {
        if (menu_item_ptr->page_num != page_num) {
            break;
        }
        if (menu_item_ptr->identifier.a_void != NULL) {
            if (menu_item_ptr->accelerator != 0) {
                curletter = menu_item_ptr->accelerator;
            } else {
                if (first_accel) {
                    curletter = menu_get_accel(TRUE);
                    first_accel = FALSE;
                    if (!menu->reuse_accels && (menu->num_pages > 1)) {
                        menu->reuse_accels = TRUE;
                    }
                } else {
                    curletter = menu_get_accel(FALSE);
                }
                menu_item_ptr->accelerator = curletter;
            }

            if (menu_item_ptr->selected) {
                curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
                mvwaddch(win, menu_item_ptr->line_num + 1, 1, '<');
                mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
                mvwaddch(win, menu_item_ptr->line_num + 1, 3, '>');
                curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
            } else {
                curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
                mvwaddch(win, menu_item_ptr->line_num + 1, 2, curletter);
                curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
                mvwprintw(win, menu_item_ptr->line_num + 1, 3, ") ");
            }
        }
        entry_cols = menu->width;
        start_col = 1;

        if (menu_item_ptr->identifier.a_void != NULL) {
            entry_cols -= 4;
            start_col += 4;
        }
#if 0
        /* FIXME: menuglyphs not implemented yet */
        if (menu_item_ptr->glyph != NO_GLYPH && iflags.use_menu_glyphs) {
            unsigned special;  /*notused */

            mapglyph(menu_item_ptr->glyph, &curletter, &color, &special, 0, 0);
            curses_toggle_color_attr(win, color, NONE, ON);
            mvwaddch(win, menu_item_ptr->line_num + 1, start_col, curletter);
            curses_toggle_color_attr(win, color, NONE, OFF);
            mvwaddch(win, menu_item_ptr->line_num + 1, start_col + 1, ' ');
            entry_cols -= 2;
            start_col += 2;
        }
#endif
        if (iflags.use_menu_color
            && (menu_color = get_menu_coloring(menu_item_ptr->str,
                                               &color, &attr)) != 0) {
            if (color != NO_COLOR) {
                curses_toggle_color_attr(win, color, NONE, ON);
            }
            attr = curses_convert_attr(attr);
            if (attr != A_NORMAL) {
                menu_item_ptr->attr = menu_item_ptr->attr | attr;
            }
        }
        curses_toggle_color_attr(win, NONE, menu_item_ptr->attr, ON);

        num_lines = curses_num_lines(menu_item_ptr->str, entry_cols);

        for (count = 0; count < num_lines; count++) {
            if (menu_item_ptr->str && *menu_item_ptr->str) {
                tmpstr = curses_break_str(menu_item_ptr->str,
                                          entry_cols, count + 1);
                mvwprintw(win, menu_item_ptr->line_num + count + 1, start_col,
                          "%s", tmpstr);
                free(tmpstr);
            }
        }
        if (menu_color && (color != NO_COLOR)) {
            curses_toggle_color_attr(win, color, NONE, OFF);
        }
        curses_toggle_color_attr(win, NONE, menu_item_ptr->attr, OFF);
        menu_item_ptr = menu_item_ptr->next_item;
    }

    if (menu->num_pages > 1) {
        footer_x = menu->width - strlen("<- (Page X of Y) ->");
        if (menu->num_pages > 9) {      /* Unlikely */
            footer_x -= 2;
        }
        mvwprintw(win, menu->height, footer_x + 3, "(Page %d of %d)",
                  page_num, menu->num_pages);
        if (page_num != 1) {
            curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
            mvwaddstr(win, menu->height, footer_x, "<=");
            curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
        }
        if (page_num != menu->num_pages) {
            curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
            mvwaddstr(win, menu->height, menu->width - 2, "=>");
            curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
        }
    }
    curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, ON);
    box(win, 0, 0);
    curses_toggle_color_attr(win, DIALOG_BORDER_COLOR, NONE, OFF);
    wrefresh(win);
}


static int
menu_get_selections(WINDOW * win, nhmenu *menu, int how)
{
    int curletter;
    int count = -1;
    int count_letter = '\0';
    int curpage = 1;
    int num_selected = 0;
    boolean dismiss = FALSE;
    char search_key[BUFSZ];
    nhmenu_item *menu_item_ptr = menu->entries;

    menu_display_page(menu, win, 1);

    while (!dismiss) {
        curletter = getch();

        if (curletter == ERR) {
            num_selected = -1;
            dismiss = TRUE;
        }

        if (curletter == '\033') {
            curletter = curses_convert_keys(curletter);
        }

        switch (how) {
        case PICK_NONE:
            if (menu->num_pages == 1) {
                if (curletter == KEY_ESC) {
                    num_selected = -1;
                } else {
                    num_selected = 0;

                }
                dismiss = TRUE;
                break;
            }
            break;
        case PICK_ANY:
            switch (curletter) {
            case MENU_SELECT_PAGE:
                (void) menu_operation(win, menu, SELECT, curpage);
                break;
            case MENU_SELECT_ALL:
                curpage = menu_operation(win, menu, SELECT, 0);
                break;
            case MENU_UNSELECT_PAGE:
                (void) menu_operation(win, menu, DESELECT, curpage);
                break;
            case MENU_UNSELECT_ALL:
                curpage = menu_operation(win, menu, DESELECT, 0);
                break;
            case MENU_INVERT_PAGE:
                (void) menu_operation(win, menu, INVERT, curpage);
                break;
            case MENU_INVERT_ALL:
                curpage = menu_operation(win, menu, INVERT, 0);
                break;
            }
            /*FALLTHRU*/
        default:
            if (isdigit(curletter)) {
                count = curses_get_count(curletter - '0');
                touchwin(win);
                refresh();
                curletter = getch();
                if (count > 0) {
                    count_letter = curletter;
                }
            }
        }

        switch (curletter) {
        case KEY_ESC:
            num_selected = -1;
            dismiss = TRUE;
            break;
        case '\n':
        case '\r':
            dismiss = TRUE;
            break;
        case KEY_RIGHT:
        case KEY_NPAGE:
        case MENU_NEXT_PAGE:
        case ' ':
            if (curpage < menu->num_pages) {
                curpage++;
                menu_display_page(menu, win, curpage);
            } else if (curletter == ' ') {
                dismiss = TRUE;
                break;
            }
            break;
        case KEY_LEFT:
        case KEY_PPAGE:
        case MENU_PREVIOUS_PAGE:
            if (curpage > 1) {
                curpage--;
                menu_display_page(menu, win, curpage);
            }
            break;
        case KEY_END:
        case MENU_LAST_PAGE:
            if (curpage != menu->num_pages) {
                curpage = menu->num_pages;
                menu_display_page(menu, win, curpage);
            }
            break;
        case KEY_HOME:
        case MENU_FIRST_PAGE:
            if (curpage != 1) {
                curpage = 1;
                menu_display_page(menu, win, curpage);
            }
            break;
        case MENU_SEARCH:
            curses_line_input_dialog("Search for:", search_key, BUFSZ);

            refresh();
            touchwin(win);
            wrefresh(win);

            if (strlen(search_key) == 0) {
                break;
            }

            menu_item_ptr = menu->entries;

            while (menu_item_ptr != NULL) {
                if (menu_item_ptr->identifier.a_void != NULL
                    && strstri(menu_item_ptr->str, search_key)) {
                    if (how == PICK_ONE) {
                        menu_clear_selections(menu);
                        menu_select_deselect(win, menu_item_ptr, SELECT);
                        num_selected = 1;
                        dismiss = TRUE;
                        break;
                    } else {
                        menu_select_deselect(win, menu_item_ptr, INVERT);
                    }
                }

                menu_item_ptr = menu_item_ptr->next_item;
            }

            menu_item_ptr = menu->entries;
            break;
        default:
            if (how == PICK_NONE) {
                num_selected = 0;
                dismiss = TRUE;
                break;
            }
        }

        menu_item_ptr = menu->entries;
        while (menu_item_ptr != NULL) {
            if (menu_item_ptr->identifier.a_void != NULL) {
                if ((curletter == menu_item_ptr->accelerator
                     && (curpage == menu_item_ptr->page_num
                         || !menu->reuse_accels))
                    || (menu_item_ptr->group_accel
                        && curletter == menu_item_ptr->group_accel)) {
                    if (curpage != menu_item_ptr->page_num) {
                        curpage = menu_item_ptr->page_num;
                        menu_display_page(menu, win, curpage);
                    }

                    if (how == PICK_ONE) {
                        menu_clear_selections(menu);
                        menu_select_deselect(win, menu_item_ptr, SELECT);
                        if (count)
                            menu_item_ptr->count = count;
                        num_selected = 1;
                        dismiss = TRUE;
                        break;
                    } else if (how == PICK_ANY && curletter == count_letter) {
                        menu_select_deselect(win, menu_item_ptr, SELECT);
                        menu_item_ptr->count = count;
                        count = 0;
                        count_letter = '\0';
                    } else {
                        menu_select_deselect(win, menu_item_ptr, INVERT);
                    }
                }
            }
            menu_item_ptr = menu_item_ptr->next_item;
        }
    }

    if (how == PICK_ANY && num_selected != -1) {
        num_selected = 0;
        menu_item_ptr = menu->entries;

        while (menu_item_ptr != NULL) {
            if (menu_item_ptr->identifier.a_void != NULL) {
                if (menu_item_ptr->selected) {
                    num_selected++;
                }
            }
            menu_item_ptr = menu_item_ptr->next_item;
        }
    }

    return num_selected;
}


/* Select, deselect, or toggle selected for the given menu entry */

static void
menu_select_deselect(WINDOW * win, nhmenu_item *item, menu_op operation)
{
    int curletter = item->accelerator;

    if ((operation == DESELECT) || (item->selected && (operation == INVERT))) {
        item->selected = FALSE;
        mvwaddch(win, item->line_num + 1, 1, ' ');
        curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, ON);
        mvwaddch(win, item->line_num + 1, 2, curletter);
        curses_toggle_color_attr(win, HIGHLIGHT_COLOR, NONE, OFF);
        mvwaddch(win, item->line_num + 1, 3, ')');
    } else {
        item->selected = TRUE;
        curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, ON);
        mvwaddch(win, item->line_num + 1, 1, '<');
        mvwaddch(win, item->line_num + 1, 2, curletter);
        mvwaddch(win, item->line_num + 1, 3, '>');
        curses_toggle_color_attr(win, HIGHLIGHT_COLOR, A_REVERSE, OFF);
    }

    wrefresh(win);
}


/* Perform the selected operation (select, unselect, invert selection)
on the given menu page.  If menu_page is 0, then perform opetation on
all pages in menu.  Returns last page displayed.  */

static int
menu_operation(WINDOW * win, nhmenu *menu, menu_op
               operation, int page_num)
{
    int first_page, last_page, current_page;
    nhmenu_item *menu_item_ptr = menu->entries;

    if (page_num == 0) {        /* Operation to occur on all pages */
        first_page = 1;
        last_page = menu->num_pages;
    } else {
        first_page = page_num;
        last_page = page_num;
    }

    /* Cycle through entries until we are on the correct page */

    while (menu_item_ptr != NULL) {
        if (menu_item_ptr->page_num == first_page) {
            break;
        }
        menu_item_ptr = menu_item_ptr->next_item;
    }

    current_page = first_page;

    if (page_num == 0) {
        menu_display_page(menu, win, current_page);
    }

    if (menu_item_ptr == NULL) {        /* Page not found */
        impossible("menu_display_page: attempt to display nonexistent page");
        return 0;
    }

    while (menu_item_ptr != NULL) {
        if (menu_item_ptr->page_num != current_page) {
            if (menu_item_ptr->page_num > last_page) {
                break;
            }

            current_page = menu_item_ptr->page_num;
            menu_display_page(menu, win, current_page);
        }

        if (menu_item_ptr->identifier.a_void != NULL) {
            menu_select_deselect(win, menu_item_ptr, operation);
        }

        menu_item_ptr = menu_item_ptr->next_item;
    }

    return current_page;
}


/* Set all menu items to unselected in menu */

static void
menu_clear_selections(nhmenu *menu)
{
    nhmenu_item *menu_item_ptr = menu->entries;

    while (menu_item_ptr != NULL) {
        menu_item_ptr->selected = FALSE;
        menu_item_ptr = menu_item_ptr->next_item;
    }
}


/* Get the maximum height for a menu */

static int
menu_max_height(void)
{
    return term_rows - 2;
}
