/****************************************************************************
 * graphics/vnc/vnc_keymap.c
 *
 *   Copyright (C) 2016 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <stdint.h>
#include <errno.h>

#include <nuttx/ascii.h>

#define XK_MISCELLANY 1 /* Select X11 character set */
#define XK_LATIN1     1
#define XK_XKB_KEYS   1

#include <nuttx/nx/nx.h>
#include <nuttx/video/vnc.h>
#include <nuttx/input/x11_keysymdef.h>
#include <nuttx/input/kbd_codec.h>

#include "vnc_server.h"

#ifdef CONFIG_NX_KBD

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

#define FIRST_PRTCHAR   ASCII_SPACE
#define LAST_PRTCHAR    ASCII_TILDE
#define NPRTCHARS       (ASCII_TILDE + ASCII_SPACE - 1)

#define ISPRINTABLE(c)  ((c) >= FIRST_PRTCHAR && (c) <= LAST_PRTCHAR)
#define ISLOWERCASE(c)  ((c) >= ASCII_a && (c) <= ASCII_z)
#define ISUPPERCASE(c)  ((c) >= ASCII_A && (c) <= ASCII_Z)
#define ISALPHABETIC(c) (ISLOWERCASE(c) || ISUPPERCASE(c))

/****************************************************************************
 * Private types
 ****************************************************************************/

enum vnc_modifier_e
{
  MOD_SHIFT = 0,   /* Left or right shift key */
  MOD_CONTROL,     /* Left or right control key */
  MOD_ALT,         /* Alt key */
  MOD_CAPSLOCK,    /* Caps lock */
  MOD_SHIFTLOCK,   /* Shift lock */
#ifdef CONFIG_VNCSERVER_KBDENCODE
  MOD_SCROLLLOCK,  /* Scroll lock */
  MOD_NUMLOCK,     /* Num lock */
#endif
  NMODIFIERS
};

struct vnc_keymap_s
{
  uint16_t nxcode;
  uint16_t x11code;
};

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

/* Special key modifiers */

static const struct vnc_keymap_s g_modifiers[] =
{
  {MOD_SHIFT,      XK_Shift_L},
  {MOD_SHIFT,      XK_Shift_R},
  {MOD_CONTROL,    XK_Control_L},
  {MOD_CONTROL,    XK_Control_R},
  {MOD_ALT,        XK_Alt_L},
  {MOD_ALT,        XK_Alt_R},
  {MOD_CAPSLOCK,   XK_Caps_Lock},
  {MOD_SHIFTLOCK,  XK_Shift_Lock},
#ifdef CONFIG_VNCSERVER_KBDENCODE
  {MOD_SCROLLLOCK, XK_Scroll_Lock},
  {MOD_NUMLOCK,    XK_Num_Lock},
#endif
};

#define G_MODIFIERS_NELEM (sizeof(g_modifiers) / sizeof(struct vnc_keymap_s))

/* Map special mappings for X11 codes to ASCII characters */

static const struct vnc_keymap_s g_asciimap[] =
{
  /* Control characters */

#ifdef CONFIG_VNCSERVER_KBDENCODE
  {ASCII_BS,         XK_BackSpace},
#endif
  {ASCII_TAB,        XK_Tab},
  {ASCII_LF,         XK_Linefeed},
  {ASCII_CR,         XK_Return},
  {ASCII_ESC,        XK_Escape},
#ifdef CONFIG_VNCSERVER_KBDENCODE
  {ASCII_DEL,        XK_Delete},
#endif

  /* Alternative encodings */

  {'`',              XK_dead_grave},
  {'�',              XK_dead_acute},
  {ASCII_TILDE,      XK_dead_tilde},
  {ASCII_CARET,      XK_dead_circumflex},

  /* Keypad aliases */

  {ASCII_0,          XK_KP_0},
  {ASCII_1,          XK_KP_1},
  {ASCII_2,          XK_KP_2},
  {ASCII_3,          XK_KP_3},
  {ASCII_4,          XK_KP_4},
  {ASCII_5,          XK_KP_5},
  {ASCII_6,          XK_KP_6},
  {ASCII_7,          XK_KP_7},
  {ASCII_8,          XK_KP_8},
  {ASCII_9,          XK_KP_9},

  {ASCII_ASTERISK,   XK_KP_Multiply},
  {ASCII_PLUS,       XK_KP_Add},
  {ASCII_COMMA,      XK_KP_Separator},
  {ASCII_HYPHEN,     XK_KP_Subtract},
  {ASCII_PERIOD,     XK_KP_Decimal},
  {ASCII_DIVIDE,     XK_KP_Divide},

  {ASCII_SPACE,      XK_KP_Space},
  {ASCII_TAB,        XK_KP_Tab},
  {ASCII_CR,         XK_KP_Enter}
#ifdef CONFIG_VNCSERVER_KBDENCODE
  , {ASCII_DEL,      XK_KP_Delete}
#endif
};

