#include "usb_cdc_char_device.h"

#include <rtthread.h>
#include <rtdevice.h>

#include <string.h>
#include <stdbool.h>
#include <stdint.h>

#include "usbd_core.h"
#include "usbd_cdc_acm.h"

#if !defined(RT_USING_DEVICE_OPS)
#error "RT_USING_DEVICE_OPS must be enabled"
#endif

#if !defined(CONFIG_USBDEV_ADVANCE_DESC)
#error "Please enable CONFIG_USBDEV_ADVANCE_DESC"
#endif

/* -------------------- FS fixed config -------------------- */
enum
{
    CDC_IN_EP  = 0x81,
    CDC_OUT_EP = 0x02,
    CDC_INT_EP = 0x83,

    CDC_VID = 0xFFFF,
    CDC_PID = 0xFFFF,

    CDC_MAX_POWER_MA = 100,
    CDC_MAX_MPS      = 64,     /* FS */

    CDC_RX_DMA_SIZE  = 2048,   /* OUT DMA buffer */
    CDC_TX_DMA_SIZE  = 2048,   /* IN  DMA buffer */
    CDC_RX_RING_SIZE = 8192,   /* software RX ringbuffer */

    CDC_CONFIG_SIZE  = (9 + CDC_ACM_DESCRIPTOR_LEN),

    /* 保险丝：防止上层误传超大 size（比如把负数当无符号） */
    CDC_WRITE_SIZE_GUARD = 64 * 1024, /* 64KB */
};

/* -------------------- DMA buffers in nocache RAM -------------------- */
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX static uint8_t g_rx_dma[CDC_RX_DMA_SIZE];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX static uint8_t g_tx_dma[CDC_TX_DMA_SIZE];

/* -------------------- descriptors (advance-desc callbacks) -------------------- */
static const uint8_t g_device_desc[] = {
    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01,
                               CDC_VID, CDC_PID,
                               0x0100, 0x01)
};

static const uint8_t g_config_desc[] = {
    USB_CONFIG_DESCRIPTOR_INIT(CDC_CONFIG_SIZE, 0x02, 0x01,
                               USB_CONFIG_BUS_POWERED, CDC_MAX_POWER_MA),
    CDC_ACM_DESCRIPTOR_INIT(0x00,
                            CDC_INT_EP,
                            CDC_OUT_EP,
                            CDC_IN_EP,
                            CDC_MAX_MPS,
                            0x02)
};

/* qualifier：FS 下不是必须，但留着不影响 */
static const uint8_t g_device_qual_desc[] = {
    0x0a, USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
    0x00, 0x02,
    0x00, 0x00, 0x00,
    0x40,
    0x00, 0x00,
};

static const char g_langid_bytes[] = { 0x09, 0x04 };
static const char *g_string_desc[] = {
    g_langid_bytes,
    "CherryUSB",
    "RTT CDC ACM FS",
    "000000000001",
};

static const uint8_t *device_descriptor_cb(uint8_t speed) { (void)speed; return g_device_desc; }
static const uint8_t *config_descriptor_cb(uint8_t speed) { (void)speed; return g_config_desc; }
static const uint8_t *device_qual_descriptor_cb(uint8_t speed) { (void)speed; return g_device_qual_desc; }
static const char *string_descriptor_cb(uint8_t speed, uint8_t index)
{
    (void)speed;
    if (index > 3) return RT_NULL;
    return g_string_desc[index];
}

static const struct usb_descriptor g_cdc_desc = {
    .device_descriptor_callback = device_descriptor_cb,
    .config_descriptor_callback = config_descriptor_cb,
    .device_quality_descriptor_callback = device_qual_descriptor_cb,
    .string_descriptor_callback = string_descriptor_cb
};

/* -------------------- driver object -------------------- */
struct cdc_char_dev
{
    struct rt_device dev; /* must be first */

    uint8_t busid;
    uintptr_t reg_base;

    volatile rt_uint8_t dtr_enable;
    volatile rt_uint8_t connected;
    volatile rt_uint8_t configured;

    struct rt_ringbuffer *rx_rb;

    rt_sem_t   tx_sem;
    rt_mutex_t tx_lock;

    uint32_t tx_timeout_ms;
    uint8_t  require_dtr_tx;

    struct usbd_interface intf0;
    struct usbd_interface intf1;
    struct usbd_endpoint  out_ep;
    struct usbd_endpoint  in_ep;
};

static struct cdc_char_dev g_cdc;

/* -------------------- forward declarations -------------------- */
static void usbd_event_handler(uint8_t busid, uint8_t event);
static void cdc_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes);
static void cdc_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes);

