/****************************************************************************
 * apps/graphics/pdcurses/pdc_mouse.c
 * Public Domain Curses
 * RCSID("$Id: mouse.c,v 1.45 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: mouse
 *
 * Synopsis:
 *       int mouse_set(unsigned long mbe);
 *       int mouse_on(unsigned long mbe);
 *       int mouse_off(unsigned long mbe);
 *       int request_mouse_pos(void);
 *       int map_button(unsigned long button);
 *       void wmouse_position(WINDOW *win, int *y, int *x);
 *       unsigned long getmouse(void);
 *       unsigned long getbmap(void);
 *
 *       int mouseinterval(int wait);
 *       bool wenclose(const WINDOW *win, int y, int x);
 *       bool wmouse_trafo(const WINDOW *win, int *y, int *x, bool to_screen);
 *       bool mouse_trafo(int *y, int *x, bool to_screen);
 *       mmask_t mousemask(mmask_t mask, mmask_t *oldmask);
 *       int nc_getmouse(MEVENT *event);
 *       int ungetmouse(MEVENT *event);
 *
 * Description:
 *       As of PDCurses 3.0, there are two separate mouse interfaces: the
 *       classic interface, which is based on the undocumented Sys V
 *       mouse functions; and an ncurses-compatible interface. Both are
 *       active at all times, and you can mix and match functions from
 *       each, though it's not recommended. The ncurses interface is
 *       essentially an emulation layer built on top of the classic
 *       interface; it's here to allow easier porting of ncurses apps.
 *
 *       The classic interface: mouse_set(), mouse_on(), mouse_off(),
 *       request_mouse_pos(), map_button(), wmouse_position(),
 *       getmouse(), and getbmap(). An application using this interface
 *       would start by calling mouse_set() or mouse_on() with a non-zero
 *       value, often ALL_MOUSE_EVENTS. Then it would check for a
 *       KEY_MOUSE return from getch(). If found, it would call
 *       request_mouse_pos() to get the current mouse status.
 *
 *       mouse_set(), mouse_on() and mouse_off() are analagous to
 *       attrset(), attron() and attroff().  These functions set the
 *       mouse button events to trap.  The button masks used in these
 *       functions are defined in curses.h and can be or'ed together.
 *       They are the group of masks starting with BUTTON1_RELEASED.
 *
 *       request_mouse_pos() requests curses to fill in the Mouse_status
 *       structure with the current state of the mouse.
 *
 *       map_button() enables the specified mouse action to activate the
 *       Soft Label Keys if the action occurs over the area of the screen
 *       where the Soft Label Keys are displayed.  The mouse actions are
 *       defined in curses.h in the group that starts with BUTTON_RELEASED.
 *
 *       wmouse_position() determines if the current mouse position is
 *       within the window passed as an argument.  If the mouse is
 *       outside the current window, -1 is returned in the y and x
 *       arguments; otherwise the y and x coordinates of the mouse
 *       (relative to the top left corner of the window) are returned in
 *       y and x.
 *
 *       getmouse() returns the current status of the trapped mouse
 *       buttons as set by mouse_set() or mouse_on().
 *
 *       getbmap() returns the current status of the button action used
 *       to map a mouse action to the Soft Label Keys as set by the
 *       map_button() function.
 *
 *       The ncurses interface: mouseinterval(), wenclose(),
 *       wmouse_trafo(), mouse_trafo(), mousemask(), nc_getmouse(), and
 *       ungetmouse(). A typical application using this interface would
 *       start by calling mousemask() with a non-zero value, often
 *       ALL_MOUSE_EVENTS. Then it would check for a KEY_MOUSE return
 *       from getch(). If found, it would call nc_getmouse() to get the
 *       current mouse status.
 *
 *       mouseinterval() sets the timeout for a mouse click. On all
 *       current platforms, PDCurses receives mouse button press and
 *       release events, but must synthesize click events. It does this
 *       by checking whether a release event is queued up after a press
 *       event. If it gets a press event, and there are no more events
 *       waiting, it will wait for the timeout interval, then check again
 *       for a release. A press followed by a release is reported as
 *       BUTTON_CLICKED; otherwise it's passed through as BUTTON_PRESSED.
 *       The default timeout is 150ms; valid values are 0 (no clicks
 *       reported) through 1000ms. In x11, the timeout can also be set
 *       via the clickPeriod resource. The return value from
 *       mouseinterval() is the old timeout. To check the old value
 *       without setting a new one, call it with a parameter of -1. Note
 *       that although there's no classic equivalent for this function
 *       (apart from the clickPeriod resource), the value set applies in
 *       both interfaces.
 *
 *       wenclose() reports whether the given screen-relative y, x
 *       coordinates fall within the given window.
 *
 *       wmouse_trafo() converts between screen-relative and window-
 *       relative coordinates. A to_screen parameter of true means to
 *       convert from window to screen; otherwise the reverse. The
 *       function returns false if the coordinates aren't within the
 *       window, or if any of the parameters are NULL. The coordinates
 *       have been converted when the function returns true.
 *
 *       mouse_trafo() is the stdscr version of wmouse_trafo().
 *
 *       mousemask() is nearly equivalent to mouse_set(), but instead of
 *       OK/ERR, it returns the value of the mask after setting it. (This
 *       isn't necessarily the same value passed in, since the mask could
 *       be altered on some platforms.) And if the second parameter is a
 *       non-null pointer, mousemask() stores the previous mask value
 *       there. Also, since the ncurses interface doesn't work with
 *       PDCurses' BUTTON_MOVED events, mousemask() filters them out.
 *
 *       nc_getmouse() returns the current mouse status in an MEVENT
 *       struct. This is equivalent to ncurses' getmouse(), renamed to
 *       avoid conflict with PDCurses' getmouse(). But if you define
 *       NCURSES_MOUSE_VERSION (preferably as 2) before including
 *       curses.h, it defines getmouse() to nc_getmouse(), along with a
 *       few other redefintions needed for compatibility with ncurses
 *       code. nc_getmouse() calls request_mouse_pos(), which (not
 *       getmouse()) is the classic equivalent.
 *
 *       ungetmouse() is the mouse equivalent of ungetch(). However,
 *       PDCurses doesn't maintain a queue of mouse events; only one can
 *       be pushed back, and it can overwrite or be overwritten by real
 *       mouse events.
 *
 * Portability                                X/Open    BSD    SYS V
 *       mouse_set                               -       -      4.0
 *       mouse_on                                -       -      4.0
 *       mouse_off                               -       -      4.0
 *       request_mouse_pos                       -       -      4.0
 *       map_button                              -       -      4.0
 *       wmouse_position                         -       -      4.0
 *       getmouse                                -       -      4.0
 *       getbmap                                 -       -      4.0
 *       mouseinterval                           -       -       -
 *       wenclose                                -       -       -
 *       wmouse_trafo                            -       -       -
 *       mouse_trafo                             -       -       -
 *       mousemask                               -       -       -
 *       nc_getmouse                             -       -       -
 *       ungetmouse                              -       -       -
 */

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

