/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames 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.
 * VgaGames 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 VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include "input.h"

void init_input(void);
void dest_input(void);

int vg4_input_nw_keyref(int);
void vg4_input_nw_getkeys(unsigned char *, int);
const char * vg4_input_code2name(int);
VG_BOOL vg4_input_tbuf_allowed(const char *);

static VG_BOOL input_open(void);
static void input_close(void);
static void input_reopen(void);
static int input_gclist(struct VG_GCList *);
static VG_BOOL input_update(VG_BOOL);
static VG_BOOL input_mouse_position(int *, int *);
static VG_BOOL input_mouse_pressed(VG_BOOL *, VG_BOOL *, VG_BOOL *);
static VG_BOOL input_mouse_newpressed(VG_BOOL *, VG_BOOL *, VG_BOOL *);
static void input_mouse_warp(int, int);
static VG_BOOL input_mouse_grabbing(VG_BOOL);
static int input_key_insert(const char *, VG_BOOL, VG_BOOL);
static VG_BOOL input_key_remove(int);
static void input_key_setkbd(int, int);
static void input_key_setgc(int, int, int);
static VG_BOOL input_key_pressed(int);
static VG_BOOL input_key_newpressed(int);
static int input_key_uniqpressed(int, const char **);
static int input_keylist(struct VG_KeyList *, int);
static void input_textbuffer(char *, size_t, const char *, const char *);
static void input_dump(FILE *);


/* set functions */
void
init_input(void)
{
  vg4data.lists.input.s = SML3_calloc(1, sizeof(*vg4data.lists.input.s));
  vg4->input->open = input_open;
  vg4->input->close = input_close;
  vg4->input->reopen = input_reopen;
  vg4->input->gclist = input_gclist;
  vg4->input->update = input_update;
  vg4->input->mouse_position = input_mouse_position;
  vg4->input->mouse_pressed = input_mouse_pressed;
  vg4->input->mouse_newpressed = input_mouse_newpressed;
  vg4->input->mouse_warp = input_mouse_warp;
  vg4->input->mouse_grabbing = input_mouse_grabbing;
  vg4->input->key_insert = input_key_insert;
  vg4->input->key_remove = input_key_remove;
  vg4->input->key_setkbd = input_key_setkbd;
  vg4->input->key_setgc = input_key_setgc;
  vg4->input->key_pressed = input_key_pressed;
  vg4->input->key_newpressed = input_key_newpressed;
  vg4->input->key_uniqpressed = input_key_uniqpressed;
  vg4->input->keylist = input_keylist;
  vg4->input->textbuffer = input_textbuffer;
  vg4->input->dump = input_dump;
} /* Ende init_input */

void
dest_input(void)
{
  if (vg4data.lists.input.s->text.c_yes != NULL) { free(vg4data.lists.input.s->text.c_yes); }
  if (vg4data.lists.input.s->text.c_no != NULL) { free(vg4data.lists.input.s->text.c_no); }
  free(vg4data.lists.input.s);
  vg4data.lists.input.s = NULL;
} /* Ende dest_input */


/* vg4_input_nw_keyref:
 * return index of keyref for network
 * @param keyref  key-entry reference-number
 * @return  index, or -1 = not found or no network-key
 */
int
vg4_input_nw_keyref(int keyref)
{
  int ielm, ipos;
  struct vgi_input *sptr;

  sptr = vg4data.lists.input.s;

  ipos = -1;
  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    if (sptr->keys.e[ielm].nw) { ipos++; }
    if (sptr->keys.e[ielm].keyref == keyref) {
      if (!sptr->keys.e[ielm].nw) { ipos = -1; }
      break;
    }
  }
  if (ielm == sptr->keys.max) { ipos = -1; }

  return ipos;
} /* Ende vg4_input_nw_keyref */


/* vg4_input_nw_getkeys:
 * set network-keys to own client's keysdata (for sending to nw-server)
 * @param keysdata  client's keysdata without mouse
 * @param keyssize  number of bits in keysdata (sizeof * 8)
 */
void
vg4_input_nw_getkeys(unsigned char *keysdata, int keyssize)
{
  static const int imaske[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
  int ielm, pressed, ibit, ibyt;
  struct vgi_input *sptr;

  if (keysdata == NULL || keyssize <= 0) { return; }

  sptr = vg4data.lists.input.s;

  ibyt = keyssize / 8;
  if (keyssize % 8 > 0) { ibyt++; }
  memset(keysdata, 0, ibyt);

  ibit = ibyt = 0;

  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    if (sptr->keys.e[ielm].nw) {
      pressed = sptr->keys.e[ielm].pressed;
      if (pressed == 1 || pressed == 2) { keysdata[ibyt] |= imaske[ibit]; }
      if (++ibit == 8) {
        if (++ibyt >= keyssize) { break; }
        ibit = 0;
      }
    }
  }
} /* Ende vg4_input_nw_getkeys */