/* -------------------- RT device ops -------------------- */
static rt_err_t cdc_init(rt_device_t dev) { (void)dev; return RT_EOK; }
static rt_err_t cdc_open(rt_device_t dev, rt_uint16_t oflag) { (void)dev; (void)oflag; return RT_EOK; }
static rt_err_t cdc_close(rt_device_t dev) { (void)dev; return RT_EOK; }

static rt_ssize_t cdc_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    (void)pos;
    if (!dev || !buffer) return -RT_EINVAL;
    if (size == 0) return 0;

    struct cdc_char_dev *c = (struct cdc_char_dev *)dev;

    /* 未枚举配置完成，不允许读（避免上层误用） */
    if (!c->configured) return -RT_EIO;

    if (!c->rx_rb) return -RT_ERROR;

    rt_base_t level = rt_hw_interrupt_disable();
    rt_size_t got = rt_ringbuffer_get(c->rx_rb, (rt_uint8_t *)buffer, size);
    rt_hw_interrupt_enable(level);

    return (rt_ssize_t)got; /* ringbuffer 空则返回 0 */
}

static rt_ssize_t cdc_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    (void)pos;
    if (!dev || !buffer) return -RT_EINVAL;
    if (size == 0) return 0;

    /* 保险丝：防止上层把负数当 size 传下来 */
    if (size > CDC_WRITE_SIZE_GUARD) return -RT_EINVAL;

    struct cdc_char_dev *c = (struct cdc_char_dev *)dev;

    /* 未配置/断开，不允许写 */
    if (!c->configured) return -RT_EIO;
    if (c->require_dtr_tx && !c->dtr_enable) return -RT_EIO;

    if (!c->tx_sem || !c->tx_lock) return -RT_ERROR;

    rt_mutex_take(c->tx_lock, RT_WAITING_FOREVER);

    rt_size_t sent_total = 0;
    const rt_uint8_t *p = (const rt_uint8_t *)buffer;

    while (sent_total < size)
    {
        /* 如果中途断开/重置，立刻失败 */
        if (!c->configured)
        {
            rt_mutex_release(c->tx_lock);
            return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_EIO;
        }

        rt_size_t chunk = size - sent_total;
        if (chunk > CDC_TX_DMA_SIZE) chunk = CDC_TX_DMA_SIZE;

        memcpy(g_tx_dma, p + sent_total, chunk);

        /* 清掉旧 token */
        while (rt_sem_trytake(c->tx_sem) == RT_EOK) {}

        usbd_ep_start_write(c->busid, CDC_IN_EP, g_tx_dma, (uint32_t)chunk);

        rt_tick_t to = rt_tick_from_millisecond(c->tx_timeout_ms);
        if (rt_sem_take(c->tx_sem, to) != RT_EOK)
        {
            rt_mutex_release(c->tx_lock);
            return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_ETIMEOUT;
        }

        /* 被唤醒后再次确认仍处于 configured（避免断开时“假完成”） */
        if (!c->configured)
        {
            rt_mutex_release(c->tx_lock);
            return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_EIO;
        }

        sent_total += chunk;
    }

    rt_mutex_release(c->tx_lock);
    return (rt_ssize_t)sent_total;
}

static rt_err_t cdc_control(rt_device_t dev, int cmd, void *args)
{
    if (!dev) return -RT_EINVAL;
    struct cdc_char_dev *c = (struct cdc_char_dev *)dev;

    switch (cmd)
    {
    case CDC_CTRL_SET_TX_TIMEOUT_MS:
        if (args) c->tx_timeout_ms = *(uint32_t *)args;
        return RT_EOK;

    case CDC_CTRL_GET_TX_TIMEOUT_MS:
        if (args) *(uint32_t *)args = c->tx_timeout_ms;
        return RT_EOK;

    case CDC_CTRL_GET_DTR:
        if (args) *(uint8_t *)args = c->dtr_enable;
        return RT_EOK;

    case CDC_CTRL_SET_REQUIRE_DTR_TX:
        if (args) c->require_dtr_tx = (*(uint8_t *)args) ? 1 : 0;
        return RT_EOK;

    case CDC_CTRL_GET_CONFIGURED:   /* ✅ 新增：查询是否已枚举配置 */
        if (args) *(uint8_t *)args = c->configured;
        return RT_EOK;

    default:
        return -RT_ENOSYS;
    }
}

/* ops table */
static const struct rt_device_ops cdc_ops =
{
    .init    = cdc_init,
    .open    = cdc_open,
    .close   = cdc_close,
    .read    = cdc_read,
    .write   = cdc_write,
    .control = cdc_control,
};

