
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "debugrep.h"
#include "usbjoystick.h"
#include "USBJoystickHdr.h"   // Module_Title
#include "errors.h"
#include "joyhelp.h"     // validate_joystick_slot*
#include "adc.h"         // ADC_CHANNELS, ADC_BUTTONS

#include "binding.h"


// Error_Syntax's message text takes the module title (%0), so it must be looked
// up with geterror_p (which supplies it), never plain geterror.
static _kernel_oserror *syntax_error(void)
{
  return geterror_p(Error_Syntax, Module_Title);
}


// The *USBJoystick_Map / _Unmap / _Mappings command layer - a readable text
// front end over the binding-table SWIs (see c.binding). Full grammar in
// doc.APIs section 6. Keycodes are numeric RISC OS internal key numbers (no
// name table). Full validation is done by binding_add/binding_write; the
// parser only turns text into a joy_binding.


// ASCII lower-case, done inline rather than via the shared C library's
// tolower(): in this module context the library ctype lookup faults (data
// abort in ROM) on some inputs, so we never call it.
static int lc(int c)
{
  return (c >= 'A' && c <= 'Z') ? c + 32 : c;
}

// Case-insensitive equality (the DDE C library has no portable strcasecmp).
static int ci_equal(const char *a, const char *b)
{
  while (*a && *b) {
    if (lc((unsigned char)*a++) != lc((unsigned char)*b++)) return 0;
  }
  return *a == *b;
}

static int all_digits(const char *s)
{
  if (!*s) return 0;
  for (; *s; s++)
    if (*s < '0' || *s > '9') return 0;
  return 1;
}

// A * command tail is terminated by a control character (< 32), not always a
// NUL, and the bytes beyond it aren't ours. NUL-terminate the string at that
// control char so strtok and the string comparisons never run off the end of
// the tail into unrelated memory.
static void terminate_tail(char *s)
{
  while ((unsigned char)*s >= ' ') s++;
  *s = '\0';
}

// Split "prefix:rest" - returns rest and NUL-terminates prefix in place, or
// NULL if there's no ':'.
static char *split_colon(char *tok)
{
  char *colon = strchr(tok, ':');
  if (!colon) return NULL;
  *colon = '\0';
  return colon + 1;
}


// Parse a source token into b->source_type/source_index. slot must be a valid
// active slot (axis names are resolved against its axes[]).
static _kernel_oserror *parse_source(uint32_t slot, char *tok, joy_binding *b)
{
  char *rest = split_colon(tok);
  if (!rest) return syntax_error();

  if (ci_equal(tok, "axis")) {
    b->source_type = JB_SRC_AXIS;

    if (all_digits(rest)) {
      b->source_index = (uint8_t)atoi(rest);
      return NULL;
    }
    // resolve by axis name (as shown by *USBJoystick_Read)
    struct joydata_struct *j = &joy_data[slot];
    for (uint32_t a = 0; a < j->num_axes; a++) {
      if (j->axes[a].name && ci_equal(rest, j->axes[a].name)) {
        b->source_index = (uint8_t)a;
        return NULL;
      }
    }
    debug_printf("command_map: no axis named '%s' on slot %u\n", rest, slot);
    return syntax_error();
  }

  if (ci_equal(tok, "btn")) {
    b->source_type = JB_SRC_BUTTON;
    b->source_index = (uint8_t)atoi(rest);
    return NULL;
  }

  debug_printf("command_map: bad source prefix '%s'\n", tok);
  return syntax_error();
}