/* return the static name for a keyboard-key (VG_INPUT_KBDCODES) or gamecontroller button/axis (VG_INPUT_GCBUTTONS/VG_INPUT_GCAXES) */
const char *
vg4_input_code2name(int keycode)
{
  if (keycode > VG_INPUT_STARTKEY && keycode < VG_INPUT_KBDCODE_MAXENUM) {
    switch(keycode) {
      case VG_INPUT_KBDCODE_ESCAPE: return "Escape";
      case VG_INPUT_KBDCODE_BACKSPACE: return "Backspace";
      case VG_INPUT_KBDCODE_TAB: return "Tab";
      case VG_INPUT_KBDCODE_CAPSLOCK: return "CapsLock";
      case VG_INPUT_KBDCODE_RETURN: return "Return";
      case VG_INPUT_KBDCODE_LSHIFT: return "Left Shift";
      case VG_INPUT_KBDCODE_RSHIFT: return "Right Shift";
      case VG_INPUT_KBDCODE_LCTRL: return "Left Ctrl";
      case VG_INPUT_KBDCODE_LGUI: return "Left GUI";
      case VG_INPUT_KBDCODE_LALT: return "Left Alt";
      case VG_INPUT_KBDCODE_SPACE: return "Space";
      case VG_INPUT_KBDCODE_RALT: return "Right Alt";
      case VG_INPUT_KBDCODE_RGUI: return "Right GUI";
      case VG_INPUT_KBDCODE_APPLICATION: return "Application";
      case VG_INPUT_KBDCODE_RCTRL: return "Right Ctrl";
      case VG_INPUT_KBDCODE_LCURS: return "Left";
      case VG_INPUT_KBDCODE_RCURS: return "Right";
      case VG_INPUT_KBDCODE_UCURS: return "Up";
      case VG_INPUT_KBDCODE_DCURS: return "Down";
      case VG_INPUT_KBDCODE_INSERT: return "Insert";
      case VG_INPUT_KBDCODE_HOME: return "Home";
      case VG_INPUT_KBDCODE_PGUP: return "PageUp";
      case VG_INPUT_KBDCODE_DELETE: return "Delete";
      case VG_INPUT_KBDCODE_END: return "End";
      case VG_INPUT_KBDCODE_PGDOWN: return "PageDown";
      case VG_INPUT_KBDCODE_KP_0: return "Keypad 0";
      case VG_INPUT_KBDCODE_KP_1: return "Keypad 1";
      case VG_INPUT_KBDCODE_KP_2: return "Keypad 2";
      case VG_INPUT_KBDCODE_KP_3: return "Keypad 3";
      case VG_INPUT_KBDCODE_KP_4: return "Keypad 4";
      case VG_INPUT_KBDCODE_KP_5: return "Keypad 5";
      case VG_INPUT_KBDCODE_KP_6: return "Keypad 6";
      case VG_INPUT_KBDCODE_KP_7: return "Keypad 7";
      case VG_INPUT_KBDCODE_KP_8: return "Keypad 8";
      case VG_INPUT_KBDCODE_KP_9: return "Keypad 9";
      case VG_INPUT_KBDCODE_KP_ENTER: return "Keypad Enter";
      case VG_INPUT_KBDCODE_F1: return "F1";
      case VG_INPUT_KBDCODE_F2: return "F2";
      case VG_INPUT_KBDCODE_F3: return "F3";
      case VG_INPUT_KBDCODE_F4: return "F4";
      case VG_INPUT_KBDCODE_F5: return "F5";
      case VG_INPUT_KBDCODE_F6: return "F6";
      case VG_INPUT_KBDCODE_F7: return "F7";
      case VG_INPUT_KBDCODE_F8: return "F8";
      case VG_INPUT_KBDCODE_F9: return "F9";
      case VG_INPUT_KBDCODE_F10: return "F10";
      case VG_INPUT_KBDCODE_F11: return "F11";
      case VG_INPUT_KBDCODE_F12: return "F12";
      default: return vg4data.iolib.f.input_code2name(keycode);
    }

  } else if (keycode > VG_INPUT_STARTGCBUTTON && keycode < VG_INPUT_GCBUTTON_MAXENUM) {
    switch(keycode) {
      case VG_INPUT_GCBUTTON_A: return "A";
      case VG_INPUT_GCBUTTON_B: return "B";
      case VG_INPUT_GCBUTTON_X: return "X";
      case VG_INPUT_GCBUTTON_Y: return "Y";
      case VG_INPUT_GCBUTTON_BACK: return "Back";
      case VG_INPUT_GCBUTTON_GUIDE: return "Guide";
      case VG_INPUT_GCBUTTON_START: return "Start";
      case VG_INPUT_GCBUTTON_LEFTSTICK: return "Leftstick";
      case VG_INPUT_GCBUTTON_RIGHTSTICK: return "Rightstick";
      case VG_INPUT_GCBUTTON_LEFTSHOULDER: return "Leftshoulder";
      case VG_INPUT_GCBUTTON_RIGHTSHOULDER: return "Rightshoulder";
      case VG_INPUT_GCBUTTON_DPAD_UP: return "DPAD-Up";
      case VG_INPUT_GCBUTTON_DPAD_DOWN: return "DPAD-Down";
      case VG_INPUT_GCBUTTON_DPAD_LEFT: return "DPAD-Left";
      case VG_INPUT_GCBUTTON_DPAD_RIGHT: return "DPAD-Right";
    }

  } else if (keycode > VG_INPUT_STARTGCAXIS && keycode < VG_INPUT_GCAXIS_MAXENUM) {
    switch(keycode) {
      case VG_INPUT_GCAXIS_LEFTX_LEFT: return "Left-X:Left";
      case VG_INPUT_GCAXIS_LEFTX_RIGHT: return "Left-X:Right";
      case VG_INPUT_GCAXIS_LEFTY_UP: return "Left-Y:Up";
      case VG_INPUT_GCAXIS_LEFTY_DOWN: return "Left-Y:Down";
      case VG_INPUT_GCAXIS_RIGHTX_LEFT: return "Right-X:Left";
      case VG_INPUT_GCAXIS_RIGHTX_RIGHT: return "Right-X:Right";
      case VG_INPUT_GCAXIS_RIGHTY_UP: return "Right-Y:Up";
      case VG_INPUT_GCAXIS_RIGHTY_DOWN: return "Right-Y:Down";
      case VG_INPUT_GCAXIS_TRIGGERLEFT: return "Left-Trigger";
      case VG_INPUT_GCAXIS_TRIGGERRIGHT: return "Right-Trigger";
    }
  }

  return "";
} /* Ende vg4_input_code2name */