/* -------------------- CherryUSB callbacks -------------------- */
static void usbd_event_handler(uint8_t busid, uint8_t event)
{
    (void)busid;

    switch (event)
    {
    case USBD_EVENT_RESET:
        g_cdc.configured = 0;
        g_cdc.connected  = 0;

        /* 可选：唤醒等待线程，让它能尽快感知状态变化 */
        if (g_cdc.dev.rx_indicate)
            g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
        break;

    case USBD_EVENT_CONNECTED:
        g_cdc.connected = 1;
        break;

    case USBD_EVENT_DISCONNECTED:
        g_cdc.configured = 0;
        g_cdc.connected  = 0;

        /* 可选：唤醒等待线程 */
        if (g_cdc.dev.rx_indicate)
            g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
        break;

    case USBD_EVENT_CONFIGURED:
        g_cdc.connected  = 1;
        g_cdc.configured = 1;

        /* 重新启动 OUT 读（防止 reset 后没读） */
        usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);

        /* 可选：唤醒等待线程，让“先启动线程再插线”也能快速进入工作状态 */
        if (g_cdc.dev.rx_indicate)
            g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
        break;

    default:
        break;
    }
}

static void cdc_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    (void)busid;
    (void)ep;

    /* 未配置时忽略数据，并继续挂下一次读 */
    if (!g_cdc.configured)
    {
        usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);
        return;
    }

    if (nbytes > 0 && nbytes <= CDC_RX_DMA_SIZE && g_cdc.rx_rb)
    {
        (void)rt_ringbuffer_put(g_cdc.rx_rb, g_rx_dma, (rt_size_t)nbytes);

        if (g_cdc.dev.rx_indicate)
            g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, (rt_size_t)nbytes);
    }

    usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);
}

static void cdc_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    (void)busid;

    /* ZLP：长度是 MPS 整倍数时补 0 包 */
    if ((nbytes % usbd_get_ep_mps(g_cdc.busid, ep)) == 0 && nbytes)
    {
        usbd_ep_start_write(g_cdc.busid, CDC_IN_EP, RT_NULL, 0);
        return;
    }

    if (g_cdc.tx_sem) rt_sem_release(g_cdc.tx_sem);

    if (g_cdc.dev.tx_complete)
        g_cdc.dev.tx_complete((rt_device_t)&g_cdc.dev, RT_NULL);
}

/* DTR hook */
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
{
    (void)busid;
    (void)intf;
    g_cdc.dtr_enable = dtr ? 1 : 0;
}

/* endpoints */
static struct usbd_endpoint g_out_ep = { .ep_addr = CDC_OUT_EP, .ep_cb = cdc_bulk_out };
static struct usbd_endpoint g_in_ep  = { .ep_addr = CDC_IN_EP,  .ep_cb = cdc_bulk_in  };

/* -------------------- public init -------------------- */
int rt_hw_usbd_cdc_char_init(const char *dev_name, uint8_t busid, uintptr_t reg_base)
{
    if (!dev_name) return -RT_EINVAL;

    memset(&g_cdc, 0, sizeof(g_cdc));
    g_cdc.busid = busid;
    g_cdc.reg_base = reg_base;

    g_cdc.tx_timeout_ms = 2000;
    g_cdc.require_dtr_tx = 0;
    g_cdc.connected = 0;
    g_cdc.configured = 0;

    g_cdc.rx_rb = rt_ringbuffer_create(CDC_RX_RING_SIZE);
    if (!g_cdc.rx_rb) return -RT_ENOMEM;

    g_cdc.tx_sem = rt_sem_create("cdc_tx", 0, RT_IPC_FLAG_FIFO);
    if (!g_cdc.tx_sem) return -RT_ENOMEM;

    g_cdc.tx_lock = rt_mutex_create("cdc_txL", RT_IPC_FLAG_FIFO);
    if (!g_cdc.tx_lock) return -RT_ENOMEM;

    /* register char device */
    g_cdc.dev.type = RT_Device_Class_Char;
    g_cdc.dev.rx_indicate = RT_NULL;
    g_cdc.dev.tx_complete = RT_NULL;
    g_cdc.dev.ops = &cdc_ops;
    g_cdc.dev.user_data = RT_NULL;

    rt_err_t r = rt_device_register((rt_device_t)&g_cdc.dev, dev_name, RT_DEVICE_FLAG_RDWR);
    if (r != RT_EOK) return r;

    /* init CherryUSB CDC */
    usbd_desc_register(g_cdc.busid, &g_cdc_desc);

    usbd_add_interface(g_cdc.busid, usbd_cdc_acm_init_intf(g_cdc.busid, &g_cdc.intf0));
    usbd_add_interface(g_cdc.busid, usbd_cdc_acm_init_intf(g_cdc.busid, &g_cdc.intf1));

    g_cdc.out_ep = g_out_ep;
    g_cdc.in_ep  = g_in_ep;
    usbd_add_endpoint(g_cdc.busid, &g_cdc.out_ep);
    usbd_add_endpoint(g_cdc.busid, &g_cdc.in_ep);

    usbd_initialize(g_cdc.busid, g_cdc.reg_base, usbd_event_handler);

    return 0;
}
