#include "usbd_core.h"
#include "usbd_desc.h"
#include "usbd_conf.h"

#define XINPUT_VID                    0x1209U
#define XINPUT_PID                    0xE668U
#define XINPUT_LANGID                 0x0409U
#define XINPUT_MANUFACTURER_STRING    "ERTOtech"
#define XINPUT_PRODUCT_STRING         "Refleks Lite XInput"
#define XINPUT_CONFIGURATION_STRING   "XInput Config"
#define XINPUT_INTERFACE_STRING       "XInput Interface"

static uint8_t *XInput_DeviceDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length);
static uint8_t *XInput_LangIdDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length);
static uint8_t *XInput_ManufacturerDescriptor(USBD_SpeedTypeDef speed,
                                              uint16_t *length);
static uint8_t *XInput_ProductDescriptor(USBD_SpeedTypeDef speed,
                                         uint16_t *length);
static uint8_t *XInput_SerialDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length);
static uint8_t *XInput_ConfigurationDescriptor(USBD_SpeedTypeDef speed,
                                               uint16_t *length);
static uint8_t *XInput_InterfaceDescriptor(USBD_SpeedTypeDef speed,
                                           uint16_t *length);
static void XInput_UpdateSerial(void);
static void XInput_IntToUnicode(uint32_t value, uint8_t *buffer, uint8_t length);

USBD_DescriptorsTypeDef FS_Desc =
{
  XInput_DeviceDescriptor,
  XInput_LangIdDescriptor,
  XInput_ManufacturerDescriptor,
  XInput_ProductDescriptor,
  XInput_SerialDescriptor,
  XInput_ConfigurationDescriptor,
  XInput_InterfaceDescriptor,
};

__ALIGN_BEGIN static uint8_t xinputDeviceDescriptor[USB_LEN_DEV_DESC] __ALIGN_END =
{
  USB_LEN_DEV_DESC,
  USB_DESC_TYPE_DEVICE,
  0x00, 0x02,               /* USB 2.00 */
  0x00, 0x00, 0x00,         /* Class is defined by the interface */
  USB_MAX_EP0_SIZE,
  LOBYTE(XINPUT_VID), HIBYTE(XINPUT_VID),
  LOBYTE(XINPUT_PID), HIBYTE(XINPUT_PID),
  0x01, 0x01,               /* Device release 1.01 */
  USBD_IDX_MFC_STR,
  USBD_IDX_PRODUCT_STR,
  USBD_IDX_SERIAL_STR,
  0x01,
};

__ALIGN_BEGIN static uint8_t xinputLangIdDescriptor[USB_LEN_LANGID_STR_DESC] __ALIGN_END =
{
  USB_LEN_LANGID_STR_DESC,
  USB_DESC_TYPE_STRING,
  LOBYTE(XINPUT_LANGID), HIBYTE(XINPUT_LANGID),
};

__ALIGN_BEGIN static uint8_t xinputStringDescriptor[USBD_MAX_STR_DESC_SIZ] __ALIGN_END;
__ALIGN_BEGIN static uint8_t xinputSerialDescriptor[USB_SIZ_STRING_SERIAL] __ALIGN_END =
{
  USB_SIZ_STRING_SERIAL,
  USB_DESC_TYPE_STRING,
};

static uint8_t *XInput_DeviceDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length)
{
  UNUSED(speed);
  *length = sizeof(xinputDeviceDescriptor);
  return xinputDeviceDescriptor;
}

static uint8_t *XInput_LangIdDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length)
{
  UNUSED(speed);
  *length = sizeof(xinputLangIdDescriptor);
  return xinputLangIdDescriptor;
}

static uint8_t *XInput_ManufacturerDescriptor(USBD_SpeedTypeDef speed,
                                              uint16_t *length)
{
  UNUSED(speed);
  USBD_GetString((uint8_t *)XINPUT_MANUFACTURER_STRING,
                 xinputStringDescriptor, length);
  return xinputStringDescriptor;
}

static uint8_t *XInput_ProductDescriptor(USBD_SpeedTypeDef speed,
                                         uint16_t *length)
{
  UNUSED(speed);
  USBD_GetString((uint8_t *)XINPUT_PRODUCT_STRING,
                 xinputStringDescriptor, length);
  return xinputStringDescriptor;
}

static uint8_t *XInput_SerialDescriptor(USBD_SpeedTypeDef speed,
                                        uint16_t *length)
{
  UNUSED(speed);
  XInput_UpdateSerial();
  *length = sizeof(xinputSerialDescriptor);
  return xinputSerialDescriptor;
}

static uint8_t *XInput_ConfigurationDescriptor(USBD_SpeedTypeDef speed,
                                               uint16_t *length)
{
  UNUSED(speed);
  USBD_GetString((uint8_t *)XINPUT_CONFIGURATION_STRING,
                 xinputStringDescriptor, length);
  return xinputStringDescriptor;
}

static uint8_t *XInput_InterfaceDescriptor(USBD_SpeedTypeDef speed,
                                           uint16_t *length)
{
  UNUSED(speed);
  USBD_GetString((uint8_t *)XINPUT_INTERFACE_STRING,
                 xinputStringDescriptor, length);
  return xinputStringDescriptor;
}

static void XInput_UpdateSerial(void)
{
  uint32_t serial0 = *(uint32_t *)DEVICE_ID1;
  uint32_t serial1 = *(uint32_t *)DEVICE_ID2;
  uint32_t serial2 = *(uint32_t *)DEVICE_ID3;

  serial0 += serial2;
  if (serial0 != 0U)
  {
    XInput_IntToUnicode(serial0, &xinputSerialDescriptor[2], 8U);
    XInput_IntToUnicode(serial1, &xinputSerialDescriptor[18], 4U);
  }
}

static void XInput_IntToUnicode(uint32_t value, uint8_t *buffer, uint8_t length)
{
  uint8_t index;

  for (index = 0U; index < length; index++)
  {
    uint8_t digit = (uint8_t)(value >> 28);
    buffer[2U * index] = (digit < 10U) ?
                         (uint8_t)(digit + '0') :
                         (uint8_t)(digit - 10U + 'A');
    buffer[(2U * index) + 1U] = 0U;
    value <<= 4;
  }
}
