/*      $NetBSD: msg_sys.def,v 1.48 2019/11/16 17:38:09 martin Exp $    */

/*
* Copyright 1997 Piermont Information Systems Inc.
* All rights reserved.
*
* Written by Philip A. Nelson for Piermont Information Systems Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. The name of Piermont Information Systems Inc. may not be used to endorse
*    or promote products derived from this software without specific prior
*    written permission.
*
* THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
*/

static WINDOW *msg_win = NULL;
static char *cbuffer;
static size_t cbuffersize;

static int last_i_was_nl, last_i_was_space;
static int last_o_was_punct, last_o_was_space;

static void     _msg_beep(void);
static int      _msg_vprintf(int, const char *, va_list);
#define MSG_PROMPT_ECHO         1
#define MSG_PROMPT_HIDE_DFLT    2
static void     _msg_vprompt(const char *, int, const char *, char *,
                       size_t, va_list);

static char *msgmap = MAP_FAILED;
static size_t msgmapsz;
static unsigned int msgmapcount;

/* Routines */

static void
_msg_beep(void)
{

       fprintf(stderr, "\a");
}

WINDOW *
msg_window(WINDOW *window)
{
       size_t ncbuffersize;
       char *ncbuffer;
       WINDOW *old;

       old = msg_win;
       if (!window)
               return old;
       msg_win = window;

       ncbuffersize = getmaxx(window) * getmaxy(window) + 1;
       while (ncbuffersize > cbuffersize) {
               ncbuffer = malloc(ncbuffersize);
               if (ncbuffer == NULL) {
                       /* we might get truncated messages... */
                       ncbuffersize <<= 1;
                       continue;
               }
               if (cbuffer != NULL)
                       free(cbuffer);
               cbuffer = ncbuffer;
               cbuffersize = ncbuffersize;
               break;
       }
       last_o_was_punct = 0;
       last_o_was_space = 1;
       return old;
}

int
msg_file(const char *file)
{
       int fd;

       if (msgmap != MAP_FAILED)
               munmap(msgmap, msgmapsz);
       msgmap = MAP_FAILED;
       if (!file)
               return 0;
       fd = open(file, O_RDONLY, 0);
       if (fd == -1)
               return -1;
       msgmapsz = lseek(fd, 0, SEEK_END);
       msgmap = mmap(0, msgmapsz, PROT_READ, MAP_SHARED, fd, 0);
       close(fd);
       if (msgmap == MAP_FAILED)
               return -1;
       /* check_magic */
       if (strcmp(msgmap, "MSGTXTS") != 0) {
               msg_file(NULL);
               return -1;
       }
       msgmapcount = atoi(msgmap + 8);
       return 0;
}

const char *
msg_string(msg msg_no)
{
       uintptr_t m = (uintptr_t)msg_no;

       if (m > sizeof msg_list / sizeof msg_list[0])
               /* guess that we were passed a text string */
               return msg_no;

       if (msgmap != MAP_FAILED && m != 0 && m <= msgmapcount) {
               unsigned int offset = atoi(msgmap + 8 + 8 * m);
               if (offset != 0 && offset < msgmapsz)
                       return msgmap + offset;
       }

       return msg_list[m];
}

void
msg_clear(void)
{

       wclear(msg_win);
       last_o_was_punct = 0;
       last_o_was_space = 1;
}

void
msg_standout(void)
{

       wstandout(msg_win);
}

void
msg_standend(void)
{

       wstandend(msg_win);
}

