/*
 * Copyright (C) 1996-2002,2010,2012-2013 Michael R. Elkins <me@mutt.org>
 * Copyright (C) 2004 g10 Code GmbH
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "mutt_menu.h"
#include "mutt_curses.h"
#include "pager.h"
#include "mbyte.h"
#include "background.h"
#ifdef USE_INOTIFY
#include "monitor.h"
#endif

#include <termios.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/time.h>
#include <time.h>

#ifdef HAVE_LANGINFO_YESEXPR
#include <langinfo.h>
#endif

/* Error message ring */
struct error_history
{
  char **msg;
  short last;
} ErrorHistory = {0, 0};

static short OldErrorHistSize = 0;


/* not possible to unget more than one char under some curses libs, and it
 * is impossible to unget function keys in SLang, so roll our own input
 * buffering routines.
 */

/* These are used for macros and exec/push commands.
 * They can be temporarily ignored by setting OPTIGNOREMACROEVENTS
 */
static size_t MacroBufferCount = 0;
static size_t MacroBufferLen = 0;
static event_t *MacroEvents;

/* These are used in all other "normal" situations, and are not
 * ignored when setting OPTIGNOREMACROEVENTS
 */
static size_t UngetCount = 0;
static size_t UngetLen = 0;
static event_t *UngetKeyEvents;

int MuttGetchTimeout = -1;

mutt_window_t *MuttHelpWindow = NULL;
mutt_window_t *MuttIndexWindow = NULL;
mutt_window_t *MuttStatusWindow = NULL;
mutt_window_t *MuttMessageWindow = NULL;
#ifdef USE_SIDEBAR
mutt_window_t *MuttSidebarWindow = NULL;
#endif

static void reflow_message_window_rows(int mw_rows);


void mutt_refresh(void)
{
  /* don't refresh when we are waiting for a child. */
  if (option(OPTKEEPQUIET))
    return;

  /* don't refresh in the middle of macros unless necessary */
  if (MacroBufferCount && !option(OPTFORCEREFRESH) &&
      !option(OPTIGNOREMACROEVENTS))
    return;

  /* else */
  refresh();
}

/* Make sure that the next refresh does a full refresh.  This could be
   optimized by not doing it at all if DISPLAY is set as this might
   indicate that a GUI based pinentry was used.  Having an option to
   customize this is of course the Mutt way.  */
void mutt_need_hard_redraw(void)
{
  keypad(stdscr, TRUE);
  clearok(stdscr, TRUE);
  mutt_set_current_menu_redraw_full();
}

/* delay is just like for timeout() or poll():
 *   the number of milliseconds mutt_getch() should block for input.
 *   delay == 0 means mutt_getch() is non-blocking.
 *   delay < 0 means mutt_getch is blocking.
 */
void mutt_getch_timeout(int delay)
{
  MuttGetchTimeout = delay;
  timeout(delay);
}

#ifdef USE_INOTIFY
static int mutt_monitor_getch(void)
{
  int ch;

  /* ncurses has its own internal buffer, so before we perform a poll,
   * we need to make sure there isn't a character waiting */
  timeout(0);
  ch = getch();
  timeout(MuttGetchTimeout);
  if (ch == ERR)
  {
    if (mutt_monitor_poll() != 0)
      ch = ERR;
    else
      ch = getch();
  }
  return ch;
}
#endif /* USE_INOTIFY */

event_t mutt_getch(void)
{
  int ch;
  event_t err = {-1, OP_NULL }, ret;
  event_t timeout = {-2, OP_NULL};

  if (UngetCount)
    return (UngetKeyEvents[--UngetCount]);

  if (!option(OPTIGNOREMACROEVENTS) && MacroBufferCount)
    return (MacroEvents[--MacroBufferCount]);

  SigInt = 0;

  mutt_allow_interrupt(1);
#ifdef KEY_RESIZE
  /* ncurses 4.2 sends this when the screen is resized */
  ch = KEY_RESIZE;
  while (ch == KEY_RESIZE)
#endif /* KEY_RESIZE */
#ifdef USE_INOTIFY
    ch = mutt_monitor_getch();
#else
    ch = getch();
#endif /* USE_INOTIFY */
  mutt_allow_interrupt(0);

  if (SigInt)
  {
    mutt_query_exit();
    return err;
  }

  /* either timeout, a sigwinch (if timeout is set), or the terminal
   * has been lost */
  if (ch == ERR)
  {
    if (!isatty(0))
    {
      endwin();
      exit(1);
    }
    return timeout;
  }

  if ((ch & 0x80) && option(OPTMETAKEY))
  {
    /* send ALT-x as ESC-x */
    ch &= ~0x80;
    mutt_unget_event(ch, 0);
    ret.ch = '\033';
    ret.op = 0;
    return ret;
  }

  ret.ch = ch;
  ret.op = 0;
  return (ch == ctrl('G') ? err : ret);
}

static int _get_field(const char *field, BUFFER *buffer, int complete,
                      int multiple, char ***files, int *numfiles)
{
  int ret;
  int x;

  ENTER_STATE *es = mutt_new_enter_state();

  do
  {
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
    if (SigWinch)
    {
      SigWinch = 0;
      mutt_resize_screen();
      clearok(stdscr, TRUE);
      mutt_current_menu_redraw();
    }
#endif
    mutt_window_clearline(MuttMessageWindow, 0);
    SETCOLOR(MT_COLOR_PROMPT);
    addstr(field);
    NORMAL_COLOR;
    mutt_refresh();
    mutt_window_getyx(MuttMessageWindow, NULL, &x);
    ret = _mutt_enter_string(buffer->data, buffer->dsize, x, complete, multiple, files, numfiles, es);
  }
  while (ret == 1);

  if (ret != 0)
    mutt_buffer_clear(buffer);
  else
    mutt_buffer_fix_dptr(buffer);

  mutt_window_clearline(MuttMessageWindow, 0);
  mutt_free_enter_state(&es);

  return (ret);
}