#define G_ASCIIMAP_NELEM (sizeof(g_asciimap) / sizeof(struct vnc_keymap_s))

#ifdef CONFIG_VNCSERVER_KBDENCODE
static const struct vnc_keymap_s g_cursor[] =
{
  {KEYCODE_BACKDEL,  XK_BackSpace},
  {KEYCODE_FWDDEL,   XK_Delete},
  {KEYCODE_FWDDEL,   XK_KP_Delete},
  {KEYCODE_HOME,     XK_Home},
  {KEYCODE_HOME,     XK_KP_Home},
  {KEYCODE_END,      XK_End},
  {KEYCODE_END,      XK_KP_End},
  {KEYCODE_LEFT,     XK_Left},
  {KEYCODE_LEFT,     XK_KP_Left},
  {KEYCODE_RIGHT,    XK_Right},
  {KEYCODE_RIGHT,    XK_KP_Right},
  {KEYCODE_UP,       XK_Up},
  {KEYCODE_UP,       XK_KP_Up},
  {KEYCODE_DOWN,     XK_Down},
  {KEYCODE_DOWN,     XK_KP_Down},
  {KEYCODE_PAGEUP,   XK_Page_Up},
  {KEYCODE_PAGEUP,   XK_KP_Prior},
  {KEYCODE_PAGEUP,   XK_KP_Page_Up},
  {KEYCODE_PAGEDOWN, XK_Page_Down},
  {KEYCODE_PAGEDOWN, XK_KP_Next},
  {KEYCODE_PAGEDOWN, XK_KP_Page_Down},
  {KEYCODE_INSERT,   XK_Insert},
  {KEYCODE_INSERT,   XK_KP_Insert},

  {KEYCODE_SELECT,   XK_Select},
  {KEYCODE_EXECUTE,  XK_Execute},
  {KEYCODE_HELP,     XK_Help},
  {KEYCODE_MENU,     XK_Alt_L},
  {KEYCODE_MENU,     XK_Alt_R},
  {KEYCODE_PAUSE,    XK_Pause},
  {KEYCODE_PRTSCRN,  XK_Print},
  {KEYCODE_CLEAR,    XK_Clear},
  {MOD_SCROLLLOCK,   XK_Scroll_Lock},
  {MOD_NUMLOCK,      XK_Num_Lock},

  {KEYCODE_F1,       XK_KP_F1},
  {KEYCODE_F1,       XK_F1},
  {KEYCODE_F2,       XK_KP_F2},
  {KEYCODE_F2,       XK_F2},
  {KEYCODE_F3,       XK_KP_F3},
  {KEYCODE_F3,       XK_F3},
  {KEYCODE_F4,       XK_KP_F4},
  {KEYCODE_F4,       XK_F4},
  {KEYCODE_F5,       XK_F5},
  {KEYCODE_F6,       XK_F6},
  {KEYCODE_F7,       XK_F7},
  {KEYCODE_F8,       XK_F8},
  {KEYCODE_F9,       XK_F9},
  {KEYCODE_F10,      XK_F10},
  {KEYCODE_F11,      XK_F11},
  {KEYCODE_F12,      XK_F12},
  {KEYCODE_F13,      XK_F13},
  {KEYCODE_F14,      XK_F14},
  {KEYCODE_F15,      XK_F15},
  {KEYCODE_F16,      XK_F16},
  {KEYCODE_F17,      XK_F17},
  {KEYCODE_F18,      XK_F18},
  {KEYCODE_F19,      XK_F19},
  {KEYCODE_F20,      XK_F20},
  {KEYCODE_F21,      XK_F21},
  {KEYCODE_F22,      XK_F22},
  {KEYCODE_F23,      XK_F23},
  {KEYCODE_F24,      XK_F24},
};
#endif