#include <string.h>

#include "curspriv.h"

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

static bool ungot = false;

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

int mouse_set(unsigned long mbe)
{
  PDC_LOG(("mouse_set() - called: event %x\n", mbe));

  SP->_trap_mbe = mbe;
  return PDC_mouse_set();
}

int mouse_on(unsigned long mbe)
{
  PDC_LOG(("mouse_on() - called: event %x\n", mbe));

  SP->_trap_mbe |= mbe;
  return PDC_mouse_set();
}

int mouse_off(unsigned long mbe)
{
  PDC_LOG(("mouse_off() - called: event %x\n", mbe));

  SP->_trap_mbe &= ~mbe;
  return PDC_mouse_set();
}

int map_button(unsigned long button)
{
  PDC_LOG(("map_button() - called: button %x\n", button));

  /* This does nothing at the moment */

  SP->_map_mbe_to_key = button;
  return OK;
}

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

  Mouse_status = pdc_mouse_status;
  return OK;
}

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

  if (win && wenclose(win, MOUSE_Y_POS, MOUSE_X_POS))
    {
      if (y)
        {
          *y = MOUSE_Y_POS - win->_begy;
        }

      if (x)
        {
          *x = MOUSE_X_POS - win->_begx;
        }
    }
  else
    {
      if (y)
        {
          *y = -1;
        }

      if (x)
        {
          *x = -1;
        }
    }
}

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

  return SP->_trap_mbe;
}

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

  return SP->_map_mbe_to_key;
}

/* ncurses mouse interface */

int mouseinterval(int wait)
{
  int old_wait;

  PDC_LOG(("mouseinterval() - called: %d\n", wait));

  old_wait = SP->mouse_wait;

  if (wait >= 0 && wait <= 1000)
    {
      SP->mouse_wait = wait;
    }

  return old_wait;
}

bool wenclose(const WINDOW *win, int y, int x)
{
  PDC_LOG(("wenclose() - called: %p %d %d\n", win, y, x));

  return (win && y >= win->_begy && y < win->_begy + win->_maxy &&
          x >= win->_begx && x < win->_begx + win->_maxx);
}

