/*
 * Copyright notice from original mutt:
 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
 * Copyright (C) 2004 g10 Code GmbH
 *
 * Parts were written/modified by:
 * Nico Golde <nico@ngolde.de>
 *
 * This file is part of mutt-ng, see http://www.muttng.org/.
 * It's licensed under the GNU General Public License,
 * please see the file GPL in the top level source directory.
 */

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

#include "mutt.h"
#include "enter.h"
#include "mutt_menu.h"
#include "mutt_curses.h"
#include "pager.h"
#include "mbyte.h"

#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/str.h"
#include "lib/debug.h"

#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>

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

/* 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.
 */
size_t UngetCount = 0;
static size_t UngetBufLen = 0;
static event_t *KeyEvent;

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 (UngetCount && !option (OPTFORCEREFRESH))
    return;

  /* else */
  refresh ();
}

/* Make sure that the next refresh does a full refresh.  This could be
   optmized 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)
{
  if (!getenv ("DISPLAY")) {
    keypad (stdscr, TRUE);
    clearok (stdscr, TRUE);
    set_option (OPTNEEDREDRAW);
  }
}

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

  if (!option (OPTUNBUFFEREDINPUT) && UngetCount)
    return (KeyEvent[--UngetCount]);

  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 */
    ch = getch ();
  mutt_allow_interrupt (0);

  if (SigInt)
    mutt_query_exit ();

  if (ch == ERR)
    return err;

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

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

int _mutt_get_field ( /* const */ char *field, char *buf, size_t buflen,
                     int complete, int multiple, char ***files, int *numfiles)
{
  int ret;
  int x, y;

  ENTER_STATE *es = mutt_new_enter_state ();

  do {
    CLEARLINE (LINES - 1);
    addstr (field);
    mutt_refresh ();
    getyx (stdscr, y, x);
    ret =
      _mutt_enter_string (buf, buflen, y, x, complete, multiple, files,
                          numfiles, es);
  }
  while (ret == 1);
  CLEARLINE (LINES - 1);
  mutt_free_enter_state (&es);

  return (ret);
}

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

  set_option (OPTUNBUFFEREDINPUT);
  rc = mutt_get_field (msg, buf, buflen, flags);
  unset_option (OPTUNBUFFEREDINPUT);

  return (rc);
}

void mutt_clear_error (void)
{
  Errorbuf[0] = 0;
  if (!option (OPTNOCURSES))
    CLEARLINE (LINES - 1);
}

void mutt_edit_file (const char *editor, const char *data)
{
  char cmd[LONG_STRING];

  mutt_endwin (NULL);
  mutt_expand_file_fmt (cmd, sizeof (cmd), editor, data);
  if (mutt_system (cmd) == -1)
    mutt_error (_("Error running \"%s\"!"), cmd);
  keypad (stdscr, TRUE);
  clearok (stdscr, TRUE);
}

int mutt_yesorno (const char *msg, int def)
{
  event_t ch;
  char *yes = _("yes");
  char *no = _("no");
  char *answer_string;
  size_t answer_string_len;

#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 | REG_EXTENDED);
  reno_ok = (expr = nl_langinfo (NOEXPR)) && expr[0] == '^' &&
    !regcomp (&reno, expr, REG_NOSUB | REG_EXTENDED);
#endif

  CLEARLINE (LINES - 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_string = mem_malloc (COLS + 1);
  snprintf (answer_string, COLS + 1, " ([%s]/%s): ", def == M_YES ? yes : no,
            def == M_YES ? no : yes);
  answer_string_len = str_len (answer_string);
  printw ("%.*s%s", COLS - answer_string_len, msg, answer_string);
  mem_free (&answer_string);

  FOREVER {
    mutt_refresh ();
    ch = mutt_getch ();
    if (CI_is_return (ch.ch))
      break;
    if (ch.ch == -1) {
      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) == *yes)) {
      def = M_YES;
      break;
    }
    else if (
#ifdef HAVE_LANGINFO_YESEXPR
              reno_ok ? (regexec (&reno, answer, 0, 0, 0) == 0) :
#endif
              (tolower (ch.ch) == *no)) {
      def = M_NO;
      break;
    }
    else {
      BEEP ();
    }
  }

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

  if (def != -1) {
    addstr ((char *) (def == M_YES ? yes : no));
    mutt_refresh ();
  }
  return (def);
}

