// Copyright 2015-2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "USB.h"
#if SOC_USB_SERIAL_JTAG_SUPPORTED

#include "Arduino.h"  // defines ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE and ARDUINO_SERIAL_EVENT_TASK_PRIORITY
#include "esp32-hal.h"
#include "esp32-hal-periman.h"
#include "HWCDC.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/ringbuf.h"
#include "esp_intr_alloc.h"
#include "soc/periph_defs.h"
#include "soc/io_mux_reg.h"
#include "soc/usb_serial_jtag_struct.h"
#pragma GCC diagnostic ignored "-Wvolatile"
#include "hal/usb_serial_jtag_ll.h"
#pragma GCC diagnostic warning "-Wvolatile"
#include "rom/ets_sys.h"

ESP_EVENT_DEFINE_BASE(ARDUINO_HW_CDC_EVENTS);

static RingbufHandle_t tx_ring_buf = NULL;
static QueueHandle_t rx_queue = NULL;
static uint8_t rx_data_buf[64] = {0};
static intr_handle_t intr_handle = NULL;
static SemaphoreHandle_t tx_lock = NULL;
static volatile bool connected = false;

// SOF in ISR causes problems for uploading firmware
//static volatile unsigned long lastSOF_ms;
//static volatile uint8_t SOF_TIMEOUT;

// timeout has no effect when USB CDC is unplugged
static uint32_t tx_timeout_ms = 100;

static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL;

static esp_err_t
  arduino_hw_cdc_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, BaseType_t *task_unblocked) {
  if (arduino_hw_cdc_event_loop_handle == NULL) {
    return ESP_FAIL;
  }
  return esp_event_isr_post_to(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_data, event_data_size, task_unblocked);
}

static esp_err_t
  arduino_hw_cdc_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg) {
  if (!arduino_hw_cdc_event_loop_handle) {
    esp_event_loop_args_t event_task_args = {
      .queue_size = 5,
      .task_name = "arduino_hw_cdc_events",
      .task_priority = ARDUINO_SERIAL_EVENT_TASK_PRIORITY,
      .task_stack_size = ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE,
      .task_core_id = tskNO_AFFINITY
    };
    if (esp_event_loop_create(&event_task_args, &arduino_hw_cdc_event_loop_handle) != ESP_OK) {
      log_e("esp_event_loop_create failed");
    }
  }
  if (arduino_hw_cdc_event_loop_handle == NULL) {
    return ESP_FAIL;
  }
  return esp_event_handler_register_with(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_handler, event_handler_arg);
}

static void hw_cdc_isr_handler(void *arg) {
  portBASE_TYPE xTaskWoken = 0;
  uint32_t usbjtag_intr_status = 0;
  arduino_hw_cdc_event_data_t event = {0};
  usbjtag_intr_status = usb_serial_jtag_ll_get_intsts_mask();

  if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
    // Interrupt tells us the host picked up the data we sent.
    if (!HWCDC::isPlugged()) {
      connected = false;
      usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
      // USB is unplugged, nothing to be done here
      return;
    } else {
      connected = true;
    }
    if (tx_ring_buf != NULL && usb_serial_jtag_ll_txfifo_writable() == 1) {
      // We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
      usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
      size_t queued_size = 0;
      uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpToFromISR(tx_ring_buf, &queued_size, 64);
      // If the hardware fifo is available, write in it. Otherwise, do nothing.
      if (queued_buff != NULL) {  //Although tx_queued_bytes may be larger than 0. We may have interrupt before xRingbufferSend() was called.
        //Copy the queued buffer into the TX FIFO
        usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
        usb_serial_jtag_ll_write_txfifo(queued_buff, queued_size);
        usb_serial_jtag_ll_txfifo_flush();
        vRingbufferReturnItemFromISR(tx_ring_buf, queued_buff, &xTaskWoken);
        if (connected) {
          usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
        }
        //send event?
        //ets_printf("TX:%lu\n", (unsigned long)queued_size);
        event.tx.len = queued_size;
        arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_TX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
      }
    } else {
      usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
    }
  }

  if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) {
    // read rx buffer(max length is 64), and send available data to ringbuffer.
    // Ensure the rx buffer size is larger than RX_MAX_SIZE.
    usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
    uint32_t rx_fifo_len = usb_serial_jtag_ll_read_rxfifo(rx_data_buf, 64);
    uint32_t i = 0;
    for (i = 0; i < rx_fifo_len; i++) {
      if (rx_queue == NULL || !xQueueSendFromISR(rx_queue, rx_data_buf + i, &xTaskWoken)) {
        break;
      }
    }
    event.rx.len = i;
    arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_RX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
    connected = true;
  }

  if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_BUS_RESET) {
    usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_BUS_RESET);
    arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
    connected = false;
  }

  // SOF ISR is causing esptool to be unable to upload firmware to the board
  // if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SOF) {
  //   usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SOF);
  //   lastSOF_ms = millis();
  // }

  if (xTaskWoken == pdTRUE) {
    portYIELD_FROM_ISR();
  }
}