// Parse a target token into b->target_kind/target_index.
static _kernel_oserror *parse_target(char *tok, joy_binding *b)
{
  char *rest = split_colon(tok);
  if (!rest) return syntax_error();

  // "stick" = the classic Joystick_Read output (8/16-bit X/Y + buttons) - the
  // "Stick N" the JoySetup UI shows. Distinct from "slot" (the device id).
  if (ci_equal(tok, "stick")) {
    b->target_kind = JB_TGT_SLOT_AXIS;
    if      (ci_equal(rest, "8bitX"))  b->target_index = JB_AXIS_8X;
    else if (ci_equal(rest, "8bitY"))  b->target_index = JB_AXIS_8Y;
    else if (ci_equal(rest, "16bitX")) b->target_index = JB_AXIS_16X;
    else if (ci_equal(rest, "16bitY")) b->target_index = JB_AXIS_16Y;
    else return syntax_error();
    return NULL;
  }

  if (ci_equal(tok, "stickbtn")) {
    b->target_kind = JB_TGT_SLOT_BTN;
    b->target_index = (uint16_t)atoi(rest);
    return NULL;
  }

  if (ci_equal(tok, "mouse")) {
    if      (ci_equal(rest, "X")) { b->target_kind = JB_TGT_MOUSE_MOVE; b->target_index = JB_MOUSE_X; }
    else if (ci_equal(rest, "Y")) { b->target_kind = JB_TGT_MOUSE_MOVE; b->target_index = JB_MOUSE_Y; }
    else if (ci_equal(rest, "S")) { b->target_kind = JB_TGT_MOUSE_BTN;  b->target_index = JB_MOUSE_SELECT; }
    else if (ci_equal(rest, "M")) { b->target_kind = JB_TGT_MOUSE_BTN;  b->target_index = JB_MOUSE_MENU; }
    else if (ci_equal(rest, "A")) { b->target_kind = JB_TGT_MOUSE_BTN;  b->target_index = JB_MOUSE_ADJUST; }
    else return syntax_error();
    return NULL;
  }

  if (ci_equal(tok, "key")) {
    b->target_kind = JB_TGT_KEY;
    b->target_index = (uint16_t)atoi(rest);
    return NULL;
  }

  if (ci_equal(tok, "adc")) {
    b->target_kind = JB_TGT_ADC;
    b->target_index = (uint16_t)atoi(rest);
    return NULL;
  }

  if (ci_equal(tok, "adcbtn")) {
    b->target_kind = JB_TGT_ADC_BTN;
    b->target_index = (uint16_t)atoi(rest);
    return NULL;
  }

  debug_printf("command_map: bad target prefix '%s'\n", tok);
  return syntax_error();
}


// Parse a "<source> <target>" pair (+ invert) into a binding. Shared by the
// *USBJoystick_Map command and the config-store reader (bind= lines). NOTE:
// src_tok/tgt_tok are modified in place (the ':' is NUL-terminated), so pass
// writable buffers. Fills *b (memset here); does NOT validate ranges - that's
// binding_add/binding_write's job.
_kernel_oserror *binding_parse_spec(uint32_t slot, char *src_tok, char *tgt_tok,
                                    int invert, joy_binding *b)
{
  memset(b, 0, sizeof *b);
  if (invert) b->flags |= JB_FLAG_INVERT;

  _kernel_oserror *err = parse_source(slot, src_tok, b);
  if (err) return err;
  return parse_target(tgt_tok, b);
}


_kernel_oserror *command_map(const char *args, int32_t argc)
{
  if (argc < 3 || argc > 4)
    return geterror(Error_BadNoParms);

  const char sp[2] = " ";
  char *a = (char *)args;
  terminate_tail(a);

  char *t = strtok(a, sp);
  if (!t || !all_digits(t)) {     // catch a missing/garbage slot (atoi would silently give 0)
    debug_printf("command_map: first argument must be a slot number\n");
    return syntax_error();
  }
  uint32_t slot = (uint32_t)atoi(t);

  char *src = strtok(NULL, sp);
  char *tgt = strtok(NULL, sp);
  char *inv = strtok(NULL, sp);

  if (!src || !tgt)
    return geterror(Error_BadNoParms);

  // Slot must be valid+active before we resolve axis names against it.
  _kernel_oserror *err = validate_joystick_slot("Joystick_Map", slot);
  if (err) return err;
  err = validate_joystick_slot_active("Joystick_Map", slot);
  if (err) return err;

  int invert = 0;
  if (inv) {
    if (ci_equal(inv, "invert")) invert = 1;
    else return syntax_error();
  }

  joy_binding b;
  err = binding_parse_spec(slot, src, tgt, invert, &b);
  if (err) return err;

  return binding_add(slot, &b);
}


_kernel_oserror *command_unmap(const char *args, int32_t argc)
{
  if (argc < 1 || argc > 3)
    return geterror(Error_BadNoParms);

  const char sp[2] = " ";
  char *a = (char *)args;
  terminate_tail(a);

  char *t = strtok(a, sp);
  if (!t || !all_digits(t)) {     // catch a missing/garbage slot (atoi would silently give 0)
    debug_printf("command_unmap: first argument must be a slot number\n");
    return syntax_error();
  }
  uint32_t slot = (uint32_t)atoi(t);

  _kernel_oserror *err = validate_joystick_slot("Joystick_Unmap", slot);
  if (err) return err;
  err = validate_joystick_slot_active("Joystick_Unmap", slot);
  if (err) return err;

  char *src = strtok(NULL, sp);
  if (!src) {
    binding_clear(slot, -1, 0);       // no source: clear the whole device
    return NULL;
  }

  char *tgt = strtok(NULL, sp);

  joy_binding b;
  memset(&b, 0, sizeof b);
  err = parse_source(slot, src, &b);
  if (err) return err;

  if (!tgt) {
    binding_clear(slot, b.source_type, b.source_index);   // all of this source
    return NULL;
  }

  err = parse_target(tgt, &b);
  if (err) return err;
  binding_remove(slot, &b);           // just this one source->target binding
  return NULL;
}