int mutt_get_field(const char *field, char *buf, size_t buflen, int complete)
{
  BUFFER *buffer;
  int rc;

  buffer = mutt_buffer_pool_get();

  mutt_buffer_increase_size(buffer, buflen);
  mutt_buffer_addstr(buffer, buf);
  rc = _get_field(field, buffer, complete, 0, NULL, NULL);
  strfcpy(buf, mutt_b2s(buffer), buflen);

  mutt_buffer_pool_release(&buffer);
  return rc;
}

int mutt_buffer_get_field(const char *field, BUFFER *buffer, int complete)
{
  return _get_field(field, buffer, complete, 0, NULL, NULL);
}

int mutt_get_field_unbuffered(char *msg, char *buf, size_t buflen, int flags)
{
  int rc, reset_ignoremacro = 0;

  if (!option(OPTIGNOREMACROEVENTS))
  {
    set_option(OPTIGNOREMACROEVENTS);
    reset_ignoremacro = 1;
  }
  rc = mutt_get_field(msg, buf, buflen, flags);
  if (reset_ignoremacro)
    unset_option(OPTIGNOREMACROEVENTS);

  return (rc);
}

void mutt_clear_error(void)
{
  Errorbuf[0] = 0;
  if (!option(OPTNOCURSES))
    mutt_window_clearline(MuttMessageWindow, 0);
}

void mutt_edit_file(const char *editor, const char *data)
{
  BUFFER *cmd;

  cmd = mutt_buffer_pool_get();

  mutt_endwin(NULL);
  mutt_expand_file_fmt(cmd, editor, data);
  if (mutt_system(mutt_b2s(cmd)))
  {
    mutt_error(_("Error running \"%s\"!"), mutt_b2s(cmd));
    mutt_sleep(2);
  }

  mutt_buffer_pool_release(&cmd);
}

/* Prompts for a yes or no response.
 * If var is non-null, it will print a help message referencing the variable
 * when '?' is pressed.
 */