// Moved to header file as inline function. Kept just as future reference.
//inline bool HWCDC::isPlugged(void) {
// SOF ISR is causing esptool to be unable to upload firmware to the board
// Timer test for SOF seems to work when uploading firmware
//  return usb_serial_jtag_is_connected();//(lastSOF_ms + SOF_TIMEOUT) >= millis();
//}

bool HWCDC::isCDC_Connected() {
  static bool running = false;

  // USB may be unplugged
  if (!isPlugged()) {
    connected = false;
    running = false;
    // SOF in ISR causes problems for uploading firmware
    //SOF_TIMEOUT = 5;  // SOF timeout when unplugged
    return false;
  }
  //else {
  //  SOF_TIMEOUT = 50;  // SOF timeout when plugged
  //}

  if (connected) {
    running = false;
    return true;
  }

  if (running == false && !connected) {  // enables it only once!
    usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
  }

  // this will feed CDC TX FIFO to trigger IN_EMPTY
  usb_serial_jtag_ll_txfifo_flush();
  running = true;
  return false;
}

static void flushTXBuffer(const uint8_t *buffer, size_t size) {
  if (!tx_ring_buf) {
    return;
  }
  UBaseType_t uxItemsWaiting = 0;
  vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
  size_t freeSpace = xRingbufferGetCurFreeSize(tx_ring_buf);
  size_t ringbufferLength = freeSpace + uxItemsWaiting;

  if (buffer == NULL) {
    // just flush the whole ring buffer and exit - used by HWCDC::flush()
    size_t queued_size = 0;
    uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo(tx_ring_buf, &queued_size, 0, ringbufferLength);
    if (queued_size && queued_buff != NULL) {
      vRingbufferReturnItem(tx_ring_buf, (void *)queued_buff);
    }
    return;
  }
  if (size == 0) {
    return;  // nothing to do
  }
  if (freeSpace >= size) {
    // there is enough space, just add the data to the ring buffer
    if (xRingbufferSend(tx_ring_buf, (void *)buffer, size, 0) != pdTRUE) {
      return;
    }
  } else {
    // how many byte should be flushed to make space for the new data
    size_t to_flush = size - freeSpace;
    if (to_flush > ringbufferLength) {
      to_flush = ringbufferLength;
    }
    size_t queued_size = 0;
    uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo(tx_ring_buf, &queued_size, 0, to_flush);
    if (queued_size && queued_buff != NULL) {
      vRingbufferReturnItem(tx_ring_buf, (void *)queued_buff);
    }
    // now add the new data that fits into the ring buffer
    uint8_t *bptr = (uint8_t *)buffer;
    if (size >= ringbufferLength) {
      size = ringbufferLength;
      bptr = (uint8_t *)buffer + (size - ringbufferLength);
    }
    if (xRingbufferSend(tx_ring_buf, (void *)bptr, size, 0) != pdTRUE) {
      return;
    }
  }
  // flushes CDC FIFO
  usb_serial_jtag_ll_txfifo_flush();
}

