#include <string.h>
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usb_host.h"

static const char *TAG = "USB_HOST";

static usb_data_cb_t serial_callback = NULL;
static bool arduino_connected_flag = false;
static bool initialized = false;
static bool rx_claimed = false;
static struct usbh_serial *serial_dev = NULL;
static TaskHandle_t usb_monitor_task_handle = NULL;
static TaskHandle_t usb_rx_task_handle = NULL;
static uint32_t serial_baud = 9600;

/* CherryUSB host event callback — forwards connect/disconnect events to
 * usb_monitor_task so it responds faster than 1s poll. */
static void usb_event_handler(uint8_t busid, uint8_t hub_index, uint8_t hub_port,
                              uint8_t intf, uint8_t event)
{
    switch (event) {
    case USBH_EVENT_INTERFACE_START:
    case USBH_EVENT_DEVICE_DISCONNECTED:
        if (usb_monitor_task_handle) {
            xTaskNotifyGive(usb_monitor_task_handle);
        }
        break;
    default:
        break;
    }
}

/* Serial bridge termios: blocking RX, configurable baud, 8N1. */
static struct usbh_serial_termios make_termios(uint32_t rx_timeout)
{
    struct usbh_serial_termios t = {
        .baudrate = serial_baud,
        .databits = 8,
        .parity = 0,
        .stopbits = 0,
        .rtscts = false,
        .rx_timeout = rx_timeout,
    };
    return t;
}

/* STK500/avrdude termios: 115200 for Arduino bootloader sync. */
static struct usbh_serial_termios make_stk500_termios(uint32_t rx_timeout)
{
    struct usbh_serial_termios t = {
        .baudrate = 115200,
        .databits = 8,
        .parity = 0,
        .stopbits = 0,
        .rtscts = false,
        .rx_timeout = rx_timeout,
    };
    return t;
}

static void usb_rx_task(void *arg)
{
    uint8_t buf[512];
    int ret;

    while (1) {
        /* If suspended by rx_claim, just idle (suspension handled by VTaskSuspend). */
        if (serial_dev && arduino_connected_flag && !rx_claimed) {
            ret = usbh_serial_read(serial_dev, buf, sizeof(buf));
            if (ret > 0) {
                if (serial_callback) {
                    serial_callback(buf, ret);
                }
            }
        }
        vTaskDelay(1);
    }
}

static void usb_monitor_task(void *arg)
{
    struct usbh_serial *dev;

    while (1) {
        if (!arduino_connected_flag) {
            dev = usbh_serial_open("/dev/ttyACM0", USBH_SERIAL_O_RDWR);
            if (!dev) {
                dev = usbh_serial_open("/dev/ttyUSB0", USBH_SERIAL_O_RDWR);
            }
            if (dev) {
                serial_dev = dev;
                arduino_connected_flag = true;

                struct usbh_serial_termios t = make_termios(0);
                usbh_serial_control(dev, USBH_SERIAL_CMD_SET_ATTR, &t);
                /* SET_ATTR already drives DTR|RTS high internally (see
                 * usbh_serial.c SET_ATTR handler). Do NOT issue TIOCMSET
                 * with value-as-pointer — that derefs an invalid address. */

                ESP_LOGI(TAG, "Arduino connected via CherryUSB");
            }
        } else {
            int ret = usbh_serial_write(serial_dev, NULL, 0);
            if (ret < 0) {
                ESP_LOGI(TAG, "Arduino disconnected");
                arduino_connected_flag = false;
                usbh_serial_close(serial_dev);
                serial_dev = NULL;
            }
        }
        xTaskNotifyWait(0, 0, NULL, pdMS_TO_TICKS(1000));
    }
}

bool usb_host_init(void)
{
    if (initialized) return true;

    esp_err_t ret = usbh_initialize(0, ESP_USB_FS0_BASE, usb_event_handler);
    if (ret != 0) {
        ESP_LOGE(TAG, "CherryUSB init failed: %d", ret);
        return false;
    }

    xTaskCreate(usb_monitor_task, "usb_mon", 4096, NULL, 3, &usb_monitor_task_handle);
    xTaskCreate(usb_rx_task, "usb_rx", 2560, NULL, 4, &usb_rx_task_handle);

    initialized = true;
    ESP_LOGI(TAG, "USB Host initialized (CherryUSB)");
    return true;
}

bool usb_host_arduino_connected(void)
{
    return arduino_connected_flag && serial_dev != NULL;
}

bool usb_host_write_cdc(const uint8_t *data, size_t len)
{
    if (!serial_dev) return false;

    int ret = usbh_serial_write(serial_dev, data, len);
    if (ret < 0) {
        ESP_LOGE(TAG, "Serial write failed: %d", ret);
        return false;
    }
    return true;
}

int usb_host_read_cdc(uint8_t *buf, size_t len, uint32_t timeout_ms)
{
    if (!serial_dev || !arduino_connected_flag) {
        return -1;
    }
    /* CherryUSB uses serial_dev->rx_timeout_ms for the sem_take timeout
     * (see usbh_serial.c:461). Setting it inline is the documented field. */
    serial_dev->rx_timeout_ms = timeout_ms;
    int ret = usbh_serial_read(serial_dev, buf, len);
    return ret;  /* >=0 bytes, <0 on error/timeout */
}

