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

#include "swis.h"
#include "Global/RISCOS.h"

#include "debugrep.h"
#include "usbjoystick.h"
#include "errors.h"
#include "joyhelp.h"
#include "rumble.h"

#include "cmdrumble.h"


#define DEFAULT_RUMBLE_MS  1000
#define MAX_NUMBER         0xFFFFFF
#define MAX_BYTES          RUMBLE_MAX_PACKET   // the command table allows 66 arguments


// A * command tail ends with a control character, not necessarily a NUL;
// terminate it there so strtok stays inside the tail.
static void terminate_tail(char *s)
{
  while ((unsigned char)*s >= ' ') s++;
  *s = '\0';
}


// Decimal, or hex when hex_default; & or 0x forces hex. A missing or garbled
// number is rejected, where atoi would quietly give 0.
static int parse_number(const char *s, int hex_default, uint32_t *out)
{
  uint32_t base = hex_default ? 16 : 10;
  uint32_t value = 0;
  int digits = 0;

  if (s[0] == '&') {
    base = 16;
    s++;
  }
  else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    base = 16;
    s += 2;
  }

  for (; *s; s++) {
    uint32_t d;
    if (*s >= '0' && *s <= '9') d = *s - '0';
    else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10;
    else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10;
    else return FALSE;

    if (d >= base) return FALSE;
    value = value * base + d;
    if (value > MAX_NUMBER) return FALSE;
    digits++;
  }

  if (digits == 0) return FALSE;
  *out = value;
  return TRUE;
}


static _kernel_oserror *get_slot(const char *caller, const char *token, uint32_t *slot)
{
  if (token == NULL || !parse_number(token, FALSE, slot) || *slot >= Joystick_MaxSlots) {
    debug_printf("%s: bad slot '%s'\n", caller, token ? token : "");
    return geterror(Error_BadJoy);
  }

  return validate_joystick_slot_active(caller, *slot);
}


static _kernel_oserror *get_bytes(const char *caller, uint8_t *buf, uint32_t *len)
{
  char *tok;

  *len = 0;
  while ((tok = strtok(NULL, " ")) != NULL) {
    uint32_t v;
    if (!parse_number(tok, TRUE, &v) || v > 0xFF) {
      debug_printf("%s: bad byte '%s'\n", caller, tok);
      return geterror_p(Error_BadByte, tok);
    }
    if (*len >= MAX_BYTES)
      return geterror(Error_TooManyParms);
    buf[(*len)++] = (uint8_t) v;
  }

  return NULL;
}


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

  char *a = (char *) args;
  terminate_tail(a);

  char *t_slot = strtok(a, " ");
  char *t_low  = strtok(NULL, " ");
  char *t_high = strtok(NULL, " ");
  char *t_ms   = strtok(NULL, " ");

  uint32_t slot, low, high, ms = DEFAULT_RUMBLE_MS;

  _kernel_oserror *e = get_slot("USBJoystick_Rumble", t_slot, &slot);
  if (e) return e;

  if (!parse_number(t_low, FALSE, &low) || low > 0xFFFF)
    return geterror_p(Error_BadStrength, t_low);

  if (!parse_number(t_high, FALSE, &high) || high > 0xFFFF)
    return geterror_p(Error_BadStrength, t_high);

  if (t_ms != NULL && (!parse_number(t_ms, FALSE, &ms) || ms < 1 || ms > 0xFFFF))
    return geterror_p(Error_BadTime, t_ms);

  debug_printf("USBJoystick_Rumble: slot %u: low %u, high %u, %u ms\n", slot, low, high, ms);

  e = rumble_set(slot, low, high, ms);
  if (e) return e;

  if (low == 0 && high == 0)
    printf("Slot %u: rumble stopped\n", slot);
  else
    printf("Slot %u: rumble low %u, high %u for %u ms\n", slot, low, high, ms);

  return NULL;
}


_kernel_oserror *command_send_out(const char *args, int32_t argc)
{
  if (argc < 2 || argc > MAX_BYTES + 1)
    return geterror(Error_BadNoParms);

  char *a = (char *) args;
  terminate_tail(a);

  uint32_t slot;
  _kernel_oserror *e = get_slot("USBJoystick_SendOut", strtok(a, " "), &slot);
  if (e) return e;

  uint8_t buf[MAX_BYTES];
  uint32_t len;
  e = get_bytes("USBJoystick_SendOut", buf, &len);
  if (e) return e;

  debug_printf("USBJoystick_SendOut: slot %u: %u bytes\n", slot, len);

  e = rumble_send(slot, buf, len);
  if (e) return e;

  printf("Slot %u: sent %u bytes to OUT endpoint %u\n", slot, len, joy_data[slot].output.endpoint);
  return NULL;
}


_kernel_oserror *command_send_report(const char *args, int32_t argc)
{
  if (argc < 2 || argc > MAX_BYTES + 2)
    return geterror(Error_BadNoParms);

  char *a = (char *) args;
  terminate_tail(a);

  uint32_t slot;
  _kernel_oserror *e = get_slot("USBJoystick_SendReport", strtok(a, " "), &slot);
  if (e) return e;

  char *t_id = strtok(NULL, " ");
  uint32_t id;
  if (t_id == NULL || !parse_number(t_id, TRUE, &id) || id > 0xFF) {
    debug_printf("USBJoystick_SendReport: bad report id '%s'\n", t_id ? t_id : "");
    return geterror_p(Error_BadByte, t_id ? t_id : "");
  }

  uint8_t buf[MAX_BYTES];
  uint32_t len;
  e = get_bytes("USBJoystick_SendReport", buf, &len);
  if (e) return e;

  debug_printf("USBJoystick_SendReport: slot %u: report %u, %u bytes\n", slot, id, len);

  e = rumble_send_report(slot, (uint8_t) id, buf, len);
  if (e) return e;

  printf("Slot %u: sent report %u with %u data bytes over the control pipe\n", slot, id, len);
  return NULL;
}