static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
  if (tx_ring_buf == NULL) {
    return;
  }
  if (!HWCDC::isConnected()) {
    // just pop/push RingBuffer and apply FIFO policy
    flushTXBuffer((const uint8_t *)&c, 1);
    return;
  }
  if (xPortInIsrContext()) {
    xRingbufferSendFromISR(tx_ring_buf, (void *)(&c), 1, NULL);
  } else {
    xRingbufferSend(tx_ring_buf, (void *)(&c), 1, tx_timeout_ms / portTICK_PERIOD_MS);
  }
  usb_serial_jtag_ll_txfifo_flush();
  usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
}

HWCDC::HWCDC() {
  // SOF in ISR causes problems for uploading firmware
  //  lastSOF_ms = 0;
  //  SOF_TIMEOUT = 5;
}

HWCDC::~HWCDC() {
  end();
}

// It should return <true> just when USB is plugged and CDC is connected.
HWCDC::operator bool() const {
  return HWCDC::isCDC_Connected();
}

void HWCDC::onEvent(esp_event_handler_t callback) {
  onEvent(ARDUINO_HW_CDC_ANY_EVENT, callback);
}

void HWCDC::onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback) {
  arduino_hw_cdc_event_handler_register_with(ARDUINO_HW_CDC_EVENTS, event, callback, this);
}

bool HWCDC::deinit(void *busptr) {
  // avoid any recursion issue with Peripheral Manager perimanSetPinBus() call
  static bool running = false;
  if (running) {
    return true;
  }
  running = true;
  // Setting USB D+ D- pins
  bool retCode = true;
  retCode &= perimanClearPinBus(USB_INT_PHY0_DM_GPIO_NUM);
  retCode &= perimanClearPinBus(USB_INT_PHY0_DP_GPIO_NUM);
  if (retCode) {
    // Force the host to re-enumerate (BUS_RESET)
    pinMode(USB_INT_PHY0_DM_GPIO_NUM, OUTPUT_OPEN_DRAIN);
    pinMode(USB_INT_PHY0_DP_GPIO_NUM, OUTPUT_OPEN_DRAIN);
    digitalWrite(USB_INT_PHY0_DM_GPIO_NUM, LOW);
    digitalWrite(USB_INT_PHY0_DP_GPIO_NUM, LOW);
  }
  // release the flag
  running = false;
  return retCode;
}

void HWCDC::begin(unsigned long baud) {
  if (tx_lock == NULL) {
    tx_lock = xSemaphoreCreateMutex();
  }
  //RX Buffer default has 256 bytes if not preset
  if (rx_queue == NULL) {
    if (!setRxBufferSize(256)) {
      log_e("HW CDC RX Buffer error");
    }
  }
  //TX Buffer default has 256 bytes if not preset
  if (tx_ring_buf == NULL) {
    if (!setTxBufferSize(256)) {
      log_e("HW CDC TX Buffer error");
    }
  }

  // the HW Serial pins needs to be first deinited in order to allow `if(Serial)` to work :-(
  // But this is also causing terminal to hang, so they are disabled
  // deinit(NULL);
  // delay(10);  // USB Host has to enumerate it again

  // Peripheral Manager setting for USB D+ D- pins
  uint8_t pin = USB_INT_PHY0_DM_GPIO_NUM;
  if (perimanGetBusDeinit(ESP32_BUS_TYPE_USB_DM) == NULL) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_USB_DM, HWCDC::deinit);
  }
  if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_USB_DM, (void *)this, -1, -1)) {
    goto err;
  }
  pin = USB_INT_PHY0_DP_GPIO_NUM;
  if (perimanGetBusDeinit(ESP32_BUS_TYPE_USB_DP) == NULL) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_USB_DP, HWCDC::deinit);
  }
  if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_USB_DP, (void *)this, -1, -1)) {
    goto err;
  }
  // Configure PHY
  // USB_Serial_JTAG use internal PHY
  USB_SERIAL_JTAG.conf0.phy_sel = 0;
  // Disable software control USB D+ D- pullup pulldown (Device FS: dp_pullup = 1)
  USB_SERIAL_JTAG.conf0.pad_pull_override = 0;
  // Enable USB D+ pullup
  USB_SERIAL_JTAG.conf0.dp_pullup = 1;
  // Enable USB pad function
  USB_SERIAL_JTAG.conf0.usb_pad_enable = 1;
  usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
  usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_BUS_RESET);
  // SOF ISR is causing esptool to be unable to upload firmware to the board
  // usb_serial_jtag_ll_ena_intr_mask(
  //   USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_BUS_RESET | USB_SERIAL_JTAG_INTR_SOF
  // );
  if (!intr_handle && esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, hw_cdc_isr_handler, NULL, &intr_handle) != ESP_OK) {
    isr_log_e("HW USB CDC failed to init interrupts");
    end();
    return;
  }
  return;