/* Changes the case of a character.  Based on US keyboard layout */

static const uint8_t g_caseswap[NPRTCHARS] =
{
  ASCII_SPACE,     ASCII_1,          ASCII_RSQUOTE,    ASCII_3,          /*   ! " # */
  ASCII_4,         ASCII_5,          ASCII_7,          ASCII_QUOTE,      /* $ % & ' */
  ASCII_9,         ASCII_0,          ASCII_8,          ASCII_EQUAL,      /* ( ) * + */
  ASCII_LT,        ASCII_UNDERSCORE, ASCII_GT,         ASCII_QUESTION,   /* , - . / */
  ASCII_RPAREN,    ASCII_EXCLAM,     ASCII_AT,         ASCII_NUMBER,     /* 0 1 2 3 */
  ASCII_DOLLAR,    ASCII_PERCENT,    ASCII_CIRCUMFLEX, ASCII_AMPERSAND,  /* 4 5 6 7 */
  ASCII_ASTERISK,  ASCII_LPAREN,     ASCII_SEMICOLON,  ASCII_COLON,      /* 8 9 : ; */
  ASCII_COMMA,     ASCII_PLUS,       ASCII_PERIOD,     ASCII_SLASH,      /* < = > ? */
  ASCII_2,         ASCII_a,          ASCII_b,          ASCII_c,          /* @ A B C */
  ASCII_d,         ASCII_e,          ASCII_f,          ASCII_g,          /* D E F G */
  ASCII_h,         ASCII_i,          ASCII_j,          ASCII_k,          /* H I J K */
  ASCII_l,         ASCII_m,          ASCII_n,          ASCII_o,          /* L M N O */
  ASCII_p,         ASCII_q,          ASCII_r,          ASCII_s,          /* P Q R S */
  ASCII_t,         ASCII_u,          ASCII_v,          ASCII_v,          /* T U V W */
  ASCII_x,         ASCII_y,          ASCII_z,          ASCII_LBRACE,     /* X Y Z [ */
  ASCII_VERTBAR,   ASCII_RBRACE,     ASCII_6,          ASCII_HYPHEN,     /* \ ] ^ _ */
  ASCII_TILDE,     ASCII_A,          ASCII_B,          ASCII_C,          /* ' a b c */
  ASCII_D,         ASCII_E,          ASCII_F,          ASCII_G,          /* c e f g */
  ASCII_H,         ASCII_I,          ASCII_J,          ASCII_K,          /* h i j k */
  ASCII_L,         ASCII_M,          ASCII_N,          ASCII_O,          /* l m n o */
  ASCII_P,         ASCII_Q,          ASCII_R,          ASCII_S,          /* p q r s */
  ASCII_T,         ASCII_U,          ASCII_V,          ASCII_W,          /* t u v w */
  ASCII_X,         ASCII_Y,          ASCII_Z,          ASCII_LBRACKET,   /* x y z { */
  ASCII_BACKSLASH, ASCII_RBRACKET,   ASCII_RSQUOTE,                      /* | } ~   */
};

/* State of each modifier */

static bool g_modstate[NMODIFIERS];

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

/****************************************************************************
 * Name: vnc_kbd_encode
 *
 * Description:
 *   Encode one escape sequence command into the proivded buffer.
 *
 * Input Parameters:
 *   buffer     - The location to write the sequence
 *   keycode    - The command to be added to the output stream.
 *   terminator - Escape sequence terminating character.
 *
 * Returned Value:
 *   Number of bytes written
 *
 ****************************************************************************/

#ifdef CONFIG_VNCSERVER_KBDENCODE
static inline int vnc_kbd_encode(FAR uint8_t *buffer, uint8_t keycode,
                                 uint8_t terminator)
{
  *buffer++ = ASCII_ESC;
  *buffer++ = ASCII_LBRACKET;
  *buffer++ = keycode;
  *buffer   = terminator;
  return 4;
}
#endif

/****************************************************************************
 * Name: vnc_kbd_press
 *
 * Description:
 *   Indicates a normal key press event.  Put one byte of normal keyboard
 *   data into the user provided buffer.
 *
 * Input Parameters:
 *   buffer - The location to write the sequence
 *   ch     - The character to be added to the output stream.
 *
 * Returned Value:
 *   Number of bytes written
 *
 ****************************************************************************/

