/****************************************************************************
 * apps/graphics/pdcurses/pdc_getch.c
 * Public Domain Curses
 * RCSID("$Id: getch.c,v 1.72 2008/07/13 16:08:18 wmcbrine Exp $")
 *
 *   Copyright (C) 2017 Gregory Nutt. All rights reserved.
 *   Adapted by: Gregory Nutt <gnutt@nuttx.org>
 *
 * Adapted from the original public domain pdcurses by Gregory Nutt and
 * released as part of NuttX under the 3-clause BSD license:
 *
 * 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. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 THE
 * COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *
 ****************************************************************************/

/* Name: getch
 *
 * Synopsis:
 *       int getch(void);
 *       int wgetch(WINDOW *win);
 *       int mvgetch(int y, int x);
 *       int mvwgetch(WINDOW *win, int y, int x);
 *       int ungetch(int ch);
 *       int flushinp(void);
 *
 *       int get_wch(wint_t *wch);
 *       int wget_wch(WINDOW *win, wint_t *wch);
 *       int mvget_wch(int y, int x, wint_t *wch);
 *       int mvwget_wch(WINDOW *win, int y, int x, wint_t *wch);
 *       int unget_wch(const wchar_t wch);
 *
 *       unsigned long PDC_get_key_modifiers(void);
 *       int PDC_save_key_modifiers(bool flag);
 *       int PDC_return_key_modifiers(bool flag);
 *
 * Description:
 *       With the getch(), wgetch(), mvgetch(), and mvwgetch() functions,
 *       a character is read from the terminal associated with the window.
 *       In nodelay mode, if there is no input waiting, the value ERR is
 *       returned. In delay mode, the program will hang until the system
 *       passes text through to the program. Depending on the setting of
 *       cbreak(), this will be after one character or after the first
 *       newline.  Unless noecho() has been set, the character will also
 *       be echoed into the designated window.
 *
 *       If keypad() is true, and a function key is pressed, the token for
 *       that function key will be returned instead of the raw characters.
 *       Possible function keys are defined in curses.h with integers
 *       beginning with 0401, whose names begin with KEY_.
 *
 *       If nodelay(win, true) has been called on the window and no input
 *       is waiting, the value ERR is returned.
 *
 *       ungetch() places ch back onto the input queue to be returned by
 *       the next call to wgetch().
 *
 *       flushinp() throws away any type-ahead that has been typed by the
 *       user and has not yet been read by the program.
 *
 *       PDC_get_key_modifiers() returns the keyboard modifiers (shift,
 *       control, alt, numlock) effective at the time of the last getch()
 *       call, if PDC_save_key_modifiers(true) has been called before the
 *       getch(). Use the macros PDC_KEY_MODIFIER_* to determine which
 *       modifier(s) were set. PDC_return_key_modifiers() tells getch()
 *       to return modifier keys pressed alone as keystrokes (KEY_ALT_L,
 *       etc.). These may not work on all platforms.
 *
 *       NOTE: getch() and ungetch() are implemented as macros, to avoid
 *       conflict with many DOS compiler's runtime libraries.
 *
 * Return Value:
 *       These functions return ERR or the value of the character, meta
 *       character or function key token.
 *
 * Portability                                X/Open    BSD    SYS V
 *       getch                                   Y       Y       Y
 *       wgetch                                  Y       Y       Y
 *       mvgetch                                 Y       Y       Y
 *       mvwgetch                                Y       Y       Y
 *       ungetch                                 Y       Y       Y
 *       flushinp                                Y       Y       Y
 *       get_wch                                 Y
 *       wget_wch                                Y
 *       mvget_wch                               Y
 *       mvwget_wch                              Y
 *       unget_wch                               Y
 *       PDC_get_key_modifiers                   -       -       -
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "curspriv.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define _INBUFSIZ   512         /* size of terminal input buffer */
#define NUNGETCH    256         /* max # chars to ungetch() */

/****************************************************************************
 * Private Data
 ****************************************************************************/