// --- unparse: binding -> canonical token text ---
// Produce the same "axis:Y" / "stick:16bitY" tokens the Map command accepts,
// for both the readable listing and the config-store writer (bind= lines).

void binding_format_source(const joy_binding *b, uint32_t slot, char *buf, size_t buflen)
{
  if (b->source_type == JB_SRC_AXIS) {
    struct joydata_struct *j = &joy_data[slot];
    const char *name = (b->source_index < j->num_axes) ? j->axes[b->source_index].name : NULL;
    if (name) snprintf(buf, buflen, "axis:%s", name);
    else      snprintf(buf, buflen, "axis:%u", b->source_index);
  }
  else {
    snprintf(buf, buflen, "btn:%u", b->source_index);
  }
}

void binding_format_target(const joy_binding *b, char *buf, size_t buflen)
{
  switch (b->target_kind) {
    case JB_TGT_SLOT_AXIS:
      switch (b->target_index) {
        case JB_AXIS_8X:  snprintf(buf, buflen, "stick:8bitX");  break;
        case JB_AXIS_8Y:  snprintf(buf, buflen, "stick:8bitY");  break;
        case JB_AXIS_16X: snprintf(buf, buflen, "stick:16bitX"); break;
        case JB_AXIS_16Y: snprintf(buf, buflen, "stick:16bitY"); break;
        default:          snprintf(buf, buflen, "stick:?");      break;
      }
      break;
    case JB_TGT_SLOT_BTN:   snprintf(buf, buflen, "stickbtn:%u", b->target_index); break;
    case JB_TGT_MOUSE_MOVE: snprintf(buf, buflen, "mouse:%s", b->target_index == JB_MOUSE_X ? "X" : "Y"); break;
    case JB_TGT_MOUSE_BTN:
      snprintf(buf, buflen, "mouse:%s", b->target_index == JB_MOUSE_SELECT ? "S" :
                                        b->target_index == JB_MOUSE_MENU   ? "M" : "A");
      break;
    case JB_TGT_KEY:     snprintf(buf, buflen, "key:%u", b->target_index);    break;
    case JB_TGT_ADC:     snprintf(buf, buflen, "adc:%u", b->target_index);    break;
    case JB_TGT_ADC_BTN: snprintf(buf, buflen, "adcbtn:%u", b->target_index); break;
    default:             snprintf(buf, buflen, "?");                          break;
  }
}


// --- readable listing (the text mirror of the Stick verification screen) ---

_kernel_oserror *command_mappings(const char *args, int32_t argc)
{
  if (argc != 1)
    return geterror(Error_BadNoParms);

  const char sp[2] = " ";
  char *a = (char *)args;
  terminate_tail(a);

  char *t = strtok(a, sp);
  if (!t || !all_digits(t)) {
    debug_printf("command_mappings: argument must be a slot number\n");
    return syntax_error();
  }
  uint32_t slot = (uint32_t)atoi(t);

  _kernel_oserror *err = validate_joystick_slot("Joystick_Mappings", slot);
  if (err) return err;
  err = validate_joystick_slot_active("Joystick_Mappings", slot);
  if (err) return err;

  struct joydata_struct *j = &joy_data[slot];

  printf("Slot %u (%s - %s): %u binding%s\n",
         slot, j->manufacturer, j->product,
         j->num_bindings, j->num_bindings == 1 ? "" : "s");

  // Header, so it's clear which side is the physical input and which is the
  // output it drives (the same source/target the *Map and *Unmap commands take).
  printf("  %-12s    %-14s\n", "source (in)", "target (out)");

  for (uint32_t i = 0; i < j->num_bindings; i++) {
    const joy_binding *b = &j->bindings[i];
    char sbuf[32], tbuf[32];
    binding_format_source(b, slot, sbuf, sizeof sbuf);
    binding_format_target(b, tbuf, sizeof tbuf);
    printf("  %-12s -> %-14s%s\n", sbuf, tbuf,
           (b->flags & JB_FLAG_INVERT) ? "  (inverted)" : "");
  }

  return NULL;
}
