#include <inttypes.h>

#include "esp_log.h"

#include "tinyusb.h"
#include "class/hid/hid_device.h"

#include "common.h"
#include "usb.h"
#include "lcd.h"

#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)

static const char *TAG = "valconomy-usb";

// See https://github.com/hathach/tinyusb/blob/cb22301f91f0465a5578be35d9be284657ddd31d/src/common/tusb_types.h#L331
const tusb_desc_device_t val_usb_dev_descriptor = {
  .bLength = sizeof(tusb_desc_device_t),
  .bDescriptorType = TUSB_DESC_DEVICE,
  // BCD-coded USB version (?)
  .bcdUSB = 0x0200,

  .bDeviceClass = 0x00,
  .bDeviceSubClass = 0x00,
  .bDeviceProtocol = 0x00,
  .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,

  .idVendor = 0x6969,
  .idProduct = 0x0004,
  // BCD-coded device version (0.1)
  .bcdDevice = 0x0010,

  // Indices of strings
  .iManufacturer = 0x01,
  .iProduct = 0x02,
  .iSerialNumber = 0x03,

  .bNumConfigurations = 0x01,
};

const char* val_usb_string_descriptor[5] = {
  // 0: supported language is English (0x0409)
  (char[]){0x09, 0x04},
  // 1: Manufacturer
  "/dev/player0",
  // 2: Product
  "Valorant Economy Helper",
  // 3: Serials, should use chip ID
  val_dev_serial,
  // 4: HID
  "Valconomy HID interface",
};

const uint8_t val_hid_report_descriptor[] = {
  TUD_HID_REPORT_DESC_GENERIC_INOUT(USB_EP_BUFSIZE),
};

const uint8_t val_hid_conf_descriptor[] = {
  // Configuration number, interface count, string index, total length, attribute, power in mA
  TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),

  // HID requires an IN endpoint even if we don't care about it
  // Interface number, string index, protocol, report descriptor len, EP Out & In address, size & polling interval (ms)
  // 0x80 in endpoint address indicates IN
  TUD_HID_INOUT_DESCRIPTOR(0, 4, HID_ITF_PROTOCOL_NONE, sizeof(val_hid_report_descriptor), EPNUM_HID, 0x80 | EPNUM_HID, USB_EP_BUFSIZE, 100)
};

// TinyUSB callbacks

// Invoked when received GET HID REPORT DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
  // We use only one interface and one HID report descriptor, so we can ignore parameter 'instance'
  return val_hid_report_descriptor;
}

// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) {
    (void) instance;
    (void) report_id;
    (void) report_type;
    (void) buffer;
    (void) reqlen;

    return 0;
}

// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
  assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT);
  ESP_LOGI(TAG, "Got %hu bytes report %hhu", bufsize, report_id);
  for (uint16_t i = 0; i < bufsize; i++) {
    ESP_LOGI(TAG, "b: %02hhx", buffer[i]);
  }
}

void val_usb_init(void) {
  ESP_LOGI(TAG, "Initializing USB");

  const tinyusb_config_t cfg = {
    .device_descriptor = &val_usb_dev_descriptor,
    .string_descriptor = val_usb_string_descriptor,
    .string_descriptor_count = sizeof(val_usb_string_descriptor) / sizeof(char *),
    .external_phy = false,

#if (TUD_OPT_HIGH_SPEED)
     // HID configuration descriptor for full-speed and high-speed are the same
    .fs_configuration_descriptor = val_hid_conf_descriptor,
    .hs_configuration_descriptor = val_hid_conf_descriptor,
    .qualifier_descriptor = NULL,
#else
    .configuration_descriptor = val_hid_conf_descriptor,
#endif // TUD_OPT_HIGH_SPEED
  };
  ESP_ERROR_CHECK(tinyusb_driver_install(&cfg));
}