/* check whether UTF8-character of textbuffer is allowed */
VG_BOOL
vg4_input_tbuf_allowed(const char *cht)
{
  VG_BOOL retw = VG_FALSE;

  if (vg4data.lists.input.s->text.c_yes != NULL && vg4data.lists.input.s->text.c_no != NULL) {
    if (SML3_utf8_strcasepbrk(cht, vg4data.lists.input.s->text.c_yes)) { retw = VG_TRUE; }
    if (retw == VG_TRUE && SML3_utf8_strcasepbrk(cht, vg4data.lists.input.s->text.c_no)) { retw = VG_FALSE; }
  } else if (vg4data.lists.input.s->text.c_yes != NULL) {
    if (SML3_utf8_strcasepbrk(cht, vg4data.lists.input.s->text.c_yes)) { retw = VG_TRUE; }
  } else if (vg4data.lists.input.s->text.c_no != NULL) {
    retw = VG_TRUE;
    if (SML3_utf8_strcasepbrk(cht, vg4data.lists.input.s->text.c_no)) { retw = VG_FALSE; }
  } else {
    retw = VG_TRUE;
  }

  return retw;
} /* Ende vg4_input_tbuf_allowed */


/* input_open:
 * open input (is called from vg4->window->open())
 * @return  VG_TRUE = OK or VG_FALSE = error
 */
static VG_BOOL
input_open(void)
{
  VG_BOOL retw = vg4data.iolib.f.input_open();
  vg4->input->update(VG_FALSE);  /* receive first window-events */
  { int winw, winh;
    vg4->window->getsize(&winw, &winh);
    vg4->input->mouse_warp(winw / 2, winh / 2);
  }
  return retw;
} /* Ende input_open */


/* input_close:
 * close input (is called from vg4->window->close())
 */
static void
input_close(void)
{
  vg4data.iolib.f.input_close();
  if (vg4data.lists.input.s->keys.e != NULL) { free(vg4data.lists.input.s->keys.e); }
  memset(vg4data.lists.input.s, 0, sizeof(*vg4data.lists.input.s));
} /* Ende input_close */


/* input_reopen:
 * reopen input (is called from vg4->window->reopen())
 */