static int __printflike(2, 0)
_msg_vprintf(int auto_fill, const char *fmt, va_list ap)
{
       const char *wstart, *afterw;
       int wordlen, nspaces;
       int ret;

       ret = vsnprintf(cbuffer, cbuffersize, fmt, ap);

       if (!auto_fill) {
               waddstr(msg_win, cbuffer);

               /*
                * nothing is perfect if they flow text after a table,
                * but this may be decent.
                */
               last_i_was_nl = last_i_was_space = 1;
               last_o_was_punct = 0;
               last_o_was_space = 1;
               goto out;
       }

       for (wstart = afterw = cbuffer; *wstart; wstart = afterw) {

               /* eat one space, or a whole word of non-spaces */
               if (isspace((unsigned char)*afterw))
                       afterw++;
               else
                       while (*afterw && !isspace((unsigned char)*afterw))
                               afterw++;

               /* this is an nl: special formatting necessary */
               if (*wstart == '\n') {
                       if (last_i_was_nl || last_i_was_space) {

                               if (getcurx(msg_win) != 0)
                                       waddch(msg_win, '\n');
                               if (last_i_was_nl) {
                                       /* last was an nl: paragraph break */
                                       waddch(msg_win, '\n');
                               } else {
                                       /* last was space: line break */
                               }
                               last_o_was_punct = 0;
                               last_o_was_space = 1;
                       } else {
                               /* last_o_was_punct unchanged */
                               /* last_o_was_space unchanged */
                       }
                       last_i_was_space = 1;
                       last_i_was_nl = 1;
                       continue;
               }

               /* this is a tab: special formatting necessary. */
               if (*wstart == '\t') {
                       if (last_i_was_nl) {
                               /* last was an nl: list indent */
                               if (getcurx(msg_win) != 0)
                                       waddch(msg_win, '\n');
                       } else {
                               /* last was not an nl: columns */
                       }
                       waddch(msg_win, '\t');
                       last_i_was_nl = 0;
                       last_i_was_space = 1;
                       last_o_was_punct = 0;
                       last_o_was_space = 1;
                       continue;
               }

               /* this is a space: ignore it but set flags */
               last_i_was_nl = 0;      /* all newlines handled above */
               last_i_was_space = isspace((unsigned char)*wstart);
               if (last_i_was_space)
                       continue;

               /*
                * we have a real "word," i.e. a sequence of non-space
                * characters.  wstart is now the start of the word,
                * afterw is the next character after the end.
                */
               wordlen = afterw - wstart;
               nspaces = last_o_was_space ? 0 : (last_o_was_punct ? 2 : 1);
               if ((getcurx(msg_win) + nspaces + wordlen) >=
                     getmaxx(msg_win) &&
                   wordlen < (getmaxx(msg_win) / 3)) {
                       /* wrap the line */
                       waddch(msg_win, '\n');
                       nspaces = 0;
               }

               /* output the word, preceded by spaces if necessary */
               while (nspaces-- > 0)
                       waddch(msg_win, ' ');
               waddbytes(msg_win, wstart, wordlen);

               /* set up the 'last' state for the next time around */
               switch (afterw[-1]) {
               case '.':
               case '?':
               case '!':
                       last_o_was_punct = 1;
                       break;
               default:
                       last_o_was_punct = 0;
                       break;
               }
               last_o_was_space = 0;

               /* ... and do it all again! */
       }

       /* String ended with a newline.  They really want a line break. */
       if (last_i_was_nl) {
               if (getcurx(msg_win) != 0)
                       waddch(msg_win, '\n');
               last_o_was_punct = 0;
               last_o_was_space = 1;
       }

out:
       wrefresh(msg_win);
       return ret;
}

void
msg_display(msg msg_no)
{

       msg_clear();
       msg_printf("%s", msg_string(msg_no));
}

void __printflike(2, 3)
msg_fmt_display(msg msg_no, const char *fmt, ...)
{
       va_list ap;

       msg_clear();

       va_start(ap, fmt);
       (void)_msg_vprintf(1, fmtcheck(msg_string(msg_no), fmt), ap);
       va_end(ap);
}

void
msg_display_add(msg msg_no)
{

       msg_printf("%s", msg_string(msg_no));
}

void __printflike(2, 3)
msg_fmt_display_add(msg msg_no, const char *fmt, ...)
{
       va_list ap;

       va_start(ap, fmt);
       (void)_msg_vprintf(1, fmtcheck(msg_string(msg_no), fmt), ap);
       va_end(ap);
}

void __printflike(1, 2)
msg_printf(const char *fmt, ...)
{
       va_list ap;

       va_start(ap, fmt);
       (void)_msg_vprintf(1, fmt, ap);
       va_end(ap);
}

