/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021.12.07     linzhenxing      first version
 */
#include <rtthread.h>
#include <dfs_file.h>
#include <dfs_fs.h>
#include <tty.h>

#define DBG_TAG               "CONSOLE"
#ifdef RT_TTY_DEBUG
#define DBG_LVL               DBG_LOG
#else
#define DBG_LVL               DBG_INFO
#endif /* RT_TTY_DEBUG */
#include <rtdbg.h>

static struct tty_struct console_dev;

static void console_rx_notify(struct rt_device *dev)
{
    struct tty_struct *console = NULL;
    int len = 0;
    int lens = 0;
    char ch = 0;
    char buf[1024] = {0};

    console = (struct tty_struct *)dev;
    RT_ASSERT(console != RT_NULL);

    while (1)
    {
        len = rt_device_read(console->io_dev, -1, &ch, 1);
        if (len == 0)
        {
            break;
        }
        lens += len;
        buf[lens-1] = ch;
        if (lens > 1024)
        {
            break;
        }
    }

    if (console->ldisc->ops->receive_buf)
    {
        console->ldisc->ops->receive_buf((struct tty_struct *)console, buf, lens);
    }
}

struct tty_struct *console_tty_get(void)
{
    return &console_dev;
}

static void iodev_close(struct tty_struct *console)
{
    struct rt_device_notify rx_notify;

    rx_notify.notify = RT_NULL;
    rx_notify.dev = RT_NULL;

    /* clear notify */
    rt_device_control(console->io_dev, RT_DEVICE_CTRL_NOTIFY_SET, &rx_notify);
    rt_device_close(console->io_dev);
}

static rt_err_t iodev_open(struct tty_struct *console)
{
    rt_err_t ret = RT_EOK;
    struct rt_device_notify rx_notify;
    rt_uint16_t oflags = 0;

    rt_device_control(console->io_dev, RT_DEVICE_CTRL_CONSOLE_OFLAG, &oflags);

    ret = rt_device_open(console->io_dev, oflags);
    if (ret != RT_EOK)
    {
        return -RT_ERROR;
    }

    rx_notify.notify = console_rx_notify;
    rx_notify.dev = (struct rt_device *)console;
    rt_device_control(console->io_dev, RT_DEVICE_CTRL_NOTIFY_SET, &rx_notify);
    return RT_EOK;
}

struct rt_device *console_get_iodev(void)
{
    rt_base_t level = 0;
    struct rt_device *iodev = RT_NULL;

    level = rt_hw_interrupt_disable();
    iodev = console_dev.io_dev;
    rt_hw_interrupt_enable(level);

    return iodev;
}

struct rt_device *console_set_iodev(struct rt_device *iodev)
{
    rt_base_t level = 0;
    struct rt_device *io_before = RT_NULL;
    struct tty_struct *console = RT_NULL;

    RT_ASSERT(iodev != RT_NULL);

    console = &console_dev;

    level = rt_hw_interrupt_disable();

    RT_ASSERT(console->init_flag >= TTY_INIT_FLAG_REGED);

    io_before = console->io_dev;
    if (iodev == io_before)
    {
        goto exit;
    }

    if (console->init_flag >= TTY_INIT_FLAG_INITED)
    {
        /* close old device */
        iodev_close(console);
    }

    console->io_dev = iodev;
    if (console->init_flag >= TTY_INIT_FLAG_INITED)
    {
        rt_err_t ret;
        /* open new device */
        ret = iodev_open(console);
        RT_ASSERT(ret == RT_EOK);
    }

exit:
    rt_hw_interrupt_enable(level);
    return io_before;
}

/* RT-Thread Device Interface */
/*
 * This function initializes console device.
 */
static rt_err_t rt_console_init(struct rt_device *dev)
{
    rt_base_t level = 0;
    rt_err_t result = RT_EOK;
    struct tty_struct *console = RT_NULL;

    RT_ASSERT(dev != RT_NULL);

    console = (struct tty_struct *)dev;

    level = rt_hw_interrupt_disable();

    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_REGED);

    result = iodev_open(console);
    if (result != RT_EOK)
    {
        goto exit;
    }

    console->init_flag = TTY_INIT_FLAG_INITED;
exit:
    rt_hw_interrupt_enable(level);
    return result;
}

static rt_err_t rt_console_open(struct rt_device *dev, rt_uint16_t oflag)
{
    rt_err_t result = RT_EOK;
    struct tty_struct *console = RT_NULL;

    RT_ASSERT(dev != RT_NULL);
    console = (struct tty_struct *)dev;
    RT_ASSERT(console != RT_NULL);
    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_INITED);
    return result;
}

static rt_err_t rt_console_close(struct rt_device *dev)
{
    rt_err_t result = RT_EOK;
    struct tty_struct *console = RT_NULL;

    console = (struct tty_struct *)dev;
    RT_ASSERT(console != RT_NULL);
    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_INITED);
    return result;
}

static rt_ssize_t rt_console_read(struct rt_device *dev,
        rt_off_t          pos,
        void             *buffer,
        rt_size_t         size)
{
    rt_size_t len = 0;
    return len;
}

static rt_ssize_t rt_console_write(struct rt_device *dev,
        rt_off_t          pos,
        const void       *buffer,
        rt_size_t         size)
{
    rt_size_t len = 0;
    struct tty_struct *console = RT_NULL;

    console = (struct tty_struct *)dev;
    RT_ASSERT(console != RT_NULL);
    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_INITED);

    len = rt_device_write((struct rt_device *)console->io_dev, -1, buffer, size);

    return len;
}

static rt_err_t  rt_console_control(rt_device_t dev, int cmd, void *args)
{
    rt_size_t len = 0;
    struct tty_struct *console = RT_NULL;

    console = (struct tty_struct *)dev;
    RT_ASSERT(console != RT_NULL);
    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_INITED);

    len = rt_device_control((struct rt_device *)console->io_dev, cmd, args);

    return len;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops console_ops =
{
    rt_console_init,
    rt_console_open,
    rt_console_close,
    rt_console_read,
    rt_console_write,
    rt_console_control,
};
#endif

/*
 * console register
 */
static struct dfs_file_ops con_fops;
rt_err_t console_register(const char *name, struct rt_device *iodev)
{
    rt_err_t ret = RT_EOK;
    struct rt_device *device = RT_NULL;
    struct tty_struct *console = &console_dev;

    RT_ASSERT(iodev != RT_NULL);
    RT_ASSERT(console->init_flag == TTY_INIT_FLAG_NONE);

    tty_init(console, TTY_DRIVER_TYPE_CONSOLE, SERIAL_TYPE_NORMAL, iodev);
    console_ldata_init(console);

    device = &(console->parent);
    device->type        = RT_Device_Class_Char;

#ifdef RT_USING_DEVICE_OPS
    device->ops         = &console_ops;
#else
    device->init        = rt_console_init;
    device->open        = rt_console_open;
    device->close       = rt_console_close;
    device->read        = rt_console_read;
    device->write       = rt_console_write;
    device->control     = rt_console_control;
#endif

    /* register a character device */
    ret = rt_device_register(device, name, 0);
    if (ret != RT_EOK)
    {
        LOG_E("console driver register fail\n");
    }
    else
    {
#ifdef RT_USING_POSIX_DEVIO
        /* set fops */
        memcpy(&con_fops, tty_get_fops(), sizeof(struct dfs_file_ops));
        device->fops = &con_fops;
#endif
    }

    return ret;
}