static void
input_reopen(void)
{
  vg4data.iolib.f.input_reopen();
  vg4->input->update(VG_FALSE);  /* receive first window-events */
  { int winw, winh;
    vg4->window->getsize(&winw, &winh);
    vg4->input->mouse_warp(winw / 2, winh / 2);
  }
} /* Ende input_reopen */


/* input_gclist:
 * get list of gamecontrollers
 * @param gclist  for returning gamecontroller list, (may be NULL)
 * @return  number of gamecontrollers
 */
static int
input_gclist(struct VG_GCList *gclist)
{
  if (gclist != NULL) {
    int igc;
    for (igc = 0; igc < vg4data.lists.input.s->gcs.max; igc++) {
      gclist->gc[igc].id = igc + 1;
      gclist->gc[igc].name = vg4data.lists.input.s->gcs.gc[igc].name;
    }
    gclist->max = vg4data.lists.input.s->gcs.max;
  }

  return vg4data.lists.input.s->gcs.max;
} /* Ende input_gclist */


/* input_update:
 * update input-events
 * @param needsfocus  whether to suspend if no focus
 *                    - VG_TRUE: yes
 *                    - VG_FALSE: no
 * @return  VG_TRUE = OK or VG_FALSE = exit-request
 */
static VG_BOOL
input_update(VG_BOOL needsfocus)
{
  static time_t nxtping = 0;
  int erg;
  VG_BOOL first, au_susp;

  first = VG_TRUE;
  au_susp = VG_FALSE;

  for (;;) {
    if (time(NULL) > nxtping + 30) { vg4_nw_sendping(VG_FALSE); nxtping = time(NULL); }
    erg = vg4data.iolib.f.input_update();
    if (erg == 0) { return VG_FALSE; }  /* exit-request */
    if (erg == 1) { break; }  /* OK */
    if (erg == 2 && !needsfocus) { break; }  /* no focus, but don't wait */
    if (first) {
      au_susp = vg4->audio->suspend(VG_TRUE);
      first = VG_FALSE;
    }
    vg4->window->flush();
    SML3_sleep_msek(100);
  }

  if (!first) { vg4->audio->suspend(au_susp); }

  return VG_TRUE;
} /* Ende input_update */


/* input_mouse_position:
 * return mouse-position in window
 * @param xpos  for returing x-position
 * @param ypos  for returing y-position
 * @return  whether mouse is grabbed in the window
 */
static VG_BOOL
input_mouse_position(int *xpos, int *ypos)
{
  if (!vg4data.lists.input.s->mouse.grabbed) {
    if (xpos != NULL) { *xpos = 0; }
    if (ypos != NULL) { *ypos = 0; }
    return VG_FALSE;
  }

  if (xpos != NULL) { *xpos = vg4data.lists.input.s->mouse.x; }
  if (ypos != NULL) { *ypos = vg4data.lists.input.s->mouse.y; }
  return VG_TRUE;
} /* Ende input_mouse_position */


/* input_mouse_pressed:
 * return pressed mouse-buttons
 * @param left    for returing whether left button is pressed, (may be NULL)
 * @param middle  for returing whether middle button is pressed, (may be NULL)
 * @param right   for returing whether right button is pressed, (may be NULL)
 * @return  VG_TRUE = one or more buttons are pressed
 *          VG_FALSE = no button is pressed
 */
static VG_BOOL
input_mouse_pressed(VG_BOOL *left, VG_BOOL *middle, VG_BOOL *right)
{
  VG_BOOL retw = VG_FALSE;

  if (!vg4data.lists.input.s->mouse.grabbed) {
    if (left != NULL) { *left = VG_FALSE; }
    if (middle != NULL) { *middle = VG_FALSE; }
    if (right != NULL) { *right = VG_FALSE; }
    return VG_FALSE;
  }

  if (vg4data.lists.input.s->mouse.left
      || vg4data.lists.input.s->mouse.middle
      || vg4data.lists.input.s->mouse.right) { retw = VG_TRUE; }

  if (left != NULL) {
    if (vg4data.lists.input.s->mouse.left > 0) {
      *left = VG_TRUE;
    } else {
      *left = VG_FALSE;
    }
  }

  if (middle != NULL) {
    if (vg4data.lists.input.s->mouse.middle > 0) {
      *middle = VG_TRUE;
    } else {
      *middle = VG_FALSE;
    }
  }

  if (right != NULL) {
    if (vg4data.lists.input.s->mouse.right > 0) {
      *right = VG_TRUE;
    } else {
      *right = VG_FALSE;
    }
  }

  return retw;
} /* Ende input_mouse_pressed */


