/* 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 "dialog.h"

void init_dialog_keydef(void);

static VG_BOOL dialog_keydef(const char *, const struct VG_Position *, struct VG_Hash *, int);

static VG_BOOL read_keys(struct VG_KeyList *, int, struct VG_Canvas *, const char *);
static VG_BOOL read_gcs(struct VG_GCList *, struct VG_Canvas *, const char *);


/* set functions */
void
init_dialog_keydef(void)
{
  vg4->dialog->keydef = dialog_keydef;
} /* Ende init_dialog_keydef */


/* read changeable input-keys */
static VG_BOOL
read_keys(struct VG_KeyList *keylist, int gcid, struct VG_Canvas *cvas, const char *cvname)
{
  char ibuf[32], listbuf[256];
  VG_BOOL nokey;
  int ikey;

  if (keylist == NULL || gcid < 0 || cvas == NULL || cvname == NULL) { return VG_FALSE; }

  /* get defined keys */
  vg4->input->keylist(keylist, gcid);

  /* insert keys into list */

  vg4->canvas->list_clear(cvas, cvname);
  nokey = VG_TRUE;

  for (ikey = 0; ikey < keylist->max; ikey++) {
    if (keylist->key[ikey].is_changeable) {
      nokey = VG_FALSE;
      snprintf(listbuf, sizeof(listbuf),
               "%%{txt[boxwidth=47%% orientation=right]: %s%%}%%{box[boxwidth=6%%]:%%}%%{txt[boxwidth=47%% orientation=left]: [%s]%%}",
               keylist->key[ikey].keyname, keylist->key[ikey].codename);
      snprintf(ibuf, sizeof(ibuf), "%d", ikey + 1);
      vg4->canvas->list_add(cvas, cvname, listbuf, ibuf);
    }
  }

  return !nokey;
} /* Ende read_keys */


/* read gamecontrollers */
static VG_BOOL
read_gcs(struct VG_GCList *gclist, struct VG_Canvas *cvas, const char *cvname)
{
  char ibuf[32], listbuf[256];
  VG_BOOL nogc;
  int igc;

  if (gclist == NULL || cvas == NULL || cvname == NULL) { return VG_FALSE; }

  /* read gamecontrollers */
  vg4->input->gclist(gclist);

  /* insert gamecontrollers into list */

  vg4->canvas->list_clear(cvas, cvname);
  nogc = VG_TRUE;

  for (igc = 0; igc < gclist->max; igc++) {
    nogc = VG_FALSE;
    snprintf(listbuf, sizeof(listbuf), "%s", gclist->gc[igc].name);
    snprintf(ibuf, sizeof(ibuf), "%d", igc + 1);
    vg4->canvas->list_add(cvas, cvname, listbuf, ibuf);
  }

  return !nogc;
} /* Ende read_gcs */


/* dialog_keydef:
 * open a canvas-dialog to re-define keys of the keyboard or a gamecontroller
 * @param cvasdir     directory to load canvasses from, or NULL = system-canvas
 * @param posdst      canvas-position on window, or NULL = centered
 * @param hvar        hash with variable-values for text control-commands "var", or NULL
 *                      hash-format:
 *                       - key:   variable name
 *                       - value: variable value
 *                    (see below)
 * @param gcid        0  = keyboard
 *                    >0 = gamecontroller-ID
 *                    -1 = select gamecontroller before
 * @return  VG_TRUE = OK or VG_FALSE = exit-request
 *
 * The canvas-names must be:
 *  - keydef.top.cvas: for re-define keys of the keyboard or a gamecontroller
 *    - with items:
 *      - [CV-TEXT]   with name "title": text for title
 *      - [CV-TEXT]   with name "device": device-name
 *      - [CV-BUTTON] with name "done": for returning
 *      - [CV-LIST] with name "keylist": for the list of defined keys
 *    - uses following variables:
 *      - key: "title", value: <title of canvas>
 *      - key: "device", value: <name of the gamecontroller or empty if keyboard>
 *    - hvar should contain:
 *      - key: "top:title", value: <title of canvas>
 *      - key: "top:device", value: <name of a specific gamecontroller, or may be empty for keyboard>
 *  - keydef.gclist.cvas: for selecting a gamecontroller
 *    - with items:
 *      - [CV-LIST] with name "gclist": for the list of gamecontrollers
 *    - uses following variables: none
 *    - hvar should contain: nothing defined
 *  - keydef.press.cvas: for pressing a key
 *    - with items:
 *      - [CV-TEXT]    with name "title": text for title
 *      - [CV-TEXT]    with name "keyname": name of current key
 *      - [CV-UNIQKEY] with name "keypress": for pressing a key
 *    - uses following variables:
 *      - key: "title", value: <title of canvas>
 *      - key: "keyname", value: <name of the current key>
 *    - hvar should contain:
 *      - key: "press:title", value: <title of canvas>
 */