/* Drain up to N bytes from CDC ringbuffer with short timeout. Returns bytes drained. */
size_t usb_host_drain_cdc(size_t max_bytes, uint32_t total_ms)
{
    size_t total = 0;
    int64_t deadline = esp_timer_get_time() + (int64_t)total_ms * 1000;
    uint8_t buf[64];
    while (total < max_bytes && esp_timer_get_time() < deadline) {
        int n = usb_host_read_cdc(buf, sizeof(buf), 10);
        if (n <= 0) break;
        total += (size_t)n;
    }
    if (total > 0) {
        ESP_LOGI(TAG, "drained %d stale bytes from CDC", (int)total);
    }
    return total;
}

void usb_host_set_serial_callback(usb_data_cb_t cb)
{
    serial_callback = cb;
}

void usb_host_rx_claim(void)
{
    if (!initialized || rx_claimed) return;

    /* Stop the rx/monitor tasks so they don't drain the CDC ringbuffer. */
    if (usb_rx_task_handle)      vTaskSuspend(usb_rx_task_handle);
    if (usb_monitor_task_handle) vTaskSuspend(usb_monitor_task_handle);

    if (serial_dev) {
        /* Reconfigure with bounded rx_timeout so usb_host_read_cdc returns
         * on timeout. SET_ATTR also kills+resubmits the IN URB and resets
         * the ringbuffer, clearing any stale serial data. */
        struct usbh_serial_termios t = make_stk500_termios(50);
        usbh_serial_control(serial_dev, USBH_SERIAL_CMD_SET_ATTR, &t);
    }
    rx_claimed = true;
    ESP_LOGI(TAG, "RX claimed for STK500");
}

void usb_host_rx_release(void)
{
    if (!initialized || !rx_claimed) return;

    if (serial_dev) {
        /* Restore blocking RX (rx_timeout=0 = forever) for serial bridge. */
        struct usbh_serial_termios t = make_termios(0);
        usbh_serial_control(serial_dev, USBH_SERIAL_CMD_SET_ATTR, &t);
    }
    rx_claimed = false;

    if (usb_rx_task_handle)      vTaskResume(usb_rx_task_handle);
    if (usb_monitor_task_handle) vTaskResume(usb_monitor_task_handle);
    ESP_LOGI(TAG, "RX released back to serial bridge");
}

void usb_host_reset_arduino(void)
{
    if (!serial_dev || !arduino_connected_flag) {
        ESP_LOGW(TAG, "reset_arduino: no device — skipping DTR pulse");
        return;
    }
    /* Mirrors avrdude stk500v1 + frontend USBHardwareDeployer.resetArduino.
     * 100nF/10kΩ RC on Uno R3 (~1ms τ) needs DTR low long enough to pull RESET
     * below V_IL reliably across CH340/CP2102 clones.
     * Sequence: DTR+RTS low (50ms) -> high -> settle 100ms for optiboot. */
    uint32_t flags_low = 0;
    usbh_serial_control(serial_dev, USBH_SERIAL_CMD_TIOCMSET, &flags_low);
    ESP_LOGI(TAG, "reset_arduino: DTR/RTS low (asserting RESET)");
    vTaskDelay(pdMS_TO_TICKS(50));

    uint32_t flags_high = USBH_SERIAL_TIOCM_DTR | USBH_SERIAL_TIOCM_RTS;
    usbh_serial_control(serial_dev, USBH_SERIAL_CMD_TIOCMSET, &flags_high);
    ESP_LOGI(TAG, "reset_arduino: DTR/RTS high (releasing RESET)");

    vTaskDelay(pdMS_TO_TICKS(100));

    ESP_LOGI(TAG, "reset_arduino: DTR pulse done (autoreset, 150ms total)");
}

void usb_host_set_baud_rate(uint32_t baud)
{
    serial_baud = baud;
    ESP_LOGI(TAG, "Baud rate set to %lu", (unsigned long)serial_baud);

    if (serial_dev && arduino_connected_flag && !rx_claimed) {
        struct usbh_serial_termios t = make_termios(0);
        usbh_serial_control(serial_dev, USBH_SERIAL_CMD_SET_ATTR, &t);
        ESP_LOGI(TAG, "Baud rate reconfigured: %lu", (unsigned long)serial_baud);
    }
}

void usb_host_deinit(void)
{
    if (!initialized) return;

    initialized = false;
    arduino_connected_flag = false;
    rx_claimed = false;

    if (usb_monitor_task_handle) {
        vTaskDelete(usb_monitor_task_handle);
        usb_monitor_task_handle = NULL;
    }
    if (usb_rx_task_handle) {
        vTaskDelete(usb_rx_task_handle);
        usb_rx_task_handle = NULL;
    }
    if (serial_dev) {
        usbh_serial_close(serial_dev);
        serial_dev = NULL;
    }
    usbh_deinitialize(0);

    ESP_LOGI(TAG, "USB Host deinitialized");
}