/* input_mouse_newpressed:
 * return pressed mouse-buttons which have not yet been queried
 * @param left    for returing whether left button is new pressed, (may be NULL)
 * @param middle  for returing whether middle button is new pressed, (may be NULL)
 * @param right   for returing whether right button is new pressed, (may be NULL)
 * @return  VG_TRUE = one or more buttons are new pressed
 *          VG_FALSE = no button is new pressed
 */
static VG_BOOL
input_mouse_newpressed(VG_BOOL *left, VG_BOOL *middle, VG_BOOL *right)
{
  VG_BOOL retw = VG_FALSE;

  if (!vg4data.lists.input.s->mouse.grabbed) {
    if (left != NULL) { *left = VG_FALSE; }
    if (middle != NULL) { *middle = VG_FALSE; }
    if (right != NULL) { *right = VG_FALSE; }
    return VG_FALSE;
  }

  if (vg4data.lists.input.s->mouse.left == 1
      || vg4data.lists.input.s->mouse.middle == 1
      || vg4data.lists.input.s->mouse.right == 1) { retw = VG_TRUE; }

  if (left != NULL) {
    if (vg4data.lists.input.s->mouse.left == 1) {
      *left = VG_TRUE;
      vg4data.lists.input.s->mouse.left = 2;
    } else {
      *left = VG_FALSE;
    }
  }

  if (middle != NULL) {
    if (vg4data.lists.input.s->mouse.middle == 1) {
      *middle = VG_TRUE;
      vg4data.lists.input.s->mouse.middle = 2;
    } else {
      *middle = VG_FALSE;
    }
  }

  if (right != NULL) {
    if (vg4data.lists.input.s->mouse.right == 1) {
      *right = VG_TRUE;
      vg4data.lists.input.s->mouse.right = 2;
    } else {
      *right = VG_FALSE;
    }
  }

  return retw;
} /* Ende input_mouse_newpressed */


/* input_mouse_warp:
 * set mouse to position in window, if grabbed
 * @param xpos  x-position
 * @param ypos  y-position
 */
static void
input_mouse_warp(int xpos, int ypos)
{
  vg4data.iolib.f.input_mouse_warp(xpos, ypos);
} /* Ende input_mouse_warp */


/* input_mouse_grabbing:
 * (un)set mouse grabbing
 * If set, mouse can be catched and released in the window with CTRL+Backspace
 * If unset, mouse cannot be catched but used for the window
 * @param on  VG_TRUE=set, VG_FALSE=unset
 * @return  previous value
 */
static VG_BOOL
input_mouse_grabbing(VG_BOOL on)
{
  VG_BOOL retw = vg4data.lists.input.s->mouse.grab_enabled;
  vg4data.lists.input.s->mouse.grab_enabled = on;
  vg4data.iolib.f.input_mouse_grabbing();

  return retw;
} /* Ende input_mouse_grabbing */


/* input_key_insert:
 * insert a key-entry
 * @param name           a short description for this key-entry
 * @param is_changeable  whether the key-codes can be changed, e.g. in a menu
 * @param is_nwkey       whether this key-entry will be sent over network if active
 * @return  key-entry reference-number (a positive arbitrary unique reference-number to this key-entry)
 *          or 0 if the maximal number of key-entries had been reached (VG_MAX_KEYENTRIES)
 */
static int
input_key_insert(const char *name, VG_BOOL is_changeable, VG_BOOL is_nwkey)
{
  struct vgi_input *sptr;
  int ielm, igc, keyref;
  char kused[VG_MAX_KEYENTRIES];

  if (name == NULL || *name == '\0') { name = "(no-name)"; }

  sptr = vg4data.lists.input.s;

  if (sptr->keys.e == NULL) { sptr->keys.e = SML3_calloc(1, sizeof(*sptr->keys.e)); }

  /* find a free keyref */
  memset(kused, 0, sizeof(kused));
  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    kused[sptr->keys.e[ielm].keyref - 1] = 1;
  }
  for (ielm = 0; ielm < VG_MAX_KEYENTRIES; ielm++) {
    if (kused[ielm] == 0) { keyref = ielm + 1; break; }
  }
  if (ielm == VG_MAX_KEYENTRIES) {
    outerr("maximal number of key-entries reached");
    return 0;
  }

  /* insert */

  ielm = sptr->keys.max++;
  sptr->keys.e = SML3_realloc(sptr->keys.e, sptr->keys.max * sizeof(*sptr->keys.e));
  memset(&sptr->keys.e[ielm], 0, sizeof(*sptr->keys.e));

  sptr->keys.e[ielm].keyref = keyref;
  sptr->keys.e[ielm].menu = is_changeable;
  sptr->keys.e[ielm].nw = is_nwkey;
  snprintf(sptr->keys.e[ielm].name, sizeof(sptr->keys.e[0].name), "%s", name);

  sptr->keys.e[ielm].devs.kbd.code = VG_INPUT_NOKEY;
  for (igc = 0; igc < sptr->gcs.max; igc++) {
    sptr->keys.e[ielm].devs.gcs.gc[igc].code = VG_INPUT_NOKEY;
  }

  return keyref;
} /* Ende input_key_insert */