static VG_BOOL
dialog_keydef(const char *cvasdir, const struct VG_Position *posdst, struct VG_Hash *hvar, int gcid)
{
  const char *cvprefix = "keydef";
  const char *cvfile_top = "keydef.top.cvas";
  const char *cvfile_gclist = "keydef.gclist.cvas";
  const char *cvfile_press = "keydef.press.cvas";
  const char *cvname_done = "done";
  const char *cvname_keylist = "keylist";
  const char *cvname_gclist = "gclist";
  const char *cvname_keypress = "keypress";
  const char *cvvar_title = "title";
  const char *cvvar_device = "device";
  const char *cvvar_keyname = "keyname";
  const char *cvicon_kbd = "keydef-kbd-ico.bmp";
  const char *cvicon_gc = "keydef-gc-ico.bmp";
  struct VG_Hash *hvar_top, *hvar_press;
  char cvaspath[512], filebuf[640], *cptr;
  VG_BOOL retw;
  struct VG_Canvas *cvas_top, *cvas_press;
  const char *selname, *gcname;
  struct VG_KeyList keylist;
  int ikey;

  hvar_top = hvar_press = NULL;
  keylist.max = 0;
  gcname = NULL;

  /* select gamecontroller? */
  if (gcid < 0) {
    struct VG_GCList gclist;
    vg4->input->gclist(&gclist);

    if (gclist.max > 1) {  /* execute gclist-canvas */
      struct VG_Image *wclone;
      struct VG_Hash *hvar_gclist;
      struct VG_Canvas *cvas_gclist;

      /* gclist-canvas */
      hvar_gclist = vg4->hash->create();
      dialog_transfer_variables(hvar, hvar_gclist, "gclist");
      dialog_cvasfile(cvprefix, cvfile_gclist, cvasdir, cvaspath, sizeof(cvaspath));
      cvas_gclist = vg4->canvas->load(cvaspath, hvar_gclist);
      if (cvas_gclist == NULL) {
        outerr("%s [at cvasdir=%s] not found", cvfile_gclist, (cvasdir == NULL ? "(null)" : cvasdir));
        return VG_TRUE;
      }

      wclone = vg4->window->clone(NULL, NULL);
      retw = VG_TRUE;
      if (read_gcs(&gclist, cvas_gclist, cvname_gclist)) {
        for (;;) {
          /* execute gclist-canvas */
          if (!vg4->canvas->exec(cvas_gclist, posdst, &selname)) { retw = VG_FALSE; break; }
          if (selname == NULL) { break; }  /* cancel */
  
          /* act according to selection */
          if (strcmp(selname, cvname_gclist) == 0) {  /* gamecontroller selected from list */
            const char *listitem = vg4->canvas->list_get_activated(cvas_gclist, selname);
            if (listitem != NULL) {
              ikey = atoi(listitem);
              if (ikey > 0) {
                gcid = gclist.gc[ikey - 1].id;
                gcname = gclist.gc[ikey - 1].name;
                break;
              }
            }
          }
        }
      }

      vg4->canvas->destroy(cvas_gclist);
      vg4->hash->destroy(hvar_gclist);
      vg4->window->clear();
      vg4->window->copy(wclone, NULL, NULL);
      vg4->image->destroy(wclone);
      if (gcid < 0) { return retw; }

    } else if (gclist.max == 1) {
      gcid = gclist.gc[0].id;
      gcname = gclist.gc[0].name;

    } else {
      return VG_TRUE;
    }
  }

  /* set variables and load canvasses */

  /* top-canvas */
  hvar_top = vg4->hash->create();
  dialog_transfer_variables(hvar, hvar_top, "top");
  if (gcname != NULL) { vg4->hash->setstr(hvar_top, cvvar_device, gcname); }
  cptr = (char *)vg4->hash->get(hvar_top, cvvar_title, NULL);
  if (cptr == NULL) {
    if (gcid > 0) {
      snprintf(filebuf, sizeof(filebuf), "%%{imgfile: %s%%}", cvicon_gc);
    } else {
      snprintf(filebuf, sizeof(filebuf), "%%{imgfile: %s%%}", cvicon_kbd);
    }
    vg4->hash->setstr(hvar_top, cvvar_title, filebuf);
  }
  dialog_cvasfile(cvprefix, cvfile_top, cvasdir, cvaspath, sizeof(cvaspath));
  cvas_top = vg4->canvas->load(cvaspath, hvar_top);
  if (cvas_top == NULL) {
    outerr("%s [at cvasdir=%s] not found", cvfile_top, (cvasdir == NULL ? "(null)" : cvasdir));
    return VG_TRUE;
  }

  /* press-canvas */
  hvar_press = vg4->hash->create();
  dialog_transfer_variables(hvar, hvar_press, "press");
  vg4->hash->setstr(hvar_press, cvvar_keyname, "");
  dialog_cvasfile(cvprefix, cvfile_press, cvasdir, cvaspath, sizeof(cvaspath));
  cvas_press = vg4->canvas->load(cvaspath, hvar_press);
  if (cvas_press == NULL) {
    outerr("%s [at cvasdir=%s] not found", cvfile_press, (cvasdir == NULL ? "(null)" : cvasdir));
    vg4->canvas->destroy(cvas_top);
    return VG_TRUE;
  }

  /* loop */

  retw = VG_TRUE;

  if (read_keys(&keylist, gcid, cvas_top, cvname_keylist)) {
    for (;;) {
      /* execute top-canvas */
      if (!vg4->canvas->exec(cvas_top, posdst, &selname)) { retw = VG_FALSE; break; }
      if (selname == NULL) { break; }  /* cancel */
      if (strcmp(selname, cvname_done) == 0) { break; }
  
      /* act according to selection */
  
      if (strcmp(selname, cvname_keylist) == 0) {  /* key selected from list */
        const char *listitem = vg4->canvas->list_get_activated(cvas_top, selname);
        if (listitem != NULL) {
          ikey = atoi(listitem);
          if (ikey > 0 && ikey <= keylist.max) {  /* item-name is index + 1 of keylist */
            ikey--;
            /* reload press-canvas */
            vg4->hash->setstr(hvar_press, cvvar_keyname, keylist.key[ikey].keyname);
            if (vg4->canvas->reload(cvas_press, hvar_press)) {
              /* execute press-canvas */
              vg4->canvas->uniqkey_set(cvas_press, cvname_keypress, gcid, keylist.key[ikey].code);
              if (!dialog_exec_subcvas(cvas_top, cvas_press, &selname)) { retw = VG_FALSE; break; }
              if (selname != NULL && strcmp(selname, cvname_keypress) == 0) {  /* key pressed */
                int keycode, ikey2;
                keycode = vg4->canvas->uniqkey_get(cvas_press, selname);
                /* don't allow key-code which is used by a non changeable key */
                for (ikey2 = 0; ikey2 < keylist.max; ikey2++) {
                  if (!keylist.key[ikey2].is_changeable && keylist.key[ikey2].code == keycode) { break; }
                }
                if (ikey2 == keylist.max) {
                  /* remove duplicate key-code */
                  for (ikey2 = 0; ikey2 < keylist.max; ikey2++) {
                    if (keylist.key[ikey2].is_changeable) {
                      if (keylist.key[ikey2].code == keycode) {
                        if (gcid == 0) {
                          vg4->input->key_setkbd(keylist.key[ikey2].keyref, VG_INPUT_NOKEY);
                        } else {
                          vg4->input->key_setgc(keylist.key[ikey2].keyref, gcid, VG_INPUT_NOKEY);
                        }
                      }
                    }
                  }
                  /* replace to new key-code */
                  if (gcid == 0) {
                    vg4->input->key_setkbd(keylist.key[ikey].keyref, keycode);
                  } else {
                    vg4->input->key_setgc(keylist.key[ikey].keyref, gcid, keycode);
                  }
                  if (!read_keys(&keylist, gcid, cvas_top, cvname_keylist)) { break; }
                }
              }
            }
          }
        }
      }
    }
  }

  vg4->canvas->destroy(cvas_press);
  vg4->canvas->destroy(cvas_top);
  if (hvar_press != NULL) { vg4->hash->destroy(hvar_press); }
  if (hvar_top != NULL) { vg4->hash->destroy(hvar_top); }

  return retw;
} /* Ende dialog_keydef */