err:
  log_e("Serial JTAG Pin %u can't be set into Peripheral Manager.", pin);
  end();
}

void HWCDC::end() {
  //Disable/clear/free tx/rx interrupt.
  usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
  usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
  esp_intr_free(intr_handle);
  intr_handle = NULL;
  if (tx_lock != NULL) {
    vSemaphoreDelete(tx_lock);
    tx_lock = NULL;
  }
  setRxBufferSize(0);
  setTxBufferSize(0);
  if (arduino_hw_cdc_event_loop_handle) {
    esp_event_loop_delete(arduino_hw_cdc_event_loop_handle);
    arduino_hw_cdc_event_loop_handle = NULL;
  }
  HWCDC::deinit(this);
  setDebugOutput(false);
  connected = false;
}

void HWCDC::setTxTimeoutMs(uint32_t timeout) {
  tx_timeout_ms = timeout;
}

/*
 * WRITING
*/

size_t HWCDC::setTxBufferSize(size_t tx_queue_len) {
  if (tx_ring_buf) {
    vRingbufferDelete(tx_ring_buf);
    tx_ring_buf = NULL;
  }
  if (!tx_queue_len) {
    return 0;
  }
  tx_ring_buf = xRingbufferCreate(tx_queue_len, RINGBUF_TYPE_BYTEBUF);
  if (!tx_ring_buf) {
    return 0;
  }
  return tx_queue_len;
}

int HWCDC::availableForWrite(void) {
  if (tx_ring_buf == NULL || tx_lock == NULL) {
    return 0;
  }
  if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
    return 0;
  }
  size_t a = xRingbufferGetCurFreeSize(tx_ring_buf);
  xSemaphoreGive(tx_lock);
  return a;
}

size_t HWCDC::write(const uint8_t *buffer, size_t size) {
  if (buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL) {
    return 0;
  }
  if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
    return 0;
  }
  if (!isCDC_Connected()) {
    // just pop/push RingBuffer and apply FIFO policy
    flushTXBuffer(buffer, size);
  } else {
    size_t space = xRingbufferGetCurFreeSize(tx_ring_buf);
    size_t to_send = size, so_far = 0;

    if (space > size) {
      space = size;
    }
    // Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
    if (space > 0 && xRingbufferSend(tx_ring_buf, (void *)(buffer), space, 0) != pdTRUE) {
      size = 0;
    } else {
      to_send -= space;
      so_far += space;
      // Now trigger the ISR to read data from the ring buffer.
      usb_serial_jtag_ll_txfifo_flush();
      if (connected) {
        usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
      }
      // tracks CDC transmission progress to avoid hanging if CDC is unplugged while still sending data
      size_t last_toSend = to_send;
      uint32_t tries = tx_timeout_ms;  // waits 1ms per sending data attempt, in case CDC is unplugged
      while (connected && to_send) {
        space = xRingbufferGetCurFreeSize(tx_ring_buf);
        if (space > to_send) {
          space = to_send;
        }
        // Blocking method, Sending data to ringbuffer, and handle the data in ISR.
        if (xRingbufferSend(tx_ring_buf, (void *)(buffer + so_far), space, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE) {
          size = so_far;
          log_w("write failed due to ring buffer full - timeout");
          break;
        }
        so_far += space;
        to_send -= space;
        // Now trigger the ISR to read data from the ring buffer.
        usb_serial_jtag_ll_txfifo_flush();
        if (connected) {
          usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
        }
        if (last_toSend == to_send) {
          // no progress in sending data... USB CDC is probably unplugged
          tries--;
          delay(1);
        } else {
          last_toSend = to_send;
          tries = tx_timeout_ms;  // reset the timeout
        }
        if (tries == 0) {  // CDC isn't connected anymore...
          size = so_far;
          log_w("write failed due to waiting USB Host - timeout");
          connected = false;
        }
      }
    }
    // CDC was disconnected while sending data ==> flush the TX buffer keeping the last data
    if (to_send && !usb_serial_jtag_ll_txfifo_writable()) {
      connected = false;
      flushTXBuffer(buffer + so_far, to_send);
    }
  }
  xSemaphoreGive(tx_lock);
  return size;
}