/* input_key_remove:
 * remove a key-entry
 * @param keyref   key-entry reference-number
 * @return  VG_TRUE = OK or VG_FALSE = not removed
 */
static VG_BOOL
input_key_remove(int keyref)
{
  struct vgi_input *sptr;
  int ielm;

  if (keyref <= 0) { return VG_FALSE; }

  sptr = vg4data.lists.input.s;

  if (sptr->keys.e == NULL) { return VG_FALSE; }

  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    if (sptr->keys.e[ielm].keyref == keyref) { break; }
  }
  if (ielm == sptr->keys.max) { return VG_FALSE; }  /* not found */

  if (ielm < sptr->keys.max - 1) {
    memmove(&sptr->keys.e[ielm], &sptr->keys.e[ielm + 1], sizeof(sptr->keys.e[0]) * (sptr->keys.max - (ielm + 1)));
  }
  sptr->keys.max--;

  return VG_TRUE;
} /* Ende input_key_remove */


/* input_key_setkbd:
 * set a keyboard key-code for a key-entry
 * @param keyref   key-entry reference-number
 * @param keycode  VG_INPUT_NOKEY or one of VG_INPUT_KBDCODES
 */
static void
input_key_setkbd(int keyref, int keycode)
{
  int ielm1, ielm2;
  struct vgi_input *sptr;

  if (keycode != VG_INPUT_NOKEY && (keycode <= VG_INPUT_STARTKEY || keycode >= VG_INPUT_KBDCODE_MAXENUM)) { return; }

  sptr = vg4data.lists.input.s;

  for (ielm1 = 0; ielm1 < sptr->keys.max; ielm1++) {
    if (sptr->keys.e[ielm1].keyref == keyref) {
      sptr->keys.e[ielm1].devs.kbd.code = keycode;
      sptr->keys.e[ielm1].devs.kbd.ispres = VG_FALSE;
      sptr->keys.e[ielm1].devs.kbd.isnew = VG_FALSE;
      break;
    }
  }

  /* set any key-entry before with the same key-code to unpressed */
  for (ielm2 = 0; ielm2 < ielm1; ielm2++) {
    if (sptr->keys.e[ielm2].devs.kbd.code == keycode) {
      sptr->keys.e[ielm2].devs.kbd.ispres = VG_FALSE;
      sptr->keys.e[ielm2].devs.kbd.isnew = VG_FALSE;
    }
  }
} /* Ende input_key_setkbd */


/* input_key_setgc:
 * set a gamecontroller button- or axis-code for a key-entry
 * @param keyref   key-entry reference-number
 * @param gcid     gamecontroller-ID or 0 = all gamecontrollers
 * @param keycode  VG_INPUT_NOKEY or one of VG_INPUT_GCBUTTONS/VG_INPUT_GCAXES
 */
static void
input_key_setgc(int keyref, int gcid, int keycode)
{
  int ielm1, ielm2, igc;
  struct vgi_input *sptr;

  sptr = vg4data.lists.input.s;

  if (gcid < 0 || gcid > sptr->gcs.max) { return; }
  if (keycode != VG_INPUT_NOKEY) {
    if (keycode > VG_INPUT_STARTGCBUTTON && keycode < VG_INPUT_GCBUTTON_MAXENUM) {;}
    else if (keycode > VG_INPUT_STARTGCAXIS && keycode < VG_INPUT_GCAXIS_MAXENUM) {;}
    else { return; }
  }

  for (ielm1 = 0; ielm1 < sptr->keys.max; ielm1++) {
    if (sptr->keys.e[ielm1].keyref == keyref) {
      for (igc = 0; igc < sptr->gcs.max; igc++) {
        if (gcid == 0 || gcid == igc + 1) {
          sptr->keys.e[ielm1].devs.gcs.gc[igc].code = keycode;
          sptr->keys.e[ielm1].devs.gcs.gc[igc].ispres = VG_FALSE;
          sptr->keys.e[ielm1].devs.gcs.gc[igc].isnew = VG_FALSE;
        }
      }
      break;
    }
  }

  /* set any key-entry before with the same key-code to unpressed */
  for (ielm2 = 0; ielm2 < ielm1; ielm2++) {
    for (igc = 0; igc < sptr->gcs.max; igc++) {
      if (gcid == 0 || gcid == igc + 1) {
        if (sptr->keys.e[ielm2].devs.gcs.gc[igc].code == keycode) {
          sptr->keys.e[ielm2].devs.gcs.gc[igc].ispres = VG_FALSE;
          sptr->keys.e[ielm2].devs.gcs.gc[igc].isnew = VG_FALSE;
        }
      }
    }
  }
} /* Ende input_key_setgc */