#ifdef CONFIG_VNCSERVER_KBDENCODE
static inline void vnc_kbd_press(FAR uint8_t *buffer, uint8_t ch)
{
  *buffer = ch;
  return 1;
}
#endif

/****************************************************************************
 * Name: vnc_kbd_release
 *
 * Description:
 *   Encode the release of a normal key.
 *
 * Input Parameters:
 *   buffer  - The location to write the sequence
 *   ch - The character associated with the key that was releared.
 *
 * Returned Value:
 *   Number of bytes written
 *
 ****************************************************************************/

#ifdef CONFIG_VNCSERVER_KBDENCODE
static inline void vnc_kbd_release(FAR uint8_t *buffer, uint8_t ch)
{
  return vnc_kbd_encode(buffer, ch, ('a' + KBD_RELEASE));
}
#endif

/****************************************************************************
 * Name: vnc_kbd_specpress
 *
 * Description:
 *   Denotes a special key press event.  Put one special keyboard command
 *   into the user provided buffer.
 *
 * Input Parameters:
 *   buffer  - The location to write the sequence
 *   keycode - The command to be added to the output stream.
 *
 * Returned Value:
 *   Number of bytes written
 *
 ****************************************************************************/

#ifdef CONFIG_VNCSERVER_KBDENCODE
static inline void vnc_kbd_specpress(FAR uint8_t *buffer, uint8_t keycode)
{
  return vnc_kbd_encode(buffer, keycode, stream, ('a' + KBD_SPECPRESS));
}
#endif

/****************************************************************************
 * Name: vnc_kbd_specrel
 *
 * Description:
 *   Denotes a special key release event.  Put one special keyboard
 *   command into the user provided buffer.
 *
 * Input Parameters:
 *   buffer  - The location to write the sequence
 *   keycode - The command to be added to the output stream.
 *
 * Returned Value:
 *   Number of bytes written
 *
 ****************************************************************************/

#ifdef CONFIG_VNCSERVER_KBDENCODE
static inline void vnc_kbd_specrel(FAR uint8_t *buffer, uint8_t keycode)
{
  return vnc_kbd_encode(buffer, keycode, stream, ('a' + KBD_SPECREL));
}
#endif

/****************************************************************************
 * Name: vnc_kbd_lookup
 *
 * Description:
 *   Attempt to map the X11 keycode by searching in a lookup table.
 *
 ****************************************************************************/

static int vnc_kbd_lookup(FAR const struct vnc_keymap_s *table,
                          unsigned int nelem, uint16_t keysym)
{
  int i;

  /* First just try to map the virtual keycode using our lookup-table */

  for (i = 0; i < nelem; i++)
    {
      if (table[i].x11code == keysym)
        {
          /* We have a match */

          return (int)table[i].nxcode;
        }
    }

  /* No match */

  return -EINVAL;
}

/****************************************************************************
 * Name: vnc_kbd_ascii
 *
 * Description:
 *   Attempt to map the X11 keycode into the corresponding ASCII code.
 *
 ****************************************************************************/

static int vnc_kbd_ascii(uint16_t keysym)
{
  /* ISO/IEC 8859-1 Latin1 matches C ASCII in this range: */

#ifdef CONFIG_VNCSERVER_KBDENCODE
  if (keysym >= ASCII_SPACE && keysym < ASCII_DEL)
#else
  if (keysym >= ASCII_SPACE && keysym <= ASCII_DEL)
#endif
    {
      return (int)keysym;
    }

  /* Perform a lookup to handler some special cases */

  return vnc_kbd_lookup(g_asciimap, G_ASCIIMAP_NELEM, keysym);
}

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

