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

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

#include "oslib/os.h"
#include "oslib/osfind.h"
#include "oslib/osgbpb.h"
#include "oslib/osbyte.h"
#include "oslib/devicefs.h"
#include "oslib/resourcefs.h"

#include "debugrep.h"
#include "usbjoystick.h"
#include "device.h"
#include "joyhelp.h"
#include "joyswis.h"
#include "mouse.h"
#include "map.h"
#include "usb.h"
#include "usbservice.h"

#include "joydev.h"



//extern const uint32_t supported_axes_usage[];
//extern const char *axes_names[];





extern struct hid_item *itemdup(struct hid_item *s);







_kernel_oserror* joy_check_device(const usb_service_t* dev, void* handle)
{
  // check this usb device (dev is a connected usb device) to see if it looks like a joystick
  IGNORE(handle);

  debug_printf("joy_check_device: (usb service call) +++++++++++++++++++++++++++++++++++\n");
  debug_printf("joy_check_device: Checking device name '%s' (sum_length %u, descriptors_offset %u, class %02x, subclass %02x, protocol %02x)\n",
    dev->name,
    dev->sum_length,
    dev->descriptors_offset,
    dev->device.bDeviceClass,
    dev->device.bDeviceSubClass,
    dev->device.bDeviceProtocol);

  // 'dev->name' is 'USB4' for example, which is our 'connected USB device' handle
  usb_device_t* device = (usb_device_t*) dev->name;

  usb_device_descriptor_t* ddesc = NULL;
  usb_config_descriptor_t* cdesc = NULL;
  usb_string_descriptor_t* sdesc = NULL;
  usb_interface_descriptor_t* idesc = NULL;
  usb_endpoint_descriptor_t* edesc = NULL;
  uint32_t csDeviceClass = 0;
  uint32_t csInterfaceClass = 0;

  debug_printf("joy_check_device: Getting USB descriptors\n");
  usb_descriptor_t *des = usb_get_descriptor_list(dev);

  // track if we've parsed something successfully - if so, we back-fill interface+endpoint info
  osbool got_valid_joystick = FALSE;

  debug_printf("joy_check_device: Parsing USB descriptors\n");
  for (char* pos=(char*)des; des->bLength; pos+=des->bLength,des=(usb_descriptor_t*)pos)
  {
    osbool unknown = FALSE;
    switch (des->bDescriptorType)
    {
      case USB_DEVICE_DESCRIPTOR:
        {
          ddesc = (usb_device_descriptor_t*)pos;
          csDeviceClass = ((ddesc->bDeviceClass << 16) | (ddesc->bDeviceSubClass << 8) | ddesc->bDeviceProtocol);
          debug_printf("joy_check_device: Device descriptor: Length %d, Type %d, Configurations %d, csDeviceClass 0x%x\n", ddesc->bLength, ddesc->bDescriptorType, ddesc->bNumConfigurations, csDeviceClass);
          debug_printf("joy_check_device: Device descriptor: iManufacturer %d, iProduct %d, iSerialNumber %d\n", ddesc->iManufacturer, ddesc->iProduct, ddesc->iSerialNumber);
          debug_printf("joy_check_device: Device descriptor: Vendor 0x%04x, Product 0x%04x, Device 0x%04x\n", ddesc->idVendor, ddesc->idProduct, ddesc->bcdDevice);
        }
        break;

      case USB_CONFIGURATION_DESCRIPTOR:
        {
          cdesc = (usb_config_descriptor_t*)pos;
          debug_printf("joy_check_device: Configuration descriptor: Length %u, Type %u, TotalLength %u, NumInterfaces %d \n", cdesc->bLength, cdesc->bDescriptorType, cdesc->wTotalLength, cdesc->bNumInterface);
        }
        break;

      case USB_STRING_DESCRIPTOR:
        {
          sdesc = (usb_string_descriptor_t*)pos;
          debug_printf("joy_check_device: String descriptor: Length %d, Type %d, String %d \n", sdesc->bLength, sdesc->bDescriptorType, sdesc->bString);
        }
        break;

      case USB_INTERFACE_DESCRIPTOR:
        {
          idesc = (usb_interface_descriptor_t*)pos;
          csInterfaceClass = ((idesc->bInterfaceClass << 16) | (idesc->bInterfaceSubClass << 8) | idesc->bInterfaceProtocol);
          debug_printf("joy_check_device: Interface descriptor: Length %u, DescriptorType %u, InterfaceNumber %d, InterfaceClass %d (3 is HID), iInterface %d, csInterfaceClass 0x%x\n", idesc->bLength, idesc->bDescriptorType, idesc->bInterfaceNumber, idesc->bInterfaceClass, idesc->iInterface, csInterfaceClass);
        }
        break;

      case USB_ENDPOINT_DESCRIPTOR:
        {
          edesc = (usb_endpoint_descriptor_t*)pos;
          debug_printf("joy_check_device: endpoint descriptor: EndpointAddress %d, (on InterfaceNumber %d)\n", edesc->bEndpointAddress, idesc->bInterfaceNumber);
          if (got_valid_joystick == TRUE)
          {
            debug_printf("joy_check_device: endpoint descriptor: We think this is a joystick (got HID report descriptor and it looks good), so set up\n");
            set_up_joystick(dev, device, ddesc, idesc, edesc);
            // reset validity so we can re-test the next endpoint
            got_valid_joystick = FALSE;
          }
          else
          {
            debug_printf("joy_check_device: endpoint descriptor: We think this isn't a joystick - ignore this endpoint\n");
          }
        }
        break;

      default:
        {
          debug_printf("joy_check_device: Other descriptor: type is %u - not Device/Config/Interface/Endpoint\n (type 33 is HID device descriptor)", des->bDescriptorType);
          debug_printf("joy_check_device: csDeviceClass 0x%x, csInterfaceClass 0x%x\n", csDeviceClass, csInterfaceClass);
          debug_printf("joy_check_device: InterfaceClass 0x%x, InterfaceSubClass 0x%x, InterfaceProtocol %u, Vendor 0x%04x, Product 0x%04x\n", idesc->bInterfaceClass, idesc->bInterfaceSubClass, idesc->bInterfaceProtocol, ddesc->idVendor, ddesc->idProduct);

          struct joy_device_struct *joy_device = lookup_joy_device(idesc->bInterfaceClass, idesc->bInterfaceSubClass, idesc->bInterfaceProtocol, ddesc->idVendor, ddesc->idProduct);
          if (joy_device) {
            debug_printf("joy_check_device: Supported device: %s\n", joy_device->name);
            void *report_descriptor = joy_device->report_descriptor;
            uint8_t report_descriptor_length = joy_device->report_descriptor_length;
            if (report_descriptor != 0 && report_descriptor_length > 0) {
              debug_printf("joy_check_device: Using baked-in HID report descriptor\n");
            }
            else {
              // this device doesn't need a baked-in descriptor, so get it from the device
              debug_printf("joy_check_device: Assume we are looking at a HID device descriptor\n");
              usb_hid_device_descriptor_t* hdesc = (usb_hid_device_descriptor_t*)pos;
              debug_printf("joy_check_device: Getting HID report descriptor from device...\n");
              report_descriptor_length = hdesc->wReportDescLength;
              report_descriptor = get_report_descriptor(device, hdesc);
            }
            if (report_descriptor == 0 || report_descriptor_length == 0) {
              debug_printf("joy_check_device: Cannot get HID report descriptor for device - aborting\n");
            }
            else {
              // now report_descriptor is a valid HID report descriptor (baked -in or read from device)
              // note that joy_index will be moved on by this next function
              got_valid_joystick = parse_usb_hid_device_descriptor(report_descriptor_length, report_descriptor);
              debug_printf("joy_check_device: Parsed HID report descriptor, got_valid_joystick is %d\n", got_valid_joystick);
              // perform custom init sequence
              if (got_valid_joystick && joy_device->init_command_length > 0) {
                debug_printf("joy_check_device: Sending custom initialisation command\n");
                _kernel_oserror* e = usb_hid_set_output_report(device, idesc->bInterfaceNumber, joy_device->init_command, joy_device->init_command_length);
                if (e) {
                  debug_printf("joy_check_device: Unable to initialise device %s: %s\n", device, e->errmess);
                }
              }
              // note device driver info
              if (got_valid_joystick)
                joy_data[joy_index].device = joy_device;
                // we need to free the memory used by getting our own descriptor
                // todo: do we?  is this right?
              if (joy_device->report_descriptor == 0) {
                free(report_descriptor);
              }
            }
          }
          else {
            debug_printf("joy_check_device: Not a supported device - aborting\n");
          }

          if (unknown)
            debug_printf("joy_check_device: Unknown descriptor\n");
        }
    }
  }

  // This will never return an error (we only have the return type setup to satisfy usb.c)
  return NULL;
}