bool wmouse_trafo(const WINDOW *win, int *y, int *x, bool to_screen)
{
  int newy, newx;

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

  if (!win || !y || !x)
    {
      return false;
    }

  newy = *y;
  newx = *x;

  if (to_screen)
    {
      newy += win->_begy;
      newx += win->_begx;

      if (!wenclose(win, newy, newx))
        {
          return false;
        }
    }
  else
    {
      if (wenclose(win, newy, newx))
        {
          newy -= win->_begy;
          newx -= win->_begx;
        }
      else
        {
          return false;
        }
    }

  *y = newy;
  *x = newx;

  return true;
}

bool mouse_trafo(int *y, int *x, bool to_screen)
{
  PDC_LOG(("mouse_trafo() - called\n"));

  return wmouse_trafo(stdscr, y, x, to_screen);
}

mmask_t mousemask(mmask_t mask, mmask_t * oldmask)
{
  PDC_LOG(("mousemask() - called\n"));

  if (oldmask)
    {
      *oldmask = SP->_trap_mbe;
    }

  /* The ncurses interface doesn't work with our move events, so filter them
   * here */

  mask &= ~(BUTTON1_MOVED | BUTTON2_MOVED | BUTTON3_MOVED);
  mouse_set(mask);
  return SP->_trap_mbe;
}

int nc_getmouse(MEVENT * event)
{
  int i;
  mmask_t bstate = 0;

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

  if (!event)
    {
      return ERR;
    }

  ungot = false;

  request_mouse_pos();

  event->id = 0;

  event->x = Mouse_status.x;
  event->y = Mouse_status.y;
  event->z = 0;

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

          if (button == BUTTON_RELEASED)
            {
              bstate |= (BUTTON1_RELEASED << shf);
            }
          else if (button == BUTTON_PRESSED)
            {
              bstate |= (BUTTON1_PRESSED << shf);
            }
          else if (button == BUTTON_CLICKED)
            {
              bstate |= (BUTTON1_CLICKED << shf);
            }
          else if (button == BUTTON_DOUBLE_CLICKED)
            {
              bstate |= (BUTTON1_DOUBLE_CLICKED << shf);
            }

          button = Mouse_status.button[i] & BUTTON_MODIFIER_MASK;

          if (button & PDC_BUTTON_SHIFT)
            {
              bstate |= BUTTON_MODIFIER_SHIFT;
            }

          if (button & PDC_BUTTON_CONTROL)
            {
              bstate |= BUTTON_MODIFIER_CONTROL;
            }

          if (button & PDC_BUTTON_ALT)
            {
              bstate |= BUTTON_MODIFIER_ALT;
            }
        }
    }

  if (MOUSE_WHEEL_UP)
    {
      bstate |= BUTTON4_PRESSED;
    }
  else if (MOUSE_WHEEL_DOWN)
    {
      bstate |= BUTTON5_PRESSED;
    }

  /* extra filter pass -- mainly for button modifiers */

  event->bstate = bstate & SP->_trap_mbe;
  return OK;
}

int ungetmouse(MEVENT * event)
{
  int i;
  unsigned long bstate;

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

  if (!event || ungot)
    {
      return ERR;
    }

  ungot = true;

  pdc_mouse_status.x = event->x;
  pdc_mouse_status.y = event->y;

  pdc_mouse_status.changes = 0;
  bstate = event->bstate;

  for (i = 0; i < 3; i++)
    {
      int shf = i * 5;
      short button = 0;

      if (bstate & ((BUTTON1_RELEASED | BUTTON1_PRESSED |
                     BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED) << shf))
        {
          pdc_mouse_status.changes |= 1 << i;

          if (bstate & (BUTTON1_PRESSED << shf))
            {
              button = BUTTON_PRESSED;
            }

          if (bstate & (BUTTON1_CLICKED << shf))
            {
              button = BUTTON_CLICKED;
            }

          if (bstate & (BUTTON1_DOUBLE_CLICKED << shf))
            {
              button = BUTTON_DOUBLE_CLICKED;
            }

          if (bstate & BUTTON_MODIFIER_SHIFT)
            {
              button |= PDC_BUTTON_SHIFT;
            }

          if (bstate & BUTTON_MODIFIER_CONTROL)
            {
              button |= PDC_BUTTON_CONTROL;
            }

          if (bstate & BUTTON_MODIFIER_ALT)
            {
              button |= PDC_BUTTON_ALT;
            }
        }

      pdc_mouse_status.button[i] = button;
    }

  if (bstate & BUTTON4_PRESSED)
    {
      pdc_mouse_status.changes |= PDC_MOUSE_WHEEL_UP;
    }
  else if (bstate & BUTTON5_PRESSED)
    {
      pdc_mouse_status.changes |= PDC_MOUSE_WHEEL_DOWN;
    }

  return ungetch(KEY_MOUSE);
}