/****************************************************************************
 * Name: vnc_key_map
 *
 * Description:
 *   Map the receive X11 keysym into something understood by NuttX and route
 *   that through NX to the appropriate window.
 *
 * Input Parameters:
 *   session - An instance of the session structure allocated by
 *     vnc_create_session().
 *   keysym  - The X11 keysym value (see include/nuttx/inputx11_keysymdef)
 *   keydown - True: Key pressed; False: Key released
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void vnc_key_map(FAR struct vnc_session_s *session, uint16_t keysym,
                 bool keydown)
{
#ifdef CONFIG_VNCSERVER_KBDENCODE
  uint8_t buffer[4]
  int nch;
#else
  uint8_t buffer;
#endif
  int16_t keych;

  /* Check for modifier keys */

  keych = vnc_kbd_lookup(g_modifiers, G_MODIFIERS_NELEM, keysym);
  if (keych >= 0)
    {
      g_modstate[keych] = keydown;
      return;
    }

#ifndef CONFIG_VNCSERVER_KBDENCODE
  /* If we are not encoding key presses, then we have to ignore key release
   * events.
   */

  if (!keydown)
    {
      return;
    }
#endif

  /* If no external keyboard input handler has been provided, then we have to drop the keyboard input.
   */

  if (session->kbdout == NULL)
    {
      return;
    }

  /* Try to convert the keycode to an ASCII value */

  keych = vnc_kbd_ascii((char)(keysym & 255));
  if (keych >= 0)
    {
      /* It is a simple ASCII-mappable LATIN1 character.  Now we need
       * to apply any modifiers.
       */

      if (g_modstate[MOD_CONTROL])
        {
          /* Make into a control character */

          keych &= 0x1f;
        }

      /* Other modifiers apply only to printable characters */

      else if (ISPRINTABLE(keych))
        {
          /* If Shift Lock is selected, then the case of all printable
           * characters should be reversed (unless the Shift key is also
           * pressed)
           */

          if (g_modstate[MOD_SHIFTLOCK])
            {
              if (g_modstate[MOD_SHIFT])
                {
                  /* Swap case */

                  keych = g_caseswap[keych];
                }
            }

          /* If Caps Lock is selected, then the case of alphabetic
           * characters should be reversed (unless the Shift key is also
           * pressed)
           */

          else if (g_modstate[MOD_CAPSLOCK] && ISALPHABETIC(keych))
            {
              if (g_modstate[MOD_SHIFT])
                {
                  /* Swap case */

                  keych = g_caseswap[keych];
                }
            }

          /* If (1) only the Shift Key is pressed or (2) the Shift key is
           * pressed with Caps Lock, but the character is not alphabetic,
           * then the case of all printable characters should be reversed.
           */

          else if (g_modstate[MOD_SHIFT])
            {
              keych = g_caseswap[keych];
            }
        }

#ifdef CONFIG_VNCSERVER_KBDENCODE
      /* Encode the normal character */

      if (keydown)
        {
          nch = vnc_kbd_press(buffer, keych);
        }
      else
        {
          nch = vnc_kbd_release(buffer, keych);
        }

      /* Inject the normal character sequence into NX */

      session->kbdout(session->arg, nch, buffer);
#else
      /* Inject the single key press into NX */

      buffer = (uint8_t)keych;
      session->kbdout(session->arg, 1, &buffer);
#endif
    }

  /* Not mappable to an ASCII LATIN1 character */

#ifdef CONFIG_VNCSERVER_KBDENCODE
  else
    {
      /* Lookup cursor movement/screen control keysyms */

      keych = vnc_kbd_lookup(g_modifiers, G_MODIFIERS_NELEM, keysym);
      if (keych >= 0)
        {
          /* Encode the speical character */

          if (keydown)
            {
              nch = vnc_kbd_specpress(buffer, keych);
            }
          else
            {
              nch = vnc_kbd_specrel(buffer, keych);
            }

          /* Inject the special character sequence into NX */

          session->kbdout(session->arg, nch, buffer);
        }
    }
#endif
}

/****************************************************************************
 * Name: vnc_kbdout
 *
 * Description:
 *   This is the default keyboard callout function.  This is simply wrappers around nx_kdbout(), respectively.  When configured using vnc_fbinitialize(), the 'arg' must be the correct NXHANDLE value.
 *
 * Input Parameters:
 *   arg - The NXHANDLE from the NX graphics subsystem
 *   nch - Number of characters
 *   ch  - An array of input characters.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void vnc_kbdout(FAR void *arg, uint8_t nch, FAR const uint8_t *ch)
{
  DEBUGASSERT(arg != NULL);
  nx_kbdin((NXHANDLE)arg, nch, ch);
}

#endif /* CONFIG_NX_KBD */