/* this function is called when the user presses the abort key */
void mutt_query_exit (void)
{
  mutt_flushinp ();
  curs_set (1);
  if (Timeout)
    timeout (-1);               /* restore blocking operation */
  if (mutt_yesorno (_("Exit Mutt-ng?"), M_YES) == M_YES) {
    mutt_endwin (NULL);
    exit (1);
  }
  mutt_clear_error ();
  mutt_curs_set (-1);
  SigInt = 0;
}

void mutt_curses_error (const char *fmt, ...)
{
  char TmpErrorbuf[STRING];
  va_list ap;

  va_start (ap, fmt);
  vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
  va_end (ap);

  debug_print (1, ("%s\n", Errorbuf));
  mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
                      0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
  snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */

  if (!option (OPTKEEPQUIET)) {
    BEEP ();
    SETCOLOR (MT_COLOR_ERROR);
    mvaddstr (LINES - 1, 0, Errorbuf);
    clrtoeol ();
    SETCOLOR (MT_COLOR_NORMAL);
    mutt_refresh ();
  }

  set_option (OPTMSGERR);
}

#ifdef USE_SOCKET
void mutt_progress_bar (progress_t* progress, long pos) {
  char posstr[SHORT_STRING];

  if (!pos) {
    if (!NetInc)
      mutt_message (progress->msg);
    else {
      if (progress->size)
        mutt_pretty_size (progress->sizestr, sizeof (progress->sizestr),
                          progress->size);
      progress->pos = 0;
    }
  }

  if (!NetInc)
    return;

  if (pos >= progress->pos + (NetInc << 10)) {
    progress->pos = pos;
    pos = pos / (NetInc << 10) * (NetInc << 10);
    mutt_pretty_size (posstr, sizeof (posstr), pos);
    if (progress->size)
      mutt_message ("%s %s/%s", progress->msg, posstr, progress->sizestr);
    else
      mutt_message ("%s %s", progress->msg, posstr);
  }
}
#endif

void mutt_curses_message (const char *fmt, ...)
{
  char TmpErrorbuf[STRING];
  va_list ap;

  va_start (ap, fmt);
  vsnprintf (Errorbuf, sizeof (Errorbuf), fmt, ap);
  va_end (ap);

  mutt_format_string (TmpErrorbuf, sizeof (TmpErrorbuf),
                      0, COLS - 2, 0, 0, Errorbuf, sizeof (Errorbuf), 0);
  snprintf (Errorbuf, sizeof (Errorbuf), "%s", TmpErrorbuf);    /* overkill */

  if (!option (OPTKEEPQUIET)) {
    SETCOLOR (MT_COLOR_MESSAGE);
    mvaddstr (LINES - 1, 0, Errorbuf);
    clrtoeol ();
    SETCOLOR (MT_COLOR_NORMAL);
    mutt_refresh ();
  }

  unset_option (OPTMSGERR);
}

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

  SETCOLOR (option (OPTMSGERR) ? MT_COLOR_ERROR : MT_COLOR_MESSAGE);
  CLEARLINE (LINES - 1);
  addstr (Errorbuf);
  SETCOLOR (MT_COLOR_NORMAL);
}

void mutt_endwin (const char *msg)
{
  if (!option (OPTNOCURSES)) {
    CLEARLINE (LINES - 1);

    attrset (A_NORMAL);
    mutt_refresh ();
    endwin ();
  }

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

void _mutt_perror (const char *s, const char* filename, int line)
{
  char *p = strerror (errno);

  debug_print (1, ("%s: %s (errno = %d)\n", s, p ? p : "unknown error", errno));
  mutt_error ("%s: %s (errno = %d) from %s:%i", s, p ? p : _("unknown error"), errno, filename, line);
}

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 || str_cmp (Pager, "builtin") == 0)
    rc = mutt_pager (banner, tempfile, do_color, info);
  else {
    char cmd[STRING];

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

  return rc;
}