uint8_t *get_report_descriptor(usb_device_t* device, usb_hid_device_descriptor_t *hdesc)
{
  int32_t report_descriptor_type = UT_GET_TYPE(hdesc->bReportDescType);
  int32_t report_descriptor_id = (hdesc->bReportDescType & 0xf);

  debug_printf("get_report_descriptor: HID device descriptor: Length %u, DescriptorType %u, HID %u, country %u, NumDescriptors %u, ReportDescType %u, ReportDescLength %u\n", hdesc->bLength, hdesc->bDescriptorType, hdesc->bcdHID, hdesc->bCountryCode, hdesc->bNumDescriptors, hdesc->bReportDescType, hdesc->wReportDescLength);

  debug_printf("get_report_descriptor: Calculated ReportDescriptorType %d, ReportDescriptorId %d\n", report_descriptor_type, report_descriptor_id);

  if (report_descriptor_type == UT_CLASS && report_descriptor_id == 2) {
    uint8_t *hidrepdesc = calloc(1, hdesc->wReportDescLength);
    if (hidrepdesc != NULL)
    {
      debug_printf("get_report_descriptor: Getting HID report descriptor\n");
      // is it correct to pass in hdesc->bReportDescType here for the type?  or should it be report_descriptor_type?
      _kernel_oserror* e = usb_get_descriptor(device, hdesc->bReportDescType, hdesc->wReportDescLength, hidrepdesc);
      if (e)
      {
        debug_printf("get_report_descriptor: Unable to get HID report descriptor: %s\n", e->errmess);
        free(hidrepdesc);
      }
      else
      {
        return hidrepdesc;
      }
    }
    else {
      debug_printf("get_report_descriptor: Failed to allocate memory for getting HID report descriptor - aborting\n");
    }
  }
  else {
    debug_printf("get_report_descriptor: report_descriptor_type is wrong (must be %u) and/or report_descriptor_id is wrong (must be 2) - aborting\n", UT_CLASS);
  }

  return 0;

}