int mutt_yesorno_with_help(const char *msg, int def, const char *var)
{
  event_t ch;
  char *yes = _("yes");
  char *no = _("no");
  BUFFER *answer_buffer = NULL, *help_buffer = NULL;
  int answer_string_wid, msg_wid;
  size_t trunc_msg_len, trunc_help_len;
  int redraw = 1, prompt_lines = 1;
  int show_help_prompt = 0, show_help = 0;

#ifdef HAVE_LANGINFO_YESEXPR
  char *expr;
  regex_t reyes;
  regex_t reno;
  int reyes_ok;
  int reno_ok;
  char answer[2];

  answer[1] = 0;

  reyes_ok = (expr = nl_langinfo(YESEXPR)) && expr[0] == '^' &&
    !REGCOMP(&reyes, expr, REG_NOSUB);
  reno_ok = (expr = nl_langinfo(NOEXPR)) && expr[0] == '^' &&
    !REGCOMP(&reno, expr, REG_NOSUB);
#endif

  if (var)
    show_help_prompt = 1;

  /*
   * In order to prevent the default answer to the question to wrapped
   * around the screen in the even the question is wider than the screen,
   * ensure there is enough room for the answer and truncate the question
   * to fit.
   */
  answer_buffer = mutt_buffer_pool_get();
  mutt_buffer_printf(answer_buffer,
                     " ([%s]/%s%s): ",
                     def == MUTT_YES ? yes : no,
                     def == MUTT_YES ? no : yes,
                     show_help_prompt ? "/?" : "");
  answer_string_wid = mutt_strwidth(mutt_b2s(answer_buffer));

  msg_wid = mutt_strwidth(msg);

  FOREVER
  {
    if (redraw || SigWinch)
    {
      redraw = 0;
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
      if (SigWinch)
      {
        SigWinch = 0;
        mutt_resize_screen();
        clearok(stdscr, TRUE);
        mutt_current_menu_redraw();
      }
#endif
      if (MuttMessageWindow->cols)
      {
        prompt_lines = (msg_wid + answer_string_wid + MuttMessageWindow->cols - 1) /
          MuttMessageWindow->cols;
        prompt_lines = MAX(1, MIN(3, prompt_lines));
      }
      else
        prompt_lines = 1;

      /* maxlen here is sort of arbitrary, so pick a reasonable upper bound */
      trunc_msg_len = mutt_wstr_trunc(msg, 4 * prompt_lines * MuttMessageWindow->cols,
                                      prompt_lines * MuttMessageWindow->cols - answer_string_wid,
                                      NULL);

      if (show_help)
        prompt_lines += 1;

      if (prompt_lines != MuttMessageWindow->rows)
      {
        reflow_message_window_rows(prompt_lines);
        mutt_current_menu_redraw();
      }

      mutt_window_move(MuttMessageWindow, 0, 0);
      SETCOLOR(MT_COLOR_PROMPT);
      if (show_help)
      {
        trunc_help_len = mutt_wstr_trunc(mutt_b2s(help_buffer),
                                         help_buffer->dsize,
                                         MuttMessageWindow->cols, NULL);
        addnstr(mutt_b2s(help_buffer), trunc_help_len);
        mutt_window_clrtoeol(MuttMessageWindow);
        mutt_window_move(MuttMessageWindow, 1, 0);
      }
      addnstr(msg, trunc_msg_len);
      addstr(mutt_b2s(answer_buffer));
      NORMAL_COLOR;
      mutt_window_clrtoeol(MuttMessageWindow);
    }

    mutt_refresh();
    /* SigWinch is not processed unless timeout is set */
    mutt_getch_timeout(30 * 1000);
    ch = mutt_getch();
    mutt_getch_timeout(-1);
    if (ch.ch == -2)
      continue;
    if (CI_is_return(ch.ch))
      break;
    if (ch.ch < 0)
    {
      def = -1;
      break;
    }

#ifdef HAVE_LANGINFO_YESEXPR
    answer[0] = ch.ch;
    if (reyes_ok ?
        (regexec(& reyes, answer, 0, 0, 0) == 0) :
#else
    if (
#endif
        (tolower(ch.ch) == 'y'))
    {
      def = MUTT_YES;
      break;
    }
    else if (
#ifdef HAVE_LANGINFO_YESEXPR
             reno_ok ?
             (regexec(& reno, answer, 0, 0, 0) == 0) :
#endif
             (tolower(ch.ch) == 'n'))
    {
      def = MUTT_NO;
      break;
    }
    else if (show_help_prompt && ch.ch == '?')
    {
      show_help_prompt = 0;
      show_help = 1;
      redraw = 1;

      mutt_buffer_printf(answer_buffer,
                         " ([%s]/%s): ",
                         def == MUTT_YES ? yes : no,
                         def == MUTT_YES ? no : yes);
      answer_string_wid = mutt_strwidth(mutt_b2s(answer_buffer));

      help_buffer = mutt_buffer_pool_get();
      /* L10N:
         In the mutt_yesorno() prompt, some variables and all
         quadoptions provide a '?' choice to provide the name of the
         configuration variable this prompt is dependent on.

         For example, the prompt "Quit Mutt?" is dependent on the
         quadoption $quit.

         Typing '?' at those prompts will print this message above
         the prompt, where %s is the name of the configuration
         variable.
      */
      mutt_buffer_printf(help_buffer, _("See $%s for more information."), var);
    }
    else
    {
      BEEP();
    }
  }

  mutt_buffer_pool_release(&answer_buffer);
  mutt_buffer_pool_release(&help_buffer);

#ifdef HAVE_LANGINFO_YESEXPR
  if (reyes_ok)
    regfree(& reyes);
  if (reno_ok)
    regfree(& reno);
#endif

  if (MuttMessageWindow->rows != 1)
  {
    reflow_message_window_rows(1);
    mutt_current_menu_redraw();
  }
  else
    mutt_window_clearline(MuttMessageWindow, 0);

  if (def != -1)
  {
    mutt_window_mvaddstr(MuttMessageWindow, 0, 0,
                         def == MUTT_YES ? yes : no);
    mutt_refresh();
  }
  else
  {
    /* when the users cancels with ^G, clear the message stored with
     * mutt_message() so it isn't displayed when the screen is refreshed. */
    mutt_clear_error();
  }
  return (def);
}

int mutt_yesorno(const char *msg, int def)
{
  return mutt_yesorno_with_help(msg, def, NULL);
}


/* this function is called when the user presses the abort key */
void mutt_query_exit(void)
{
  mutt_flushinp();
  curs_set(1);
  if (Timeout)
    mutt_getch_timeout(-1); /* restore blocking operation */
  if (mutt_yesorno(_("Exit Mutt?"), MUTT_YES) == MUTT_YES)
  {
    if (!(mutt_background_has_backgrounded() &&
          option(OPTBACKGROUNDCONFIRMQUIT) &&
          mutt_query_boolean(OPTBACKGROUNDCONFIRMQUIT,
              _("There are $background_edit sessions. Really quit Mutt?"),
              MUTT_NO) != MUTT_YES))
    {
      endwin();
      exit(1);
    }
  }
  mutt_clear_error();
  mutt_curs_set(-1);
  SigInt = 0;
}

void mutt_error_history_init(void)
{
  short i;

  if (OldErrorHistSize && ErrorHistory.msg)
  {
    for (i = 0; i < OldErrorHistSize; i++)
      FREE(&ErrorHistory.msg[i]);
    FREE(&ErrorHistory.msg);
  }

  if (ErrorHistSize)
    ErrorHistory.msg = safe_calloc(ErrorHistSize, sizeof(char *));
  ErrorHistory.last = 0;

  OldErrorHistSize = ErrorHistSize;
}

static void error_history_add(const char *s)
{
  static int in_process = 0;

  if (!ErrorHistSize || in_process || !s || !*s)
    return;
  in_process = 1;

  mutt_str_replace(&ErrorHistory.msg[ErrorHistory.last], s);
  if (++ErrorHistory.last >= ErrorHistSize)
    ErrorHistory.last = 0;

  in_process = 0;
}

static void error_history_dump(FILE *f)
{
  short cur;

  cur = ErrorHistory.last;
  do
  {
    if (ErrorHistory.msg[cur])
    {
      fputs(ErrorHistory.msg[cur], f);
      fputc('\n', f);
    }
    if (++cur >= ErrorHistSize)
      cur = 0;
  } while (cur != ErrorHistory.last);
}

void mutt_error_history_display(void)
{
  static int in_process = 0;
  BUFFER *t = NULL;
  FILE *f;

  if (!ErrorHistSize)
  {
    mutt_error _("Error History is disabled.");
    return;
  }

  if (in_process)
  {
    mutt_error _("Error History is currently being shown.");
    return;
  }

  t = mutt_buffer_pool_get();
  mutt_buffer_mktemp(t);
  if ((f = safe_fopen(mutt_b2s(t), "w")) == NULL)
  {
    mutt_perror(mutt_b2s(t));
    goto cleanup;
  }
  error_history_dump(f);
  safe_fclose(&f);

  in_process = 1;
  mutt_do_pager(_("Error History"), mutt_b2s(t), 0, NULL);
  in_process = 0;

cleanup:
  mutt_buffer_pool_release(&t);
}

static void curses_message(int error, const char *fmt, va_list ap)
{
  char scratch[LONG_STRING];

  vsnprintf(scratch, sizeof(scratch), fmt, ap);
  error_history_add(scratch);

  muttdbg(1, "%s", scratch);
  mutt_format_string(Errorbuf, sizeof(Errorbuf),
                     0, MuttMessageWindow->cols, FMT_LEFT, 0, scratch, sizeof(scratch), 0);

  if (!option(OPTKEEPQUIET))
  {
    if (error)
      BEEP();
    SETCOLOR(error ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
    mutt_window_mvaddstr(MuttMessageWindow, 0, 0, Errorbuf);
    NORMAL_COLOR;
    mutt_window_clrtoeol(MuttMessageWindow);
    mutt_refresh();
  }

  if (error)
    set_option(OPTMSGERR);
  else
    unset_option(OPTMSGERR);
}

void mutt_curses_error(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  curses_message(1, fmt, ap);
  va_end(ap);
}

void mutt_curses_message(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  curses_message(0, fmt, ap);
  va_end(ap);
}

void mutt_progress_init(progress_t *progress, const char *msg,
                        unsigned short flags, unsigned short inc,
                        long size)
{
  struct timeval tv = { 0, 0 };

  if (!progress)
    return;
  if (option(OPTNOCURSES))
    return;

  memset(progress, 0, sizeof(progress_t));
  progress->inc = inc;
  progress->flags = flags;
  progress->msg = msg;
  progress->size = size;
  if (progress->size)
  {
    if (progress->flags & MUTT_PROGRESS_SIZE)
      mutt_pretty_size(progress->sizestr, sizeof(progress->sizestr),
                       progress->size);
    else
      snprintf(progress->sizestr, sizeof(progress->sizestr), "%ld",
               progress->size);
  }
  if (!inc)
  {
    if (size)
      mutt_message("%s (%s)", msg, progress->sizestr);
    else
      mutt_message(msg);
    return;
  }
  if (gettimeofday(&tv, NULL) < 0)
    mutt_errno_dbg(1, "gettimeofday failed");
  /* if timestamp is 0 no time-based suppression is done */
  if (TimeInc)
    progress->timestamp_millis = ((unsigned long long) tv.tv_sec * 1000ULL)
      + (unsigned long long) (tv.tv_usec / 1000);
  mutt_progress_update(progress, 0, 0);
}

void mutt_progress_update(progress_t *progress, long pos, int percent)
{
  char posstr[SHORT_STRING];
  short update = 0;
  struct timeval tv = { 0, 0 };
  unsigned long long now_millis = 0;

  if (option(OPTNOCURSES))
    return;

  if (!progress->inc)
    goto out;

  /* refresh if size > inc */
  if (progress->flags & MUTT_PROGRESS_SIZE &&
      (pos >= progress->pos + (progress->inc << 10)))
    update = 1;
  else if (pos >= progress->pos + progress->inc)
    update = 1;

  /* skip refresh if not enough time has passed */
  if (update && progress->timestamp_millis && !gettimeofday(&tv, NULL))
  {
    now_millis = ((unsigned long long) tv.tv_sec * 1000ULL)
      + (unsigned long long) (tv.tv_usec / 1000);
    if (now_millis &&
        (now_millis - progress->timestamp_millis < TimeInc))
      update = 0;
  }

  /* always show the first update */
  if (!pos)
    update = 1;

  if (update)
  {
    if (progress->flags & MUTT_PROGRESS_SIZE)
    {
      pos = pos / (progress->inc << 10) * (progress->inc << 10);
      mutt_pretty_size(posstr, sizeof(posstr), pos);
    }
    else
      snprintf(posstr, sizeof(posstr), "%ld", pos);

    muttdbg(5, "updating progress: %s", posstr);

    progress->pos = pos;
    if (now_millis)
      progress->timestamp_millis = now_millis;

    if (progress->size > 0)
    {
      /* L10N:
       * This is a progress status message, printed as longer-running actions
       * take place to give an update to the user.
       *
       * An example might be:
       * "Fetching message headers... 40/200 (20%)"
       *
       * %1$s - the message: "Fetching message headers..."
       * %2$s - the current position in the action: "40"
       * %3$s - the total number to be completed: "200"
       * %4$d - the percentage complete: 20
       *
       * Note that the various punctuation and the format of the percentage
       * can be adjusted, including the order the parts are displayed in.
       */
      mutt_message(_("%1$s %2$s/%3$s (%4$d%%)"),
                   progress->msg, posstr, progress->sizestr,
                   percent > 0 ? percent :
                   (int) (100.0 * (double) progress->pos / progress->size));
    }
    else
    {
      if (percent > 0)
      {
        /* L10N:
         * This is a progress status message, printed as longer-running actions
         * take place to give an update to the user.
         *
         * An example might be:
         * "Reading =mutt-po... 500 (30%)"
         *
         * %1$s - the message: "Reading =mutt-po..."
         * %2$s - the current position in the action: "500"
         * %3$d - the percentage complete: 30
         *
         * Note that the various punctuation and the format of the percentage
         * can be adjusted, including the order the parts are displayed in.
         */
        mutt_message(_("%1$s %2$s (%3$d%%)"), progress->msg, posstr, percent);
      }
      else
        mutt_message("%s %s", progress->msg, posstr);
    }
  }

out:
  if (pos >= progress->size)
    mutt_clear_error();
}

void mutt_init_windows(void)
{
  MuttHelpWindow = safe_calloc(sizeof(mutt_window_t), 1);
  MuttIndexWindow = safe_calloc(sizeof(mutt_window_t), 1);
  MuttStatusWindow = safe_calloc(sizeof(mutt_window_t), 1);
  MuttMessageWindow = safe_calloc(sizeof(mutt_window_t), 1);
#ifdef USE_SIDEBAR
  MuttSidebarWindow = safe_calloc(sizeof(mutt_window_t), 1);
#endif
}

void mutt_free_windows(void)
{
  FREE(&MuttHelpWindow);
  FREE(&MuttIndexWindow);
  FREE(&MuttStatusWindow);
  FREE(&MuttMessageWindow);
#ifdef USE_SIDEBAR
  FREE(&MuttSidebarWindow);
#endif
}

void mutt_reflow_windows(void)
{
  if (option(OPTNOCURSES))
    return;

  muttdbg(2, "In mutt_reflow_windows");

  MuttStatusWindow->rows = 1;
  MuttStatusWindow->cols = COLS;
  MuttStatusWindow->row_offset = option(OPTSTATUSONTOP) ? 0 : LINES - 2;
  MuttStatusWindow->col_offset = 0;

  memcpy(MuttHelpWindow, MuttStatusWindow, sizeof(mutt_window_t));
  if (! option(OPTHELP))
    MuttHelpWindow->rows = 0;
  else
    MuttHelpWindow->row_offset = option(OPTSTATUSONTOP) ? LINES - 2 : 0;

  memcpy(MuttMessageWindow, MuttStatusWindow, sizeof(mutt_window_t));
  MuttMessageWindow->row_offset = LINES - 1;

  memcpy(MuttIndexWindow, MuttStatusWindow, sizeof(mutt_window_t));
  MuttIndexWindow->rows = MAX(LINES - MuttStatusWindow->rows -
                              MuttHelpWindow->rows - MuttMessageWindow->rows, 0);
  MuttIndexWindow->row_offset = option(OPTSTATUSONTOP) ? MuttStatusWindow->rows :
                                                         MuttHelpWindow->rows;

#ifdef USE_SIDEBAR
  if (option(OPTSIDEBAR))
  {
    memcpy(MuttSidebarWindow, MuttIndexWindow, sizeof(mutt_window_t));
    MuttSidebarWindow->cols = MAX(SidebarWidth, 0);
    /* Ensure the index window has at least one column, to prevent
     * pager regressions. */
    if (MuttSidebarWindow->cols >= MuttIndexWindow->cols)
      MuttSidebarWindow->cols = MuttIndexWindow->cols - 1;

    MuttIndexWindow->cols -= MuttSidebarWindow->cols;
    MuttIndexWindow->col_offset += MuttSidebarWindow->cols;
  }
#endif

  mutt_set_current_menu_redraw_full();
  /* the pager menu needs this flag set to recalc lineInfo */
  mutt_set_current_menu_redraw(REDRAW_FLOW);
}

static void reflow_message_window_rows(int mw_rows)
{
  MuttMessageWindow->rows = mw_rows;
  MuttMessageWindow->row_offset = LINES - mw_rows;

  MuttStatusWindow->row_offset = option(OPTSTATUSONTOP) ? 0 : LINES - mw_rows - 1;

  if (option(OPTHELP))
    MuttHelpWindow->row_offset = option(OPTSTATUSONTOP) ? LINES - mw_rows - 1 : 0;

  MuttIndexWindow->rows = MAX(LINES - MuttStatusWindow->rows -
                              MuttHelpWindow->rows - MuttMessageWindow->rows, 0);

#ifdef USE_SIDEBAR
  if (option(OPTSIDEBAR))
    MuttSidebarWindow->rows = MuttIndexWindow->rows;
#endif

  /* We don't also set REDRAW_FLOW because this function only
   * changes rows and is a temporary adjustment. */
  mutt_set_current_menu_redraw_full();
}

int mutt_window_move(mutt_window_t *win, int row, int col)
{
  return move(win->row_offset + row, win->col_offset + col);
}

int mutt_window_mvaddch(mutt_window_t *win, int row, int col, const chtype ch)
{
  return mvaddch(win->row_offset + row, win->col_offset + col, ch);
}

int mutt_window_mvaddstr(mutt_window_t *win, int row, int col, const char *str)
{
  return mvaddstr(win->row_offset + row, win->col_offset + col, str);
}

#ifdef USE_SLANG_CURSES
static int vw_printw(SLcurses_Window_Type *win, const char *fmt, va_list ap)
{
  char buf[LONG_STRING];

  (void) SLvsnprintf(buf, sizeof(buf), (char *)fmt, ap);
  SLcurses_waddnstr(win, buf, -1);
  return 0;
}
#endif

int mutt_window_mvprintw(mutt_window_t *win, int row, int col, const char *fmt, ...)
{
  va_list ap;
  int rv;

  if ((rv = mutt_window_move(win, row, col)) != ERR)
  {
    va_start(ap, fmt);
    rv = vw_printw(stdscr, fmt, ap);
    va_end(ap);
  }

  return rv;
}

/* Assumes the cursor has already been positioned within the
 * window.
 */
void mutt_window_clrtoeol(mutt_window_t *win)
{
  int row, col, curcol;

  if (win->col_offset + win->cols == COLS)
    clrtoeol();
  else
  {
    getyx(stdscr, row, col);
    curcol = col;
    while (curcol < win->col_offset + win->cols)
    {
      addch(' ');
      curcol++;
    }
    move(row, col);
  }
}

void mutt_window_clearline(mutt_window_t *win, int row)
{
  mutt_window_move(win, row, 0);
  mutt_window_clrtoeol(win);
}

/* Assumes the current position is inside the window.
 * Otherwise it will happily return negative or values outside
 * the window boundaries
 */
void mutt_window_getyx(mutt_window_t *win, int *y, int *x)
{
  int row, col;

  getyx(stdscr, row, col);
  if (y)
    *y = row - win->row_offset;
  if (x)
    *x = col - win->col_offset;
}


void mutt_show_error(void)
{
  if (option(OPTKEEPQUIET))
    return;

  SETCOLOR(option(OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
  mutt_window_mvaddstr(MuttMessageWindow, 0, 0, Errorbuf);
  NORMAL_COLOR;
  mutt_window_clrtoeol(MuttMessageWindow);
}

void mutt_endwin(const char *msg)
{
  int e = errno;

  if (!option(OPTNOCURSES))
  {
    /* at least in some situations (screen + xterm under SuSE11/12) endwin()
     * doesn't properly flush the screen without an explicit call.
     */
    mutt_refresh();
    endwin();
    SigWinch = 1;
  }

  if (msg && *msg)
  {
    puts(msg);
    fflush(stdout);
  }

  errno = e;
}

void mutt_perror(const char *s)
{
  const char *p = strerror(errno);

  mutt_errno_dbg(1, s);
  mutt_error("%s: %s (errno = %d)", s, p ? p : _("unknown error"), errno);
}

int mutt_any_key_to_continue(const char *s)
{
  struct termios t;
  struct termios old;
  int f, ch;

  f = open("/dev/tty", O_RDONLY);
  tcgetattr(f, &t);
  memcpy((void *)&old, (void *)&t, sizeof(struct termios)); /* save original state */
  t.c_lflag &= ~(ICANON | ECHO);
  t.c_cc[VMIN] = 1;
  t.c_cc[VTIME] = 0;
  tcsetattr(f, TCSADRAIN, &t);
  fflush(stdout);
  if (s)
    fputs(s, stdout);
  else
    fputs(_("Press any key to continue..."), stdout);
  fflush(stdout);
  ch = fgetc(stdin);
  fflush(stdin);
  tcsetattr(f, TCSADRAIN, &old);
  close(f);
  fputs("\r\n", stdout);
  mutt_clear_error();
  return (ch);
}

int mutt_do_pager(const char *banner,
                  const char *tempfile,
                  int do_color,
                  pager_t *info)
{
  int rc;

  if (!Pager || mutt_strcmp(Pager, "builtin") == 0)
    rc = mutt_pager(banner, tempfile, do_color, info);
  else
  {
    BUFFER *cmd = NULL;

    cmd = mutt_buffer_pool_get();
    mutt_endwin(NULL);
    mutt_expand_file_fmt(cmd, Pager, tempfile);
    if (mutt_system(mutt_b2s(cmd)) == -1)
    {
      mutt_error(_("Error running \"%s\"!"), mutt_b2s(cmd));
      rc = -1;
    }
    else
      rc = 0;
    mutt_unlink(tempfile);
    mutt_buffer_pool_release(&cmd);
  }

  return rc;
}

static int _enter_fname(const char *prompt, BUFFER *fname, int flags,
                        int multiple, char ***files, int *numfiles)
{
  event_t ch;

  SETCOLOR(MT_COLOR_PROMPT);
  mutt_window_mvaddstr(MuttMessageWindow, 0, 0, prompt);
  addstr(_(" ('?' for list): "));
  NORMAL_COLOR;
  if (mutt_buffer_len(fname))
    addstr(mutt_b2s(fname));
  mutt_window_clrtoeol(MuttMessageWindow);
  mutt_refresh();

  do
  {
    ch = mutt_getch();
  } while (ch.ch == -2);
  if (ch.ch < 0)
  {
    mutt_window_clearline(MuttMessageWindow, 0);
    return (-1);
  }
  else if (ch.ch == '?')
  {
    int sel_flags = 0;

    if (flags & MUTT_MAILBOX)
      sel_flags |= MUTT_SEL_MAILBOX;
    else if (flags & MUTT_FILE)
      sel_flags |= MUTT_SEL_FILE;

    if (multiple)
      sel_flags |= MUTT_SEL_MULTI;

    mutt_refresh();
    mutt_buffer_clear(fname);
    _mutt_buffer_select_file(fname, sel_flags, files, numfiles);
  }
  else
  {
    char *pc;

    safe_asprintf(&pc, "%s: ", prompt);
    mutt_unget_event(ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);

    mutt_buffer_increase_size(fname, LONG_STRING);
    if (_get_field(pc, fname,
                   flags | MUTT_CLEAR,
                   multiple, files, numfiles) != 0)
      mutt_buffer_clear(fname);
    FREE(&pc);
  }

  return 0;
}

int mutt_enter_mailbox(const char *prompt, BUFFER *fname, int do_incoming)
{
  int flags = MUTT_MAILBOX;

  if (do_incoming)
    flags |= MUTT_INCOMING;

  return _enter_fname(prompt, fname, flags, 0, NULL, NULL);
}

int mutt_enter_filename(const char *prompt, BUFFER *fname)
{
  return _enter_fname(prompt, fname, MUTT_FILE, 0, NULL, NULL);
}

int mutt_enter_filenames(const char *prompt, char ***files, int *numfiles)
{
  BUFFER *tmp;
  int rc;

  tmp = mutt_buffer_pool_get();
  rc = _enter_fname(prompt, tmp, MUTT_FILE, 1, files, numfiles);
  mutt_buffer_pool_release(&tmp);
  return rc;
}

void mutt_unget_event(int ch, int op)
{
  event_t tmp;

  tmp.ch = ch;
  tmp.op = op;

  if (UngetCount >= UngetLen)
    safe_realloc(&UngetKeyEvents, (UngetLen += 16) * sizeof(event_t));

  UngetKeyEvents[UngetCount++] = tmp;
}

void mutt_unget_string(char *s)
{
  char *p = s + mutt_strlen(s) - 1;

  while (p >= s)
  {
    mutt_unget_event((unsigned char)*p--, 0);
  }
}

/*
 * Adds the ch/op to the macro buffer.
 * This should be used for macros, push, and exec commands only.
 */
void mutt_push_macro_event(int ch, int op)
{
  event_t tmp;

  tmp.ch = ch;
  tmp.op = op;

  if (MacroBufferCount >= MacroBufferLen)
    safe_realloc(&MacroEvents, (MacroBufferLen += 128) * sizeof(event_t));

  MacroEvents[MacroBufferCount++] = tmp;
}

void mutt_flush_macro_to_endcond(void)
{
  UngetCount = 0;
  while (MacroBufferCount > 0)
  {
    if (MacroEvents[--MacroBufferCount].op == OP_END_COND)
      return;
  }
}

/* Normally, OP_END_COND should only be in the MacroEvent buffer.
 * km_error_key() (ab)uses OP_END_COND as a barrier in the unget
 * buffer, and calls this function to flush. */
void mutt_flush_unget_to_endcond(void)
{
  while (UngetCount > 0)
  {
    if (UngetKeyEvents[--UngetCount].op == OP_END_COND)
      return;
  }
}

void mutt_flushinp(void)
{
  UngetCount = 0;
  MacroBufferCount = 0;
  flushinp();
}

#if (defined(USE_SLANG_CURSES) || defined(HAVE_CURS_SET))
/* The argument can take 3 values:
 * -1: restore the value of the last call
 *  0: make the cursor invisible
 *  1: make the cursor visible
 */
void mutt_curs_set(int cursor)
{
  static int SavedCursor = 1;

  if (cursor < 0)
    cursor = SavedCursor;
  else
    SavedCursor = cursor;

  if (curs_set(cursor) == ERR)
  {
    if (cursor == 1)    /* cnorm */
      curs_set(2);     /* cvvis */
  }
}
#endif

int mutt_multi_choice(char *prompt, char *letters)
{
  event_t ch;
  int choice;
  int redraw = 1, prompt_lines = 1;
  char *p;

  FOREVER
  {
    if (redraw || SigWinch)
    {
      redraw = 0;
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
      if (SigWinch)
      {
        SigWinch = 0;
        mutt_resize_screen();
        clearok(stdscr, TRUE);
        mutt_current_menu_redraw();
      }
#endif
      if (MuttMessageWindow->cols)
      {
        prompt_lines = (mutt_strwidth(prompt) + MuttMessageWindow->cols - 1) /
          MuttMessageWindow->cols;
        prompt_lines = MAX(1, MIN(3, prompt_lines));
      }
      if (prompt_lines != MuttMessageWindow->rows)
      {
        reflow_message_window_rows(prompt_lines);
        mutt_current_menu_redraw();
      }

      SETCOLOR(MT_COLOR_PROMPT);
      mutt_window_mvaddstr(MuttMessageWindow, 0, 0, prompt);
      NORMAL_COLOR;
      mutt_window_clrtoeol(MuttMessageWindow);
    }

    mutt_refresh();
    /* SigWinch is not processed unless timeout is set */
    mutt_getch_timeout(30 * 1000);
    ch  = mutt_getch();
    mutt_getch_timeout(-1);
    if (ch.ch == -2)
      continue;
    /* (ch.ch == 0) is technically possible.  Treat the same as < 0 (abort) */
    if (ch.ch <= 0 || CI_is_return(ch.ch))
    {
      choice = -1;
      break;
    }
    else
    {
      p = strchr(letters, ch.ch);
      if (p)
      {
        choice = p - letters + 1;
        break;
      }
      else if (ch.ch <= '9' && ch.ch > '0')
      {
        choice = ch.ch - '0';
        if (choice <= mutt_strlen(letters))
          break;
      }
    }
    BEEP();
  }
  if (MuttMessageWindow->rows != 1)
  {
    reflow_message_window_rows(1);
    mutt_current_menu_redraw();
  }
  else
    mutt_window_clearline(MuttMessageWindow, 0);
  mutt_refresh();
  return choice;
}

/*
 * addwch would be provided by an up-to-date curses library
 */

int mutt_addwch(wchar_t wc)
{
  char buf[MB_LEN_MAX*2];
  mbstate_t mbstate;
  size_t n1, n2;

  memset(&mbstate, 0, sizeof(mbstate));
  if ((n1 = wcrtomb(buf, wc, &mbstate)) == (size_t)(-1) ||
      (n2 = wcrtomb(buf + n1, 0, &mbstate)) == (size_t)(-1))
    return -1; /* ERR */
  else
    return addstr(buf);
}


/*
 * This formats a string, a bit like
 * snprintf (dest, destlen, "%-*.*s", min_width, max_width, s),
 * except that the widths refer to the number of character cells
 * when printed.
 */

void mutt_format_string(char *dest, size_t destlen,
                        int min_width, int max_width,
                        int justify, char m_pad_char,
                        const char *s, size_t n,
                        int arboreal)
{
  char *p;
  wchar_t wc;
  int w;
  size_t k, k2;
  char scratch[MB_LEN_MAX];
  mbstate_t mbstate1, mbstate2;

  memset(&mbstate1, 0, sizeof(mbstate1));
  memset(&mbstate2, 0, sizeof(mbstate2));
  --destlen;
  p = dest;
  for (; n && (k = mbrtowc(&wc, s, n, &mbstate1)); s += k, n -= k)
  {
    if (k == (size_t)(-1) || k == (size_t)(-2))
    {
      if (k == (size_t)(-1) && errno == EILSEQ)
        memset(&mbstate1, 0, sizeof(mbstate1));

      k = (k == (size_t)(-1)) ? 1 : n;
      wc = replacement_char();
    }
    if (arboreal && wc < MUTT_TREE_MAX)
      w = 1; /* hack */
    else
    {
      if (iswblank(wc))
        wc = ' ';
      else
        if (!IsWPrint(wc))
          wc = '?';
      w = wcwidth(wc);
    }
    if (w >= 0)
    {
      if (w > max_width || (k2 = wcrtomb(scratch, wc, &mbstate2)) > destlen)
        break;
      min_width -= w;
      max_width -= w;
      strncpy(p, scratch, k2);
      p += k2;
      destlen -= k2;
    }
  }
  w = (int)destlen < min_width ? destlen : min_width;
  if (w <= 0)
    *p = '\0';
  else if (justify == FMT_RIGHT)        /* right justify */
  {
    p[w] = '\0';
    while (--p >= dest)
      p[w] = *p;
    while (--w >= 0)
      dest[w] = m_pad_char;
  }
  else if (justify == FMT_CENTER)       /* center */
  {
    char *savedp = p;
    int half = (w+1) / 2; /* half of cushion space */

    p[w] = '\0';

    /* move str to center of buffer */
    while (--p >= dest)
      p[half] = *p;

    /* fill rhs */
    p = savedp + half;
    while (--w >= half)
      *p++ = m_pad_char;

    /* fill lhs */
    while (half--)
      dest[half] = m_pad_char;
  }
  else                                  /* left justify */
  {
    while (--w >= 0)
      *p++ = m_pad_char;
    *p = '\0';
  }
}

/*
 * This formats a string rather like
 *   snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
 *   snprintf (dest, destlen, fmt, s);
 * except that the numbers in the conversion specification refer to
 * the number of character cells when printed.
 */

static void mutt_format_s_x(char *dest,
                            size_t destlen,
                            const char *prefix,
                            const char *s,
                            int arboreal)
{
  int justify = FMT_RIGHT;
  char *p;
  int min_width;
  int max_width = INT_MAX;

  if (*prefix == '-')
    ++prefix, justify = FMT_LEFT;
  else if (*prefix == '=')
    ++prefix, justify = FMT_CENTER;
  min_width = strtol(prefix, &p, 10);
  if (*p == '.')
  {
    prefix = p + 1;
    max_width = strtol(prefix, &p, 10);
    if (p <= prefix)
      max_width = INT_MAX;
  }

  mutt_format_string(dest, destlen, min_width, max_width,
                     justify, ' ', s, mutt_strlen(s), arboreal);
}

void mutt_format_s(char *dest,
                   size_t destlen,
                   const char *prefix,
                   const char *s)
{
  mutt_format_s_x(dest, destlen, prefix, s, 0);
}

void mutt_format_s_tree(char *dest,
                        size_t destlen,
                        const char *prefix,
                        const char *s)
{
  mutt_format_s_x(dest, destlen, prefix, s, 1);
}

/*
 * mutt_paddstr (n, s) is almost equivalent to
 * mutt_format_string (bigbuf, big, n, n, FMT_LEFT, ' ', s, big, 0), addstr (bigbuf)
 */

void mutt_paddstr(int n, const char *s)
{
  wchar_t wc;
  int w;
  size_t k;
  size_t len = mutt_strlen(s);
  mbstate_t mbstate;

  memset(&mbstate, 0, sizeof(mbstate));
  for (; len && (k = mbrtowc(&wc, s, len, &mbstate)); s += k, len -= k)
  {
    if (k == (size_t)(-1) || k == (size_t)(-2))
    {
      if (k == (size_t) (-1))
        memset(&mbstate, 0, sizeof(mbstate));
      k = (k == (size_t)(-1)) ? 1 : len;
      wc = replacement_char();
    }
    if (!IsWPrint(wc))
      wc = '?';
    w = wcwidth(wc);
    if (w >= 0)
    {
      if (w > n)
        break;
      mutt_addwch(wc);
      n -= w;
    }
  }
  while (n-- > 0)
    addch(' ');
}

/* See how many bytes to copy from string so its at most maxlen bytes
 * long and maxwid columns wide */
size_t mutt_wstr_trunc(const char *src, size_t maxlen, size_t maxwid, size_t *width)
{
  wchar_t wc;
  size_t n, w = 0, l = 0, cl;
  int cw;
  mbstate_t mbstate;

  if (!src)
    goto out;

  n = mutt_strlen(src);

  memset(&mbstate, 0, sizeof(mbstate));
  for (w = 0; n && (cl = mbrtowc(&wc, src, n, &mbstate)); src += cl, n -= cl)
  {
    if (cl == (size_t)(-1) || cl == (size_t)(-2))
    {
      if (cl == (size_t)(-1))
        memset(&mbstate, 0, sizeof(mbstate));
      cl = (cl == (size_t)(-1)) ? 1 : n;
      wc = replacement_char();
    }
    cw = wcwidth(wc);
    /* hack because MUTT_TREE symbols aren't turned into characters
     * until rendered by print_enriched_string (#3364) */
    if (cw < 0 && cl == 1 && src[0] && src[0] < MUTT_TREE_MAX)
      cw = 1;
    else if (cw < 0)
      cw = 0;                   /* unprintable wchar */
    if (cl + l > maxlen || cw + w > maxwid)
      break;
    l += cl;
    w += cw;
  }
out:
  if (width)
    *width = w;
  return l;
}

/*
 * returns the number of bytes the first (multibyte) character
 * of input consumes:
 *      < 0 ... conversion error
 *      = 0 ... end of input
 *      > 0 ... length (bytes)
 */
int mutt_charlen(const char *s, int *width)
{
  wchar_t wc;
  mbstate_t mbstate;
  size_t k, n;

  if (!s || !*s)
    return 0;

  n = mutt_strlen(s);
  memset(&mbstate, 0, sizeof(mbstate));
  k = mbrtowc(&wc, s, n, &mbstate);
  if (width)
    *width = wcwidth(wc);
  return (k == (size_t)(-1) || k == (size_t)(-2)) ? -1 : k;
}

/*
 * mutt_strwidth is like mutt_strlen except that it returns the width
 * referring to the number of character cells.
 */

int mutt_strwidth(const char *s)
{
  wchar_t wc;
  int w;
  size_t k, n;
  mbstate_t mbstate;

  if (!s) return 0;

  n = mutt_strlen(s);

  memset(&mbstate, 0, sizeof(mbstate));
  for (w=0; n && (k = mbrtowc(&wc, s, n, &mbstate)); s += k, n -= k)
  {
    if (k == (size_t)(-1) || k == (size_t)(-2))
    {
      if (k == (size_t)(-1))
        memset(&mbstate, 0, sizeof(mbstate));
      k = (k == (size_t)(-1)) ? 1 : n;
      wc = replacement_char();
    }
    if (!IsWPrint(wc))
      wc = '?';
    w += wcwidth(wc);
  }
  return w;
}
