
#include <stdint.h>

#include "swis.h"
#include "Global/RISCOS.h"    // KEYV
#include "Global/Keyboard.h"  // KeyV_KeyUp, KeyV_KeyDown

#include "debugrep.h"
#include "usbjoystick.h"

#include "keys.h"


// The KeyNo currently asserted (held down) by us for each logical source of a
// slot: index 0-3 are the four axis directions (up/down/left/right), then one
// per physical button. NOT_MAPPED means "nothing held for this source". We
// track the actual KeyNo (not just a flag) so a remap while a key is held
// still releases the right key.
#define KEY_DIR_COUNT 4
static int32_t key_dir_held[JOY_MAX][KEY_DIR_COUNT];
static int32_t key_btn_held[JOY_MAX][JOY_BUTTONS];


void keys_init(void)
{
  for (uint32_t s = 0; s < JOY_MAX; s++) {
    for (uint32_t d = 0; d < KEY_DIR_COUNT; d++)
      key_dir_held[s][d] = NOT_MAPPED;
    for (uint32_t b = 0; b < JOY_BUTTONS; b++)
      key_btn_held[s][b] = NOT_MAPPED;
  }
}


// Inject one key up/down onto KeyV, the same call the real USB keyboard uses:
// R0 = reason (KeyV_KeyDown/KeyV_KeyUp), R1 = RISC OS internal KeyNo, R9 = KEYV.
static void send_key(int32_t keyno, int down)
{
  _swix(OS_CallAVector, _INR(0,1) | _IN(9),
        down ? KeyV_KeyDown : KeyV_KeyUp, keyno, KEYV);
}


// Drive one logical source towards the wanted state. keyno is the code that
// source maps to now (NOT_MAPPED = unmapped); want is whether it should be
// held; *held is the KeyNo we currently have down for it (NOT_MAPPED = none).
// Emits KeyV events only on an actual change, and copes with the mapping
// changing while a key is held (releases the stale key, presses the new one).
static void set_key(int32_t keyno, int want, int32_t *held)
{
  int32_t cur = *held;

  if (want && keyno != NOT_MAPPED) {
    if (cur == keyno) return;                 // already holding the right key
    if (cur != NOT_MAPPED) send_key(cur, FALSE);  // release a stale/old key
    send_key(keyno, TRUE);
    *held = keyno;
  }
  else {
    if (cur != NOT_MAPPED) {                   // want release (or now unmapped)
      send_key(cur, FALSE);
      *held = NOT_MAPPED;
    }
  }
}


void update_keys(uint32_t i)
{
  struct joydata_struct *j = &joy_data[i];

  // The global Control gate (Joystick_Control, independent of the binding
  // table - see h.joyswis) can be turned off while a key this slot mapped is
  // still physically held; release_keys() cleanly lets go of anything held,
  // the same call used on detach, rather than just stopping and leaving a
  // key stuck down.
  if (!keyboard_control_on) {
    release_keys(i);
    return;
  }

  // Axis directions from the 8-bit mapped stick, using the same digital
  // dead-zone thresholds as encode_stick_serial_port (c.joyswis) - kept in
  // step with that by hand; if the two ever need to share, factor a helper.
  int up = 0, down = 0, left = 0, right = 0;

  if (j->mapped_x_8 != NOT_MAPPED) {
    struct axisdata_struct *ax = &j->axes[j->mapped_x_8];
    left  = (ax->val < ax->serial_port_lower_bound);
    right = (ax->val > ax->serial_port_upper_bound);
    if (ax->flip) { int t = left; left = right; right = t; }
  }

  if (j->mapped_y_8 != NOT_MAPPED) {
    struct axisdata_struct *ay = &j->axes[j->mapped_y_8];
    up   = (ay->val < ay->serial_port_lower_bound);
    down = (ay->val > ay->serial_port_upper_bound);
    if (ay->flip) { int t = up; up = down; down = t; }
  }

  set_key(j->key_up,    up,    &key_dir_held[i][0]);
  set_key(j->key_down,  down,  &key_dir_held[i][1]);
  set_key(j->key_left,  left,  &key_dir_held[i][2]);
  set_key(j->key_right, right, &key_dir_held[i][3]);

  // Buttons: key_buttons[b] is the KeyNo for physical button b.
  for (uint32_t b = 0; b < JOY_BUTTONS; b++) {
    if (j->key_buttons[b] == NOT_MAPPED) {
      // Still release if we somehow hold one for a now-unmapped button.
      if (key_btn_held[i][b] != NOT_MAPPED)
        set_key(NOT_MAPPED, FALSE, &key_btn_held[i][b]);
      continue;
    }
    int pressed = (b < j->num_buttons) && j->buttons[b];
    set_key(j->key_buttons[b], pressed, &key_btn_held[i][b]);
  }
}


void release_keys(uint32_t i)
{
  for (uint32_t d = 0; d < KEY_DIR_COUNT; d++)
    if (key_dir_held[i][d] != NOT_MAPPED)
      set_key(NOT_MAPPED, FALSE, &key_dir_held[i][d]);

  for (uint32_t b = 0; b < JOY_BUTTONS; b++)
    if (key_btn_held[i][b] != NOT_MAPPED)
      set_key(NOT_MAPPED, FALSE, &key_btn_held[i][b]);
}