void set_up_joystick(const usb_service_t* dev, usb_device_t* device, usb_device_descriptor_t* ddesc, usb_interface_descriptor_t* idesc, usb_endpoint_descriptor_t* edesc)
{
  debug_printf("set_up_joystick: Fill up joystick info for device '%s', interface %u, endpoint %u\n", device, idesc->bInterfaceNumber, edesc->bEndpointAddress);

  // ids
  joy_data[joy_index].vendor_id = ddesc->idVendor;
  joy_data[joy_index].product_id = ddesc->idProduct;
  joy_data[joy_index].device_id = ddesc->bcdDevice;

  // grab product/manufacturer/serial strings
  if (ddesc->iManufacturer > 0) {
    usb_string_descriptor_t *man = usb_get_device_string_descriptor(device, ddesc->iManufacturer);
    if (man) {
      usb_string_descriptor_to_ascii(man, joy_data[joy_index].manufacturer);
      debug_printf("set_up_joystick: String index %u, Manufacturer: '%s'\n", ddesc->iManufacturer, joy_data[joy_index].manufacturer);
      free((void*)man);
    }
  }
  else {
    debug_printf("set_up_joystick: No manufacturer string\n");
  }

  if (ddesc->iProduct > 0) {
    usb_string_descriptor_t *product = usb_get_device_string_descriptor(device, ddesc->iProduct);
    if (product) {
      usb_string_descriptor_to_ascii(product, joy_data[joy_index].product);
      debug_printf("set_up_joystick: String index %u, Product: '%s'\n", ddesc->iProduct, joy_data[joy_index].product);
      free((void*)product);
    }
  }
  else {
    debug_printf("set_up_joystick: No product string\n");
  }

  if (ddesc->iSerialNumber > 0) {
    usb_string_descriptor_t *serial = usb_get_device_string_descriptor(device, ddesc->iSerialNumber);
    if (serial) {
      usb_string_descriptor_to_ascii(serial, joy_data[joy_index].serial);
      debug_printf("set_up_joystick: String index %u, Serial: '%s'\n", ddesc->iSerialNumber, joy_data[joy_index].serial);
      free((void*)serial);
    }
  }
  else {
    debug_printf("set_up_joystick: No serial number string\n");
  }

  // construct devicefs path
  memcpy(joy_data[joy_index].device_name, dev->name, USB_DEVICE_NAME_LENGTH);
  joy_data[joy_index].interface = idesc->bInterfaceNumber;
  joy_data[joy_index].endpoint = edesc->bEndpointAddress & 0x1f;
  sprintf(joy_data[joy_index].usb_path, USB_PATH_SPECIFIER, joy_data[joy_index].interface, joy_data[joy_index].endpoint, joy_data[joy_index].device_name);
  debug_printf("set_up_joystick: Constructed DeviceFS path for stick id %u, path is: %s\n", joy_index, joy_data[joy_index].usb_path);

  // start talking to it
  debug_printf("set_up_joystick: Open a connection to the joystick (id is %u)\n", joy_index);
  open_joystick(joy_index);
}