int _mutt_enter_fname (const char *prompt, char *buf, size_t blen,
                       int *redraw, int buffy, int multiple, char ***files,
                       int *numfiles)
{
  event_t ch;

  mvaddstr (LINES - 1, 0, (char *) prompt);
  addstr (_(" ('?' for list): "));
  if (buf[0])
    addstr (buf);
  clrtoeol ();
  mutt_refresh ();

  ch = mutt_getch ();
  if (ch.ch == -1) {
    CLEARLINE (LINES - 1);
    return (-1);
  }
  else if (ch.ch == '?') {
    mutt_refresh ();
    buf[0] = 0;
    _mutt_select_file (buf, blen, M_SEL_FOLDER | (multiple ? M_SEL_MULTI : 0),
                       files, numfiles);
    *redraw = REDRAW_FULL;
  }
  else {
    char *pc = mem_malloc (str_len (prompt) + 3);

    sprintf (pc, "%s: ", prompt);       /* __SPRINTF_CHECKED__ */
    mutt_ungetch (ch.op ? 0 : ch.ch, ch.op ? ch.op : 0);
    if (_mutt_get_field
        (pc, buf, blen, (buffy ? M_EFILE : M_FILE) | M_CLEAR, multiple, files,
         numfiles)
        != 0)
      buf[0] = 0;
    MAYBE_REDRAW (*redraw);
    mem_free (&pc);
  }

  return 0;
}

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

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

  if (UngetCount >= UngetBufLen)
    mem_realloc (&KeyEvent, (UngetBufLen += 128) * sizeof (event_t));

  KeyEvent[UngetCount++] = tmp;
}

void mutt_flushinp (void)
{
  UngetCount = 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;
  char *p;

  mvaddstr (LINES - 1, 0, prompt);
  clrtoeol ();
  FOREVER {
    mutt_refresh ();
    ch = mutt_getch ();
    if (ch.ch == -1 || 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 <= str_len (letters))
          break;
      }
    }
    BEEP ();
  }
  CLEARLINE (LINES - 1);
  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 right_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)) {
      k = (k == (size_t) (-1)) ? 1 : n;
      wc = replacement_char ();
    }
    if (arboreal && wc < M_TREE_MAX)
      w = 1;                    /* hack */
    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 (right_justify) {
    p[w] = '\0';
    while (--p >= dest)
      p[w] = *p;
    while (--w >= 0)
      dest[w] = m_pad_char;
  }
  else {
    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 right_justify = 1;
  char *p;
  int min_width;
  int max_width = INT_MAX;

  if (*prefix == '-')
    ++prefix, right_justify = 0;
  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,
                      right_justify, ' ', s, str_len (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, 0, ' ', s, big, 0), addstr (bigbuf)
 */

void mutt_paddstr (int n, const char *s)
{
  wchar_t wc;
  int w;
  size_t k;
  size_t len = str_len (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)) {
      k = (k == (size_t) (-1)) ? 1 : len;
      wc = replacement_char ();
    }
    if (!IsWPrint (wc))
      wc = '?';
    w = wcwidth (wc);
    if (w >= 0) {
      if (w > n)
        break;
      addnstr ((char *) s, k);
      n -= w;
    }
  }
  while (n-- > 0)
    addch (' ');
}

/*
 * mutt_strwidth is like str_len except that it returns the width
 * refering to the number of characters cells.
 */

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

  if (!s)
    return 0;

  n = str_len (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)) {
      k = (k == (size_t) (-1)) ? 1 : n;
      wc = replacement_char ();
    }
    if (!IsWPrint (wc))
      wc = '?';
    w += wcwidth (wc);
  }
  return w;
}


syntax highlighted by Code2HTML, v. 0.9.1