static int c_pindex = 0;        /* putter index */
static int c_gindex = 1;        /* getter index */
static int c_ungind = 0;        /* ungetch() push index */
static int c_ungch[NUNGETCH];   /* array of ungotten chars */

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int _mouse_key(WINDOW *win)
{
  unsigned long mbe = SP->_trap_mbe;
  int key = KEY_MOUSE;
  int i;

  /* Filter unwanted mouse events */

  for (i = 0; i < 3; i++)
    {
      if (pdc_mouse_status.changes & (1 << i))
        {
          int shf = i * 5;
          short button = pdc_mouse_status.button[i] & BUTTON_ACTION_MASK;

          if ((!(mbe & (BUTTON1_PRESSED << shf)) &&
                (button == BUTTON_PRESSED)) ||
              (!(mbe & (BUTTON1_CLICKED << shf)) &&
                (button == BUTTON_CLICKED)) ||
              (!(mbe & (BUTTON1_DOUBLE_CLICKED << shf)) &&
                (button == BUTTON_DOUBLE_CLICKED)) ||
              (!(mbe & (BUTTON1_MOVED << shf)) &&
                (button == BUTTON_MOVED)) ||
              (!(mbe & (BUTTON1_RELEASED << shf)) &&
                (button == BUTTON_RELEASED)))
            {
              pdc_mouse_status.changes ^= (1 << i);
            }
        }
    }

  if (pdc_mouse_status.changes & PDC_MOUSE_MOVED)
    {
      if (!(mbe & (BUTTON1_MOVED | BUTTON2_MOVED | BUTTON3_MOVED)))
        {
          pdc_mouse_status.changes ^= PDC_MOUSE_MOVED;
        }
    }

  if (pdc_mouse_status.changes & (PDC_MOUSE_WHEEL_UP | PDC_MOUSE_WHEEL_DOWN))
    {
      if (!(mbe & MOUSE_WHEEL_SCROLL))
        {
          pdc_mouse_status.changes &=
            ~(PDC_MOUSE_WHEEL_UP | PDC_MOUSE_WHEEL_DOWN);
        }
    }

  if (!pdc_mouse_status.changes)
    {
      return -1;
    }

  /* Check for click in slk area */

  i = PDC_mouse_in_slk(pdc_mouse_status.y, pdc_mouse_status.x);

  if (i)
    {
      if (pdc_mouse_status.button[0] & (BUTTON_PRESSED | BUTTON_CLICKED))
        {
          key = KEY_F(i);
        }
      else
        {
          key = -1;
        }
    }

  return key;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int wgetch(WINDOW *win)
{
  static int buffer[_INBUFSIZ]; /* character buffer */
  int key, waitcount;

  PDC_LOG(("wgetch() - called\n"));

  if (!win)
    {
      return ERR;
    }

  waitcount = 0;

  /* Set the number of 1/20th second napms() calls */

  if (SP->delaytenths)
    {
      waitcount = 2 * SP->delaytenths;
    }
  else if (win->_delayms)
    {
      /* Can't really do millisecond intervals, so delay in 1/20ths of a
       * second (50ms).
       */

      waitcount = win->_delayms / 50;
      if (!waitcount)
        {
          waitcount = 1;
        }
    }

  /* refresh window when wgetch is called if there have been changes to it
   * and it is not a pad.
   */

  if (!(win->_flags & _PAD) &&
      ((!win->_leaveit &&
       (win->_begx + win->_curx != SP->curscol ||
        win->_begy + win->_cury != SP->cursrow)) ||
       is_wintouched(win)))
    {
      wrefresh(win);
    }

  /* if ungotten char exists, remove and return it */

  if (c_ungind)
    {
      return c_ungch[--c_ungind];
    }

  /* if normal and data in buffer */

  if ((!SP->raw_inp && !SP->cbreak) && (c_gindex < c_pindex))
    {
      return buffer[c_gindex++];
    }

  /* prepare to buffer data */

  c_pindex = 0;
  c_gindex = 0;

  /* to get here, no keys are buffered. go and get one. */

  for (;;)                      /* loop for any buffering */
    {
      /* is there a keystroke ready? */

      if (!PDC_check_key())
        {
          /* if not, handle timeout() and halfdelay() */

          if (SP->delaytenths || win->_delayms)
            {
              if (!waitcount)
                {
                  return ERR;
                }

              waitcount--;
            }
          else if (win->_nodelay)
            {
              return ERR;
            }

          napms(50);            /* Sleep for 1/20th second */
          continue;             /* Then check again */
        }

      /* If there is, fetch it */

      key = PDC_get_key();

      if (SP->key_code)
        {
          /* Filter special keys if not in keypad mode */

          if (!win->_use_keypad)
            {
              key = -1;
            }

          /* Filter mouse events; translate mouse clicks in the slk area to
           * function keys.
           */

          else if (key == KEY_MOUSE)
            {
              key = _mouse_key(win);
            }
        }

      /* Unwanted key? loop back */

      if (key == -1)
        {
          continue;
        }

      /* Translate CR */

      if (key == '\r' && SP->autocr && !SP->raw_inp)
        {
          key = '\n';
        }

      /* If echo is enabled */

      if (SP->echo && !SP->key_code)
        {
          waddch(win, key);
          wrefresh(win);
        }

      /* If no buffering */

      if (SP->raw_inp || SP->cbreak)
        {
          return key;
        }

      /* If no overflow, put data in buffer */

      if (key == '\b')
        {
          if (c_pindex > c_gindex)
            {
              c_pindex--;
            }
        }
      else if (c_pindex < _INBUFSIZ - 2)
        {
          buffer[c_pindex++] = key;
        }

      /* If we got a line */

      if (key == '\n' || key == '\r')
        {
          return buffer[c_gindex++];
        }
    }
}

int mvgetch(int y, int x)
{
  PDC_LOG(("mvgetch() - called\n"));

  if (move(y, x) == ERR)
    {
      return ERR;
    }

  return wgetch(stdscr);
}

int mvwgetch(WINDOW *win, int y, int x)
{
  PDC_LOG(("mvwgetch() - called\n"));

  if (wmove(win, y, x) == ERR)
    {
      return ERR;
    }

  return wgetch(win);
}

int PDC_ungetch(int ch)
{
  PDC_LOG(("ungetch() - called\n"));

  if (c_ungind >= NUNGETCH)     /* Pushback stack full */
    {
      return ERR;
    }

  c_ungch[c_ungind++] = ch;

  return OK;
}

int flushinp(void)
{
  PDC_LOG(("flushinp() - called\n"));

  PDC_flushinp();

  c_gindex = 1;                 /* Set indices to kill buffer */
  c_pindex = 0;
  c_ungind = 0;                 /* Clear c_ungch array */

  return OK;
}

unsigned long PDC_get_key_modifiers(void)
{
  PDC_LOG(("PDC_get_key_modifiers() - called\n"));

  return pdc_key_modifiers;
}

int PDC_save_key_modifiers(bool flag)
{
  PDC_LOG(("PDC_save_key_modifiers() - called\n"));

  SP->save_key_modifiers = flag;
  return OK;
}

int PDC_return_key_modifiers(bool flag)
{
  PDC_LOG(("PDC_return_key_modifiers() - called\n"));

  SP->return_key_modifiers = flag;
  return PDC_modifiers_set();
}

#ifdef CONFIG_PDCURSES_WIDE
int wget_wch(WINDOW *win, wint_t *wch)
{
  int key;

  PDC_LOG(("wget_wch() - called\n"));

  if (!wch)
    {
      return ERR;
    }

  key = wgetch(win);

  if (key == ERR)
    {
     return ERR;
    }

  *wch = key;

  return SP->key_code ? KEY_CODE_YES : OK;
}

int get_wch(wint_t *wch)
{
  PDC_LOG(("get_wch() - called\n"));

  return wget_wch(stdscr, wch);
}

int mvget_wch(int y, int x, wint_t *wch)
{
  PDC_LOG(("mvget_wch() - called\n"));

  if (move(y, x) == ERR)
    {
      return ERR;
    }

  return wget_wch(stdscr, wch);
}

int mvwget_wch(WINDOW *win, int y, int x, wint_t *wch)
{
  PDC_LOG(("mvwget_wch() - called\n"));

  if (wmove(win, y, x) == ERR)
    {
      return ERR;
    }

  return wget_wch(win, wch);
}

int unget_wch(const wchar_t wch)
{
  return PDC_ungetch(wch);
}
#endif