osbool parse_usb_hid_device_descriptor(uint32_t length, uint8_t* buf)
{
  osbool is_joystick = FALSE;
  osbool got_something = FALSE;
  report_desc_t rd = NULL;

  joy_index = next_available_joystick_index();
  if (joy_index == NONE_AVAILABLE)
  {
    debug_printf("parse_usb_hid_device_descriptor: Cannot parse this HID device descriptor - we have run out of joysticks!\n");
    return FALSE;
  }
  debug_printf("parse_usb_hid_device_descriptor: Will use joystick number %d (next available) for this HID parse\n", joy_index);

  joy_data[joy_index].in_use = TRUE;

  // share HID report descriptor data with libusbhid
  rd = hid_use_report_desc(buf, length);

  if (rd == NULL) {
    debug_printf("parse_usb_hid_device_descriptor: Failed to share report descriptor with libusbhid - aborting parse\n");
    return FALSE;
  }

  debug_printf("parse_usb_hid_device_descriptor: Shared report descriptor 'rd' (size %u) with libusbhid\n", rd->size);

  // report_id is 0 by default, but some devices need a different value
  // todo: this could be dubious: what if we need to support MULTIPLE reportids?  Would they be two sticks?!
  joy_data[joy_index].has_report_id = FALSE;
  int32_t report_id = NO_REPORT_ID;
  int32_t custom_report_id = hid_get_report_id(rd);       // returns -1 for 'not used/applicable'
  if (custom_report_id > 0) {
    debug_printf("parse_usb_hid_device_descriptor: HID report descriptors use a reportid, so switching from %d to %d\n", report_id, custom_report_id);
    report_id = custom_report_id;
    joy_data[joy_index].has_report_id = TRUE;
  }

  // we only want to see inputs
  uint32_t size = hid_report_size(rd, hid_input, report_id);

  // todo: does this size include the 1st byte that might be a reportid?  
  joy_data[joy_index].report_length = size;

  if (size > 0) {
    debug_printf("parse_usb_hid_device_descriptor: Got hid report size %d for reportid %d, has_report_id is %d\n", size, report_id, joy_data[joy_index].has_report_id);
  }
  else {
    debug_printf("parse_usb_hid_device_descriptor: Error: HID report size is zero, abandoning parse\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  // reserve space for storing raw data (now we know the required size)
  joy_data[joy_index].data_buf = malloc(size);
  if (joy_data[joy_index].data_buf == NULL)
  {
    debug_printf("parse_usb_hid_device_descriptor: Error: Not enough memory for joystick raw report data buffer.\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  // initialise parsing data

  struct hid_data *d;
  struct hid_item h;

  // start parsing

  //debug_printf("parse_usb_hid_device_descriptor: Starting parse of HID report descriptor\n");
  uint32_t l = 1;
  IGNORE(l);
  for (d = hid_start_parse(rd, 1 << hid_input, report_id); hid_get_item(d, &h); )
  {
    int32_t page = HID_PAGE(h.usage);
    int32_t usage = HID_USAGE(h.usage);

    //debug_printf("parse_usb_hid_device_descriptor: HID parser 1: l %u, pos %u, h.usage=0x%x, page=0x%x, usage=0x%x, kind %d, reportid %d\n", l++, h.pos, h.usage, page, usage, h.kind, h.report_ID);

    is_joystick = is_joystick ||
        (
            h.kind == hid_collection &&
            page == HUP_GENERIC_DESKTOP &&
            (usage == HUG_JOYSTICK || usage == HUG_GAME_PAD)
        );

    if (h.kind != hid_input) continue;

    if (!is_joystick) continue;

    //debug_printf("parse_usb_hid_device_descriptor: HID parser 2: is_joystick %d\n", is_joystick);

    if (page == HUP_GENERIC_DESKTOP)
    {
      if (usage == HUG_HAT_SWITCH)
      {
        got_something = TRUE;
        if (joy_data[joy_index].hat_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got HAT switch, copying\n", h.report_ID);
          joy_data[joy_index].hat_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_UP)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_up_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad up, copying\n", h.report_ID);
          joy_data[joy_index].dpad_up_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_DOWN)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_down_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad down, copying\n", h.report_ID);
          joy_data[joy_index].dpad_down_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_LEFT)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_left_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad left, copying\n", h.report_ID);
          joy_data[joy_index].dpad_left_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_RIGHT)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_right_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad right, copying\n", h.report_ID);
          joy_data[joy_index].dpad_right_item = itemdup(&h);
        }
      }

      else if (supported_axes_usage[usage])
      {
        uint32_t type = supported_axes_usage[usage];
        IGNORE(type);
        //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got supported axis, usage is 0x%x, type is %u\n", h.report_ID, usage, type);
        got_something = TRUE;
        // see if we can find somewhere to copy the axis item
        for (uint32_t j=0; j<JOY_USAGE_AXES; j++) {
          if (joy_data[joy_index].axes_items[j] == NULL) {
            //debug_printf("HID parser:   copying axis HID item to index %u\n", j);
            joy_data[joy_index].axes_items[j] = itemdup(&h);
            break;
          }
          else {
            //debug_printf("parse_usb_hid_device_descriptor: HID parser:   index %u is full, will try next index\n", j);
          }
        }
      }

      else
      {
        debug_printf("HID parser:   reportid %d, usage is unsupported (not axis/dpad/hat, code 0x%x)\n", h.report_ID, usage);
      }

    }

    else if (page == HUP_BUTTON)
    {
      if ((usage > 0) && (usage <= JOY_BUTTONS))
      {
        got_something = TRUE;
        //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got button, setting up button %d\n", h.report_ID, usage-1);

        if (joy_data[joy_index].button_items[usage-1] == NULL)
          joy_data[joy_index].button_items[usage-1] = itemdup(&h);

        if (usage > joy_data[joy_index].num_buttons)
          joy_data[joy_index].num_buttons = usage;
      }
    }

    else
    {
      //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, ignoring item where page %u and usage %u\n", h.report_ID, page, usage);
    }

  }  // end of for (each hid_item)

  hid_end_parse(d);
  debug_printf("parse_usb_hid_device_descriptor: Parse completed - freeing libusbhid report descriptor memory\n");
  hid_dispose_report_desc(rd);

  if (!got_something)
  {
    // nothing useful in this hid descriptor
    debug_printf("parse_usb_hid_device_descriptor: Got nothing - free raw data buffer memory and exit\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  debug_printf("parse_usb_hid_device_descriptor: Analysing parse output to build axes info\n");

  // set up analogue axes
  for (uint32_t j=0; j<JOY_USAGE_AXES; j++)
  {
    struct hid_item *hid_item = joy_data[joy_index].axes_items[j];
    if (hid_item != NULL)
    {
      // we have a HID item for an axis, so build an axisdata_struct for it
      struct axisdata_struct *axis = &(joy_data[joy_index].axes[j]);
      joy_data[joy_index].num_axes++;

      uint32_t item_usage = HID_USAGE(hid_item->usage);

      axis->type = supported_axes_usage[item_usage];
      axis->name = (char*)axes_names[axis->type];
      axis->min = hid_item->logical_minimum;
      axis->max = hid_item->logical_maximum;
      axis->mid = ((axis->max - axis->min + 1) / 2) + axis->min;
      axis->val = axis->mid;
      axis->is_digital = FALSE;

      debug_printf("parse_usb_hid_device_descriptor: Axis %d, item_usage is %u, type is %u, name is %s\n", j, item_usage, axis->type, axis->name);
      
      uint32_t range = (axis->max) - (axis->min);
      uint32_t deadzone = (range) / 8;

      axis->serial_port_lower_bound = axis->mid - deadzone;
      axis->serial_port_upper_bound = axis->mid + deadzone;

      axis->acorn_slope_div_8 = (range > ACORN_AXIS_VALUES_8);
      if (axis->acorn_slope_div_8)
        axis->acorn_slope_8 = round_up_down((float)range / (float)ACORN_AXIS_VALUES_8);
      else
        axis->acorn_slope_8 = round_up_down((float)ACORN_AXIS_VALUES_8 / (float)range);

      axis->acorn_slope_div_16 = (range > ACORN_AXIS_VALUES_16);
      if (axis->acorn_slope_div_16)
        axis->acorn_slope_16 = round_up_down((float)range / (float)ACORN_AXIS_VALUES_16);
      else
        axis->acorn_slope_16 = round_up_down((float)ACORN_AXIS_VALUES_16 / (float)range);

      axis->pc_slope_div = (range > 100);
      if (axis->pc_slope_div)
        axis->pc_slope = round_up_down((float)range / (float)100);
      else
        axis->pc_slope = round_up_down((float)100 / (float)range);

    }
  }

  // if we have a HAT switch, then add two axes for it (x and y), if we have spare slots
  if (joy_data[joy_index].hat_item)
  {
    if (joy_data[joy_index].num_axes < JOY_AXES-2)
    {
      debug_printf("parse_usb_hid_device_descriptor: Stick has HAT switch and we have two axes spare to use\n");
      joy_data[joy_index].has_hat = TRUE;
      uint32_t n = joy_data[joy_index].num_axes;
      joy_data[joy_index].num_axes += 2;
      joy_data[joy_index].hat_axis = n;
      joy_data[joy_index].axes[n].type = AXIS_HATX;
      joy_data[joy_index].axes[n].name = (char*)axes_names[AXIS_HATX];
      joy_data[joy_index].axes[n+1].type = AXIS_HATY;
      joy_data[joy_index].axes[n+1].name = (char*)axes_names[AXIS_HATY];
      for (uint32_t j=0; j<2; j++)
      {
        joy_data[joy_index].axes[n+j].min = -1;
        joy_data[joy_index].axes[n+j].max = 1;
        joy_data[joy_index].axes[n+j].mid = 0;
        joy_data[joy_index].axes[n+j].val = 0;
        joy_data[joy_index].axes[n+j].serial_port_lower_bound = 0;
        joy_data[joy_index].axes[n+j].serial_port_upper_bound = 0;
        joy_data[joy_index].axes[n+j].acorn_slope_8 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_8 = FALSE;
        joy_data[joy_index].axes[n+j].acorn_slope_16 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_16 = FALSE;
        joy_data[joy_index].axes[n+j].pc_slope = 1;
        joy_data[joy_index].axes[n+j].pc_slope_div = FALSE;
        joy_data[joy_index].axes[n+j].is_digital = TRUE;
      }
    }
    else
    {
      // too many axes to support hat
      debug_printf("parse_usb_hid_device_descriptor: Too many axis to support HAT (got %d, max is %d ) - exiting\n", joy_data[joy_index].num_axes, JOY_AXES-1);
      clean_joystick_data_item(joy_index);
    }
  }

  // if we have a dpad, then add two axes for it (x and y), if we have spare slots
  if (joy_data[joy_index].dpad_up_item &&
      joy_data[joy_index].dpad_down_item &&
      joy_data[joy_index].dpad_left_item &&
      joy_data[joy_index].dpad_right_item)
  {
    if (joy_data[joy_index].num_axes < JOY_AXES-2)
    {
      debug_printf("parse_usb_hid_device_descriptor: Stick has DPAD and we have two axes spare to use\n");
      joy_data[joy_index].has_dpad = TRUE;
      uint32_t n = joy_data[joy_index].num_axes;
      joy_data[joy_index].num_axes += 2;
      joy_data[joy_index].dpad_axis = n;
      joy_data[joy_index].axes[n].type = AXIS_DPADX;
      joy_data[joy_index].axes[n].name = (char*)axes_names[AXIS_DPADX];
      joy_data[joy_index].axes[n+1].type = AXIS_DPADY;
      joy_data[joy_index].axes[n+1].name = (char*)axes_names[AXIS_DPADY];
      for (uint32_t j=0; j<2; j++)
      {
        joy_data[joy_index].axes[n+j].min = -1;
        joy_data[joy_index].axes[n+j].max = 1;
        joy_data[joy_index].axes[n+j].mid = 0;
        joy_data[joy_index].axes[n+j].val = 0;
        joy_data[joy_index].axes[n+j].serial_port_lower_bound = 0;
        joy_data[joy_index].axes[n+j].serial_port_upper_bound = 0;
        joy_data[joy_index].axes[n+j].acorn_slope_8 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_8 = FALSE;
        joy_data[joy_index].axes[n+j].acorn_slope_16 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_16 = FALSE;
        joy_data[joy_index].axes[n+j].pc_slope = 1;
        joy_data[joy_index].axes[n+j].pc_slope_div = FALSE;
        joy_data[joy_index].axes[n+j].is_digital = TRUE;
      }
    }
    else
    {
      // too many axes to support dpad
      debug_printf("parse_usb_hid_device_descriptor: Too many axis to support DPAD (got %d, max is %d ) - exiting\n", joy_data[joy_index].num_axes, JOY_AXES-1);
      clean_joystick_data_item(joy_index);
    }
  }

  debug_printf("parse_usb_hid_device_descriptor: Joystick/gamepad parsed and configured successfully (joy_index is %d)\n", joy_index);

  return TRUE;
}



void joystick_decode(uint32_t i)
{
  /* 0   1   2   3   4   5   6   7 */
  /* u  ru  r   rd   d  ld  l   lu */

  const int32_t hat_x[] = {
     0,  1,  1,  1,  0, -1, -1, -1
  };
  const int32_t hat_y[] = {
    -1, -1,  0,  1,  1,  1,  0, -1
  };

  int32_t d;
  uint32_t j;
  char *p = joy_data[i].data_buf;

  // extract the reportid byte (if there is one), falling back to 0 as a default for ('no report id')
  uint32_t reportid = 0;
  if (joy_data[i].has_report_id) {
    reportid = joy_data[i].data_buf[0];
    p++;
  }

  // run any custom validators on the data packet
  osbool valid = FALSE;
  if (joy_data[i].device->data_valid)
    valid = joy_data[i].device->data_valid(p);
  else
    valid = TRUE;

  if (!valid)
    return;

  // move past the data offset (if there is one - it may be zero)
  p = p + joy_data[i].device->report_data_offset;

  // now parse the HID report data (currently pointed to by p)

  // standard axes
  for (j=0; j<joy_data[i].num_axes; j++)
  {
    if (joy_data[i].axes_items[j] && joy_data[i].axes_items[j]->report_ID == reportid) {
      joy_data[i].axes[j].val = hid_get_data(p, joy_data[i].axes_items[j]);
    }
  }

  // HAT switch
  if (joy_data[i].has_hat && joy_data[i].hat_item->report_ID == reportid)
  {
    d = hid_get_data(p, joy_data[i].hat_item) - joy_data[i].hat_item->logical_minimum;
    j = joy_data[i].hat_axis;
    if (d < 0 || d >= 8)
    {
      joy_data[i].axes[j].val = 0;
      joy_data[i].axes[j+1].val = 0;
    }
    else
    {
      joy_data[i].axes[j].val = hat_x[d];
      joy_data[i].axes[j+1].val = hat_y[d];
    }
  }

  // DPAD
  if (joy_data[i].has_dpad)
  {
    j = joy_data[i].dpad_axis;

    // left/right (x axis)
    if (joy_data[i].dpad_left_item->report_ID == reportid && joy_data[i].dpad_right_item->report_ID == reportid) {
      d = hid_get_data(p, joy_data[i].dpad_left_item) - joy_data[i].dpad_left_item->logical_minimum;
      if (d) {
        joy_data[i].axes[j].val = -1;
      }
      else {
        d = hid_get_data(p, joy_data[i].dpad_right_item) - joy_data[i].dpad_right_item->logical_minimum;
        if (d) {
          joy_data[i].axes[j].val = 1;
        }
        else {
          joy_data[i].axes[j].val = 0;
        }
      }
    }

    // up/down (y axis)
    if (joy_data[i].dpad_up_item->report_ID == reportid && joy_data[i].dpad_down_item->report_ID == reportid) {
      d = hid_get_data(p, joy_data[i].dpad_up_item) - joy_data[i].dpad_up_item->logical_minimum;
      if (d) {
        joy_data[i].axes[j+1].val = -1;
      }
      else {
        d = hid_get_data(p, joy_data[i].dpad_down_item) - joy_data[i].dpad_down_item->logical_minimum;
        if (d) {
          joy_data[i].axes[j+1].val = 1;
        }
        else {
          joy_data[i].axes[j+1].val = 0;
        }
      }
    }
  }

  // buttons
  for (j=0; j<joy_data[i].num_buttons; j++)
  {
    if (joy_data[i].button_items[j] && joy_data[i].button_items[j]->report_ID == reportid)
      joy_data[i].buttons[j] = (hid_get_data(p, joy_data[i].button_items[j]) == joy_data[i].button_items[j]->logical_maximum);
  }

  // do we need to update the mouse?
  // todo: implement acceleration
  //
  // this is called on every datarx, so we can note previous position
  // need to convert relative movement into scaled factor

  if (i == mouse_joy && joy_data[i].mapped_mouse_x != NOT_MAPPED && joy_data[i].mapped_mouse_y != NOT_MAPPED) {
    update_mouse(i);
  }

  /*
  // todo: keypresses: how do we know if stick has moved u/d/l/r?
  // need to check acorn mapping, then read previous value to detect change.
  if (joy_data[i].key_up != NOT_MAPPED) {
  }*/

}





void open_joystick(uint32_t i)
{
  if (joy_data[i].in_use == FALSE)
    return;

  // open the usb data stream for reading
  os_error* error = xosfind_openinw(osfind_NO_PATH | osfind_ERROR_IF_ABSENT, joy_data[i].usb_path, NULL, &(joy_data[i].fp));

  if (error)
  {
    debug_printf("open_joystick: Cannot open fp %u on %s: %s\n", joy_data[i].fp, joy_data[i].usb_path, error->errmess);
    joy_data[i].fp = 0;
  }
  else if (joy_data[i].fp == 0) {
    debug_printf("open_joystick: Cannot open (fp is 0) on %s\n", joy_data[i].usb_path);
  }
  else
  {
    // find out the buffer handle and devicefs handle
    error = (os_error*) usb_get_device_handle((usb_device_t*)joy_data[i].device_name, joy_data[i].fp, &(joy_data[i].buffer_handle), &(joy_data[i].devicefs_handle));
    if (error) {
      debug_printf("open_joystick: Cannot get device/buffer handles: %s\n", error->errmess);
    }
    else {
      // get the internal buffer info
      error = xbuffer_internal_info(joy_data[i].buffer_handle, &(joy_data[i].buffer_internal_id), &(joy_data[i].buffer_service_routine), &(joy_data[i].buffer_workspace));
      if (error) {
        debug_printf("open_joystick: Cannot get buffer internal info: %s\n", error->errmess);
      }
      else {
        joy_data[i].open = TRUE;
        auto_map(i);
      }
    }
  }
}







void joy_remove_device(const char* device_name)
{
  debug_printf("joy_remove_device: Removing device %s\n", device_name);
  for (uint32_t i=0; i<JOY_MAX; i++)
  {
    if (strcmp(joy_data[i].device_name, device_name) == 0) {
      if (joy_data[i].fp > 0) {
        os_error* error = xosfind_closew(joy_data[i].fp);
        if (error) {
          debug_printf("joy_remove_device: Cannot close fp %d when device %s is removed: %s", joy_data[i].fp, device_name, error->errmess);
        }
      }
      if (mouse_joy == i) {
        debug_printf("joy_remove_device: switching off mouse control (was on stick id %u)\n", i);
        mouse_joy = NOT_MAPPED;
      }
      unmap(i);
      clean_joystick_data_item(i);
      debug_printf("joy_remove_device: Removed device %s\n", device_name);
    }
  }
}









void servicecall_handler(int32_t service_number, _kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);
  switch (service_number)
  {
    case Service_USBDriver:
      switch (r->r[0])
      {
        case Service_USBDriver_Attach:
          joy_check_device((const usb_service_t*)(r->r[2]), NULL);
          break;
      }
      break;

    case Service_DeviceDead:
      if (r->r[3] != 0)
        joy_remove_device((const char*)(r->r[3]));
      break;
  }
}