/* input_key_pressed:
 * return if a key is pressed
 * @param keyref  key-entry reference-number
 * @return  VG_TRUE = pressed
 *          VG_FALSE = not pressed
 */
static VG_BOOL
input_key_pressed(int keyref)
{
  int ielm, pressed;
  struct vgi_input *sptr;

  sptr = vg4data.lists.input.s;

  pressed = 0;
  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    if (sptr->keys.e[ielm].keyref == keyref) {
      pressed = sptr->keys.e[ielm].pressed;
      break;
    }
  }

  if (pressed == 1 || pressed == 2) { return VG_TRUE; }
  return VG_FALSE;
} /* Ende input_key_pressed */


/* input_key_newpressed:
 * return if a key is pressed which has not yet been queried
 * @param keyref  key-entry reference-number
 * @return  VG_TRUE = pressed
 *          VG_FALSE = not pressed
 */
static VG_BOOL
input_key_newpressed(int keyref)
{
  int ielm, *pressed;
  struct vgi_input *sptr;

  sptr = vg4data.lists.input.s;

  pressed = NULL;
  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    if (sptr->keys.e[ielm].keyref == keyref) {
      pressed = &sptr->keys.e[ielm].pressed;
      break;
    }
  }

  if (pressed != NULL && *pressed == 1) { *pressed = 2; return VG_TRUE; }
  return VG_FALSE;
} /* Ende input_key_newpressed */


/* input_key_uniqpressed:
 * return an uniquely pressed key
 * @param gcid  0 = keyboard or >0 = gamecontroller-ID
 * @param name  for returning static pointer to key-name, (may be NULL)
 * @return  VG_INPUT_NOKEY or one of VG_INPUT_KBDCODES/VG_INPUT_GCBUTTONS/VG_INPUT_GCAXES
 */
static int
input_key_uniqpressed(int gcid, const char **name)
{
  int keycode = vg4data.iolib.f.input_key_uniqpressed(gcid);
  if (name != NULL) { *name = vg4_input_code2name(keycode); }
  return keycode;
} /* Ende input_key_uniqpressed */


/* input_keylist:
 * get list of key-entries
 * @param keylist  for returning key-entry list, (may be NULL)
 * @param gcid     0 = keyboard or >0 = gamecontroller-ID
 * @return  number of key-entries
 */
static int
input_keylist(struct VG_KeyList *keylist, int gcid)
{
  struct vgi_input *sptr;
  int ielm;

  sptr = vg4data.lists.input.s;

  if (gcid < 0 || gcid > sptr->gcs.max) {
    if (keylist != NULL) { keylist->max = 0; }
    return 0;
  }

  if (keylist != NULL) {
    for (ielm = 0; ielm < sptr->keys.max; ielm++) {
      memset(&keylist->key[ielm], 0, sizeof(keylist->key[0]));
      keylist->key[ielm].keyref = sptr->keys.e[ielm].keyref;
      vg4->misc->strcpy(keylist->key[ielm].keyname, sizeof(keylist->key[0].keyname), sptr->keys.e[ielm].name);
      keylist->key[ielm].is_changeable = sptr->keys.e[ielm].menu;
      keylist->key[ielm].is_nwkey = sptr->keys.e[ielm].nw;
      if (gcid == 0) {
        keylist->key[ielm].code = sptr->keys.e[ielm].devs.kbd.code;
      } else {
        keylist->key[ielm].code = sptr->keys.e[ielm].devs.gcs.gc[gcid - 1].code;
      }
      vg4->misc->strcpy(keylist->key[ielm].codename, sizeof(keylist->key[0].codename), vg4_input_code2name(keylist->key[ielm].code));
    }
    keylist->max = sptr->keys.max;
  }

  return sptr->keys.max;
} /* Ende input_keylist */


/* input_textbuffer:
 * set a text buffer for text input
 * @param text    text buffer or NULL = no buffer
 * @param tsize   size of text buffer
 * @param ch_acc  allowed characters, or NULL = all
 * @param ch_rej  not allowed characters, or NULL = none
 *
 * format for (not) allowed characters:
 *  - single UTF-8 characters
 *  - character-groups
 *    - %d = digits
 *    - %a = letters
 *    - %b = blanks
 *    - %p = special characters: any printable character which is not a space or an alphanumeric character
 *    - %% = %
 * where single characters take precedence over character-groups
 *
 * Example:
 *  - ch_acc = %d%a.:
 *    allow digits and letters and period and colon
 *  - ch_acc = %d%a%p
 *    ch_rej = /
 *    allow digits and letters and special characters except slash (/)
 */