size_t HWCDC::write(uint8_t c) {
  return write(&c, 1);
}

void HWCDC::flush(void) {
  if (tx_ring_buf == NULL || tx_lock == NULL) {
    return;
  }
  if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
    return;
  }
  if (!isCDC_Connected()) {
    flushTXBuffer(NULL, 0);
  } else {
    UBaseType_t uxItemsWaiting = 0;
    vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
    if (uxItemsWaiting) {
      // Now trigger the ISR to read data from the ring buffer.
      usb_serial_jtag_ll_txfifo_flush();
      if (connected) {
        usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
      }
    }
    uint32_t tries = tx_timeout_ms;  // waits 1ms per ISR sending data attempt, in case CDC is unplugged
    while (connected && tries && uxItemsWaiting) {
      delay(1);
      UBaseType_t lastUxItemsWaiting = uxItemsWaiting;
      vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
      if (lastUxItemsWaiting == uxItemsWaiting) {
        tries--;
      }
      if (connected) {
        usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
      }
    }
    if (tries == 0) {  // CDC isn't connected anymore...
      connected = false;
      flushTXBuffer(NULL, 0);  // flushes all TX Buffer
    }
  }
  xSemaphoreGive(tx_lock);
}

/*
 * READING
*/

size_t HWCDC::setRxBufferSize(size_t rx_queue_len) {
  if (rx_queue) {
    vQueueDelete(rx_queue);
    rx_queue = NULL;
  }
  if (!rx_queue_len) {
    return 0;
  }
  rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t));
  if (!rx_queue) {
    return 0;
  }
  return rx_queue_len;
}

int HWCDC::available(void) {
  if (rx_queue == NULL) {
    return -1;
  }
  return uxQueueMessagesWaiting(rx_queue);
}

int HWCDC::peek(void) {
  if (rx_queue == NULL) {
    return -1;
  }
  uint8_t c;
  if (xQueuePeek(rx_queue, &c, 0)) {
    return c;
  }
  return -1;
}

int HWCDC::read(void) {
  if (rx_queue == NULL) {
    return -1;
  }
  uint8_t c = 0;
  if (xQueueReceive(rx_queue, &c, 0)) {
    return c;
  }
  return -1;
}

size_t HWCDC::read(uint8_t *buffer, size_t size) {
  if (rx_queue == NULL) {
    return -1;
  }
  uint8_t c = 0;
  size_t count = 0;
  while (count < size && xQueueReceive(rx_queue, &c, 0)) {
    buffer[count++] = c;
  }
  return count;
}

/*
 * DEBUG
*/

void HWCDC::setDebugOutput(bool en) {
  if (en) {
    uartSetDebug(NULL);
    ets_install_putc2((void (*)(char)) & cdc0_write_char);
  } else {
    ets_install_putc2(NULL);
  }
  ets_install_putc1(NULL);  // closes UART log output
}

#if ARDUINO_USB_MODE && ARDUINO_USB_CDC_ON_BOOT  // Hardware JTAG CDC selected
// USBSerial is always available to be used
HWCDC HWCDCSerial;
#endif

#endif /* SOC_USB_SERIAL_JTAG_SUPPORTED */