static void __printflike(1, 0)
_msg_vprompt(const char *fmt, int flags, const char *def, char *val,
   size_t val_buf_len, va_list ap)
{
       int ch;
       int len, pos, npos, off;
       int first;
       int txt_y, txt_x;
       char *ibuf;
       int maxx;

       if (val == NULL || val_buf_len == 0) {
               /* No answer wanted */
               val = NULL;
               val_buf_len = 1;
       }

       ibuf = malloc(val_buf_len);

       keypad(msg_win, TRUE);
       _msg_vprintf(0, fmt, ap);
       ibuf[0] = 0;
       if (def != NULL && *def) {
               if (flags & MSG_PROMPT_HIDE_DFLT)
                       strlcpy(ibuf, def, val_buf_len);
               else {
                       waddstr(msg_win, " [");
                       waddstr(msg_win, def);
                       waddstr(msg_win, "]");
               }
       }
       waddstr(msg_win, ": ");
       len = strlen(ibuf);
       pos = len;
       getyx(msg_win, txt_y, txt_x);
       maxx = getmaxx(msg_win) - txt_x - 1;
       off = 0;

       for (first = 1; ; first = 0) {

               if (flags & MSG_PROMPT_ECHO) {
                       /* shift text right as we near the buffer start */
                       if (pos - off < 4)
                               off = pos - 4;
                       /* keep offset to a minimum if we are at the end */
                       if (pos == len)
                               off = pos - maxx;
                       if (off < 0 || len <= maxx)
                               off = 0;
                       /* shift text left as we near the buffer end */
                       npos = pos + 4;
                       if (npos > len)
                               npos = len;
                       if (npos - off > maxx)
                               off = npos - maxx;
                       /* calc. length to display */
                       npos = len - off;
                       if (npos > maxx)
                               npos = maxx;
                       mvwaddnstr(msg_win, txt_y, txt_x, ibuf + off, npos);
                       wclrtoeol(msg_win);
                       if (off != 0)
                               mvwaddstr(msg_win, txt_y, txt_x, "+");
                       wmove(msg_win, txt_y, txt_x + pos - off);
                       wrefresh(msg_win);
               }

               ch = wgetch(msg_win);
               if (ch == '\n')
                       break;

               switch (ch) {
               case KEY_BACKSPACE:
               case 'h' & 0x1f: case 0x7f:  /* bs or del - delete left */
                       if (first) {
                               /* delete all of default string */
                               len = pos = 0;
                               break;
                       }
                       if (pos > 0) {
                               memmove(ibuf + pos - 1, ibuf + pos, len - pos);
                               len--;
                               pos--;
                       } else
                               _msg_beep();
                       break;
               case 'l' & 0x1f:
                       endwin();
                       doupdate();
                       break;
               case 'u' & 0x1f:        /* ^U; line kill */
                       /* kill line */
                       len = pos = 0;
                       break;
               case 'w' & 0x1f:        /* ^W; word kill */
                       /*
                        * word kill kills the spaces and the 'word'
                        * (non-spaces) last typed.  the spaces before
                        * the 'word' aren't killed.
                        */
                       npos = pos;
                       while (npos > 0 && isspace((unsigned char)ibuf[npos - 1]))
                               npos--;
                       while (npos > 0 && !isspace((unsigned char)ibuf[npos - 1]))
                               npos--;
                       memmove(ibuf + npos, ibuf + pos, len - pos);
                       len -= pos - npos;
                       pos = npos;
                       break;
               case KEY_LEFT:
                       if (pos > 0)
                               pos--;
                       break;
               case KEY_RIGHT:
                       if (len == 0 && pos == 0 && def != NULL) {
                               /* restore default! */
                               strlcpy(ibuf, def, val_buf_len);
                               len = pos = strlen(ibuf);
                               break;
                       }
                       if (pos < len)
                               pos++;
                       break;
               default:
                       if (len < (int)(val_buf_len - 1) && isprint(ch)) {
                               memmove(ibuf + pos + 1, ibuf + pos, len - pos);
                               ibuf[pos++] = ch;
                               len++;
                       } else
                               _msg_beep();
                       break;
               }
       }

       if (flags & MSG_PROMPT_ECHO) {
               mvwaddch(msg_win, txt_y, txt_x + len - off, '\n');
               last_o_was_punct = 0;
               last_o_was_space = 1;
       }

       if (val != NULL) {
               /* copy the appropriate string to the output */
               if (len != 0 || flags & MSG_PROMPT_HIDE_DFLT) {
                       ibuf[len] = '\0';
                       strlcpy(val, ibuf, val_buf_len);
               } else if (def != NULL && val != def) {
                       strlcpy(val, def, val_buf_len);
               }
       }
       free(ibuf);
}

void
msg_prompt(msg msg_no, const char *def, char *val, size_t val_buf_len)
{

       msg_fmt_prompt(msg_no, def, val, val_buf_len, "");
}

void __printflike(5, 6)
msg_fmt_prompt(msg msg_no, const char *def, char *val, size_t val_buf_len,
   const char *fmt, ...)
{
       va_list ap;

       msg_clear();

       va_start(ap, fmt);
       _msg_vprompt(fmtcheck(msg_string(msg_no), fmt), MSG_PROMPT_ECHO,
               def, val, val_buf_len, ap);
       va_end(ap);
}

void
msg_prompt_win(msg msg_no, int x, int y, int w, int h,
   const char *def, char *val, size_t val_buf_len)
{
   msg_fmt_prompt_win(msg_no, x, y, w, h, def, val, val_buf_len, "");
}