static void
input_textbuffer(char *text, size_t tsize, const char *ch_acc, const char *ch_rej)
{
  if (text == NULL) { tsize = 0; } else if (tsize == 0) { text = NULL; }

  if (vg4data.lists.input.s->text.c_yes != NULL) {
    free(vg4data.lists.input.s->text.c_yes);
    vg4data.lists.input.s->text.c_yes = NULL;
  }
  if (vg4data.lists.input.s->text.c_no != NULL) {
    free(vg4data.lists.input.s->text.c_no);
    vg4data.lists.input.s->text.c_no = NULL;
  }

  vg4data.lists.input.s->text.tptr = text;
  vg4data.lists.input.s->text.tsize = tsize;
  if (ch_acc != NULL && *ch_acc != '\0') { vg4data.lists.input.s->text.c_yes = SML3_strdup(ch_acc); }
  if (ch_rej != NULL && *ch_rej != '\0') { vg4data.lists.input.s->text.c_no = SML3_strdup(ch_rej); }

  vg4data.iolib.f.input_textbuffer((text == NULL ? VG_FALSE : VG_TRUE));
} /* Ende input_textbuffer */


/* input_dump:
 * dump input entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
input_dump(FILE *outfp)
{
  struct vgi_input *sptr;
  int igc, ielm;

  if (outfp == NULL) { outfp = stdout; }

  sptr = vg4data.lists.input.s;

  fprintf(outfp, "\nDump of input entries\n");
  fprintf(outfp, "=====================\n\n");

  fprintf(outfp, "Found gamecontrollers:\n");
  for (igc = 0; igc < sptr->gcs.max; igc++) {
    fprintf(outfp, "- ID=%d: %s\n", igc + 1, sptr->gcs.gc[igc].name);
  }
  fprintf(outfp, "\n");

  fprintf(outfp, "Key entries:\n");
  for (ielm = 0; ielm < sptr->keys.max; ielm++) {
    fprintf(outfp, "- key-reference=%d: %s\n", sptr->keys.e[ielm].keyref, sptr->keys.e[ielm].name);
    fprintf(outfp, "  is-changeable=%s, is-network-key=%s, is-pressed=%s\n",
            (sptr->keys.e[ielm].menu ? "yes" : "no"),
            (sptr->keys.e[ielm].nw ? "yes" : "no"),
            (sptr->keys.e[ielm].pressed > 0 ? "yes" : "no"));
    if (sptr->keys.e[ielm].devs.kbd.code != VG_INPUT_NOKEY) {
      fprintf(outfp, "  keyboard-code=%d (%s)\n", sptr->keys.e[ielm].devs.kbd.code, vg4_input_code2name(sptr->keys.e[ielm].devs.kbd.code));
    }
    fprintf(outfp, "  gamecontroller-codes:\n");
    for (igc = 0; igc < sptr->gcs.max; igc++) {
      if (sptr->keys.e[ielm].devs.gcs.gc[igc].code != VG_INPUT_NOKEY) {
        fprintf(outfp, "  - [ID=%d]=%d (%s)\n", igc + 1,
                sptr->keys.e[ielm].devs.gcs.gc[igc].code, vg4_input_code2name(sptr->keys.e[ielm].devs.gcs.gc[igc].code));
      }
    }
  }
  fprintf(outfp, "\n");

  fprintf(outfp, "Mouse:\n");
  fprintf(outfp, "- grabbed=%s\n", (sptr->mouse.grabbed ? "yes" : "no"));
  fprintf(outfp, "- position: x=%d, y=%d\n", sptr->mouse.x, sptr->mouse.y);
  fprintf(outfp, "- pressed: left=%s, middle=%s, right=%s\n",
          (sptr->mouse.left > 0 ? "yes": "no"),
          (sptr->mouse.middle > 0 ? "yes": "no"),
          (sptr->mouse.right > 0 ? "yes": "no"));
  fprintf(outfp, "\n");

  fprintf(outfp, "Text-input:\n");
  fprintf(outfp, "- active=");
  if (sptr->text.tptr != NULL && sptr->text.tsize > 0) {
    fprintf(outfp, "yes: \"%s\"\n", sptr->text.tptr);
  } else {
    fprintf(outfp, "no\n");
  }
  fprintf(outfp, "\n");
} /* Ende input_dump */
