/*
 * Copyright (c) 2025, sakumisu
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <rtthread.h>
#include <rtdevice.h>

#include "usbh_core.h"
#include "usbh_serial.h"

static rt_err_t rt_usbh_serial_open(struct rt_device *dev, rt_uint16_t oflag)
{
    struct usbh_serial *serial;

    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);

    serial = (struct usbh_serial *)dev->user_data;

    serial = usbh_serial_open(serial->hport->config.intf[serial->intf].devname, USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
    if (serial == RT_NULL) {
        USB_LOG_ERR("serial open failed\n");
        return -RT_ERROR;
    }

    struct usbh_serial_termios termios;

    memset(&termios, 0, sizeof(termios));
    termios.baudrate = 115200;
    termios.stopbits = 0;
    termios.parity = 0;
    termios.databits = 8;
    termios.rtscts = false;
    termios.rx_timeout = 0;

    usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);

    return RT_EOK;
}

static rt_err_t rt_usbh_serial_close(struct rt_device *dev)
{
    struct usbh_serial *serial;

    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);

    serial = (struct usbh_serial *)dev->user_data;

    usbh_serial_close(serial);

    return RT_EOK;
}

static rt_ssize_t rt_usbh_serial_read(struct rt_device *dev,
                                      rt_off_t pos,
                                      void *buffer,
                                      rt_size_t size)
{
    struct usbh_serial *serial;

    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);

    serial = (struct usbh_serial *)dev->user_data;

    return usbh_serial_read(serial, buffer, size);
}

static rt_ssize_t rt_usbh_serial_write(struct rt_device *dev,
                                       rt_off_t pos,
                                       const void *buffer,
                                       rt_size_t size)
{
    struct usbh_serial *serial;
    int ret = 0;
    rt_uint8_t *align_buf;

    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);

    serial = (struct usbh_serial *)dev->user_data;

    align_buf = (rt_uint8_t *)buffer;

    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
        align_buf = rt_malloc_align(USB_ALIGN_UP(size, CONFIG_USB_ALIGN_SIZE), CONFIG_USB_ALIGN_SIZE);
        if (!align_buf) {
            USB_LOG_ERR("serial get align buf failed\n");
            return 0;
        }

        usb_memcpy(align_buf, buffer, size);
    }

    ret = usbh_serial_write(serial, align_buf, size);

    if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) {
        rt_free_align(align_buf);
    }

    return ret;
}

static rt_err_t rt_usbh_serial_control(struct rt_device *dev,
                                       int cmd,
                                       void *args)
{
    struct usbh_serial *serial;
    struct serial_configure *config;
    int ret = -RT_EINVAL;

    RT_ASSERT(dev != RT_NULL && dev->user_data != RT_NULL);

    serial = (struct usbh_serial *)dev->user_data;

    switch (cmd) {
        case RT_DEVICE_CTRL_CONFIG: {
            config = (struct serial_configure *)args;

            struct usbh_serial_termios termios;

            memset(&termios, 0, sizeof(termios));
            termios.baudrate = config->baud_rate;
            termios.stopbits = 0;
            termios.parity = config->parity;
            termios.databits = config->data_bits;
            termios.rtscts = false;
            termios.rx_timeout = 0;

            usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
        } break;

        default:
            break;
    }

    return ret;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops usbh_serial_ops = {
    NULL,
    rt_usbh_serial_open,
    rt_usbh_serial_close,
    rt_usbh_serial_read,
    rt_usbh_serial_write,
    rt_usbh_serial_control
};
#endif

rt_err_t usbh_serial_register(struct usbh_serial *serial)
{
    rt_err_t ret;
    struct rt_device *device;
    RT_ASSERT(serial != RT_NULL);

    device = rt_malloc(sizeof(struct rt_device));
    if (device == RT_NULL) {
        USB_LOG_ERR("serial device malloc failed\n");
        return -RT_ENOMEM;
    }
    memset(device, 0, sizeof(struct rt_device));

    device->type = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    device->ops = &usbh_serial_ops;
#else
    device->init = NULL;
    device->open = rt_usbh_serial_open;
    device->close = rt_usbh_serial_close;
    device->read = rt_usbh_serial_read;
    device->write = rt_usbh_serial_write;
    device->control = rt_usbh_serial_control;
#endif
    device->user_data = serial;
    serial->user_data = device;

    /* skip /dev/ to avoid BAD file */
    const char *dev_name = serial->hport->config.intf[serial->intf].devname;
    if (strncmp(dev_name, "/dev/", 5) == 0) {
        dev_name += 5;
    }

    /* register a character device */
    ret = rt_device_register(device, dev_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_REMOVABLE);

#ifdef RT_USING_POSIX_DEVIO
    /* set fops */
    device->fops = &usbh_serial_fops;
#endif
    return ret;
}

void usbh_serial_unregister(struct usbh_serial *serial)
{
    struct rt_device *device;

    RT_ASSERT(serial != NULL && serial->user_data != NULL);

    device = (struct rt_device *)serial->user_data;

    rt_device_unregister(device);
    rt_free(device);
}

void usbh_serial_run(struct usbh_serial *serial)
{
    usbh_serial_register(serial);
}

void usbh_serial_stop(struct usbh_serial *serial)
{
    usbh_serial_unregister(serial);
}