void __printflike(9, 10)
msg_fmt_prompt_win(msg msg_no, int x, int y, int w, int h,
   const char *def, char *val, size_t val_buf_len, const char *fmt, ...)
{
       va_list ap;
       WINDOW *win;
       WINDOW *svmsg = NULL, *sv_win = NULL; /* XXX -Wuninitialized [many] */
       int maxx, maxy;
       int msg_flags = MSG_PROMPT_ECHO | MSG_PROMPT_HIDE_DFLT;
       const char *np, *ep;

       maxx = getmaxx(msg_win);
       maxy = getmaxy(msg_win);
       if (w == 0) {
               va_start(ap, fmt);
               w = vsnprintf(NULL, 0, fmtcheck(msg_string(msg_no), fmt), ap);
               va_end(ap);
               if (def != NULL && *def != 0 && w + (int)val_buf_len * 2 < maxx)                {
                       w += 2 + strlen(def) + 1;
                       msg_flags &= ~MSG_PROMPT_HIDE_DFLT;
               }
               w += 1 + 2 + val_buf_len + 1;
               if (w > maxx) {
                       if (!(msg_flags & MSG_PROMPT_HIDE_DFLT)) {
                               w -= 2 + strlen(def) + 1;
                               msg_flags |= MSG_PROMPT_HIDE_DFLT;
                       }
                       w = maxx;
               }
       } else if (w > 0 && def != NULL && *def != 0) {
               size_t tl = strlen(def);
               if (tl + 1 + 2 + val_buf_len + 1 < (unsigned)w)
                       msg_flags &= ~MSG_PROMPT_HIDE_DFLT;
       }

       if (x == -1)
               x = (maxx - w) / 2 + 1;
       if (h < 0) {
               h = 3;
               for (np = msg_string(msg_no); (ep = strchr(np, '\n'));
                   np = ep + 1)
                       h++;
       }
       if (h < 3)
               h = 3;
       if (y < 3)
               y = (maxy - h) / 2;
       if (y + h > maxy)
               y = maxy - h;

       win = subwin(msg_win, h, w, y, x);
       if (win == NULL)
               wprintw(msg_win, "msg_prompt_win: "
                       "newwin(%d, %d, %d, %d) failed\n",
                       h, w, y, x);
       else {
               /*
                * Save screen contents from under our window
                * Due to a mis-feature of NetBSD curses, curscr contains
                * the data processed by doupdate() not that by wnoutrefresh().
                * We must call doupdate() here to ensure we save the correct
                * data.  See PR 26660
                */
               doupdate();
               sv_win = dupwin(win);
               if (sv_win)
                       overwrite(curscr, sv_win);
               wbkgd(win, getbkgd(msg_win));
               wattrset(win, getattrs(msg_win));
               box(win, 0, 0);
               wrefresh(win);

               /* Change message window to be our little box */
               svmsg = msg_window(subwin(msg_win, h - 2, w - 2, y + 1, x + 1));
               wbkgd(msg_win, getbkgd(win));
               wattrset(msg_win, getattrs(win));

               msg_clear();
       }

       va_start(ap, fmt);
       _msg_vprompt(fmtcheck(msg_string(msg_no), fmt), msg_flags, def, val,
           val_buf_len, ap);
       va_end(ap);

       if (win != NULL) {
               wclear(win);
               if (sv_win) {
                       /* Restore original screen contents */
                       overwrite(sv_win, win);
                       delwin(sv_win);
               }
               wnoutrefresh(win);
               /* Restore normal message window */
               delwin(msg_window(svmsg));
               delwin(win);
       }
}

void
msg_prompt_add(msg msg_no, const char *def, char *val, size_t val_buf_len)
{

       msg_fmt_prompt_add(msg_no, def, val, val_buf_len, "");
}

void __printflike(5, 6)
msg_fmt_prompt_add(msg msg_no, const char *def, char *val, size_t val_buf_len,
   const char *fmt, ...)
{
       va_list ap;

       va_start(ap, fmt);
       _msg_vprompt(fmtcheck(msg_string(msg_no), fmt), MSG_PROMPT_ECHO, def,
           val, val_buf_len, ap);
       va_end(ap);
}

void
msg_prompt_noecho(msg msg_no, const char *def, char *val, size_t val_buf_len)
{
       msg_fmt_prompt_noecho(msg_no, def, val, val_buf_len, "");
}

void __printflike(5, 6)
msg_fmt_prompt_noecho(msg msg_no, const char *def, char *val,
   size_t val_buf_len, const char *fmt, ...)
{
       va_list ap;

       msg_clear();

       va_start(ap, fmt);
       _msg_vprompt(fmtcheck(msg_string(msg_no), fmt), 0, def, val,
           val_buf_len, ap);
       va_end(ap);
}

void
msg_table_add(msg msg_no)
{

       msg_fmt_table_add(msg_no, "");
}

void __printflike(2, 3)
msg_fmt_table_add(msg msg_no, const char *fmt, ...)
{
       va_list ap;

       va_start(ap, fmt);
       (void)_msg_vprintf(0, fmtcheck(msg_string(msg_no), fmt), ap);
       va_end(ap);
}

int
msg_row(void)
{

       return getcury(msg_win) + getbegy(msg_win);
}