#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"

static const char *TAG = "knob";

const uint8_t hid_report_descriptor[] = {
    TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
};

const char* string_descriptor[5] = {
    // array of pointer to string descriptors
    (char[]){0x09, 0x04},  // 0: Supported language is English (0x0409)
    "Airball.aero",                // 1: Manufacturer
    "Knob+CANBus peripheral",      // 2: Product
    "123456",                      // 3: Serials, should use chip ID
    "Adjustment knob",             // 4: Our device
};

#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN)

static const uint8_t configuration_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),

    // Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
    TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10),

    // CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size.
    TUD_CDC_DESCRIPTOR(1, 4, 0x81, 8, 0x82, 0x83, 64),
};

static uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1];

uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
  return &hid_report_descriptor[instance];
}

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) {
  return 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) {
}

void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
  /* initialization */
  size_t rx_size = 0;

  /* read */
  esp_err_t ret = tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
  if (ret == ESP_OK) {
    ESP_LOGI(TAG, "Data from channel %d:", itf);
    ESP_LOG_BUFFER_HEXDUMP(TAG, buf, rx_size, ESP_LOG_INFO);
  } else {
    ESP_LOGE(TAG, "Read error");
  }

  /* write back */
  tinyusb_cdcacm_write_queue(itf, buf, rx_size);
  tinyusb_cdcacm_write_flush(itf, 0);
  memcpy(buf, "\r\n", 2);
  tinyusb_cdcacm_write_queue(itf, buf, 2);
}

void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
  int dtr = event->line_state_changed_data.dtr;
  int rts = event->line_state_changed_data.rts;
  ESP_LOGI(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts);
}

void init_cdcacm() {
  tinyusb_config_cdcacm_t acm_cfg = {
      .usb_dev = TINYUSB_USBDEV_0,
      .cdc_port = TINYUSB_CDC_ACM_0,
      .callback_rx = &tinyusb_cdc_rx_callback,
      // .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
      .callback_rx_wanted_char = NULL,
      .callback_line_state_changed = NULL,
      .callback_line_coding_changed = NULL
  };
  ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
}

void init_usbhid() {
  // Keyboard output: Send key 'a/A' pressed and released
  ESP_LOGI(TAG, "Sending Keyboard report");
  uint8_t keycode[6] = {HID_KEY_A};
  tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, keycode);
  vTaskDelay(pdMS_TO_TICKS(50));
  tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
}

void init_tinyusb() {
  const tinyusb_config_t tusb_cfg = {
      .device_descriptor = NULL,
      .external_phy = false, // In the most cases you need to use a `false` value
      .configuration_descriptor = configuration_descriptor,
      .string_descriptor = string_descriptor,
      .string_descriptor_count = sizeof(string_descriptor) / sizeof(string_descriptor[0]),
  };
  ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
}

void app_main() {
  init_tinyusb();
  init_cdcacm();
  init_usbhid();
}

