#include "tkl_uart.h"
#include <stdio.h>
#include <termios.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include "uni_log.h"
#include <termios.h>
#define MAX_PATH 256

/* system uart name */
#define UART_NAME_PATTERN_SYS "/dev/ttyS%d"
/* USB uart name */
#define UART_NAME_PATTERN_USB "/dev/ttyUSB%d"

#define UART_TYPE_VALID(type) (((type) >= 0) && ((type) < TUYA_UART_MAX_TYPE))
#define UART_DEV_MAX 32
#define UART_DEV_VALID(num) (((num) >= 0) && ((num) < UART_DEV_MAX))

typedef struct
{
    INT_T fd;
    pthread_t tid;
    TUYA_UART_IRQ_CB rx_cb;
    UINT8_T readchar;
    UINT8_T *readbuff;
} uart_dev_t;

static uart_dev_t s_uart_dev[4] = {0};

STATIC BOOL_T uart_dev_valid(INT_T type, INT_T num)
{
    if (!UART_TYPE_VALID(type))
    {
        tkl_log_output("invalid uart type %d\n", type);
        return FALSE;
    }
    if (!UART_DEV_VALID(num))
    {
        tkl_log_output("invalid uart number %d\n", num);
        return FALSE;
    }
    return TRUE;
}

STATIC VOID __irq_handler(VOID *arg)
{
    uart_dev_t *uart_dev = arg;

    for (;;)
    {
        fd_set readfd;

        FD_ZERO(&readfd);
        FD_SET(uart_dev->fd, &readfd);
        select(uart_dev->fd + 1, &readfd, NULL, NULL, NULL);
        if (FD_ISSET(uart_dev->fd, &readfd))
        {
            uart_dev->rx_cb(0);
        }
    }
}

STATIC VOID __udp_irq_handler(VOID *arg)
{
    uart_dev_t *uart_dev = arg;

    for (;;)
    {
        fd_set readfd;
        FD_ZERO(&readfd);
        FD_SET(uart_dev->fd, &readfd);
        select(uart_dev->fd + 1, &readfd, NULL, NULL, NULL);
        if (FD_ISSET(uart_dev->fd, &readfd))
        {
            ssize_t readlen = recvfrom(uart_dev->fd, uart_dev->readbuff, sizeof(uart_dev->readbuff), 0, NULL, 0);
            for (int i = 0; i < readlen; i++)
            {
                uart_dev->readchar = uart_dev->readbuff[i];
                uart_dev->rx_cb(1);
            }
        }
    }
}

/**
 * @brief uart init
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] cfg: uart config
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_init(UINT32_T port_id, TUYA_UART_BASE_CFG_T *cfg)
{
    PR_DEBUG("TKL_Uart_Init port_id =%d \n", port_id);

    if (0 == port_id)
    {
        struct termios term_orig;
        struct termios term_vi;

        s_uart_dev[port_id].fd = open("/dev/stdin", O_RDWR | O_NOCTTY | O_NDELAY);
        if (0 > s_uart_dev[port_id].fd)
        {
            return OPRT_COM_ERROR;
        }

        tcgetattr(s_uart_dev[port_id].fd, &term_orig);
        term_vi = term_orig;
        term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
        term_vi.c_iflag &= (~IXON & ~ICRNL);
        tcsetattr(s_uart_dev[port_id].fd, TCSANOW, &term_vi);

        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_create(&s_uart_dev[port_id].tid, &attr, __irq_handler, &s_uart_dev[port_id]);
        pthread_attr_destroy(&attr);
    }
    else if (1 == port_id)
    {
        s_uart_dev[port_id].fd = socket(AF_INET, SOCK_DGRAM, 0);
        fcntl(s_uart_dev[port_id].fd, F_SETFD, FD_CLOEXEC);

        int port = 7878;
        const char *ip = "172.16.206.81"; // IP地址字符串
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
        address.sin_addr.s_addr = inet_addr(ip);

        if (bind(s_uart_dev[port_id].fd, (struct sockaddr *)&address, sizeof(address)) == -1)
        {
            perror("bind error");
            exit(1);
        }
        if (s_uart_dev[port_id].readbuff == NULL)
            s_uart_dev[port_id].readbuff = (UINT8_T *)malloc(1024);
        pthread_create(&s_uart_dev[port_id].tid, NULL, __udp_irq_handler, &s_uart_dev[port_id]);
    }
    else if (3 == port_id)
    {
        INT_T type = TUYA_UART_GET_PORT_TYPE(port_id);
        INT_T num = TUYA_UART_GET_PORT_NUMBER(port_id);
        CHAR_T pattern[MAX_PATH] = {0};
        CHAR_T path[MAX_PATH] = {0};
        INT_T fd = -1;
        struct termios tty_attr;
        speed_t speed;
        TUYA_UART_FLOWCTRL_TYPE_E flowcntl;

        if (!uart_dev_valid(type, num))
        {
            return OPRT_INVALID_PARM;
        }
        if (cfg == NULL)
        {
            return OPRT_INVALID_PARM;
        }

        // get uart name pattern
        if (type == TUYA_UART_SYS)
        {
            strncpy(pattern, UART_NAME_PATTERN_SYS, strlen(UART_NAME_PATTERN_SYS) + 1);
        }
        else if (type == TUYA_UART_USB)
        {
            strncpy(pattern, UART_NAME_PATTERN_USB, strlen(UART_NAME_PATTERN_USB) + 1);
        }
        else
        {
            tkl_log_output("Invalid uart type %d\n", type);
            return OPRT_INVALID_PARM;
        }

        // get uart name
        sprintf(path, pattern, num);

        fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
        if (fd < 0)
        {
            tkl_log_output("Failed to open %s\n", path);
            return OPRT_NOT_FOUND;
        }

        // configure uart

        memset(&tty_attr, 0, sizeof(struct termios));
        tcgetattr(fd, &tty_attr);

        /*
         * Enable Raw mode: all special processing of input and output
         * characters is disabled.
         */
        tty_attr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
        tty_attr.c_oflag &= ~OPOST;
        tty_attr.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);

        // set baudrate
        switch (cfg->baudrate)
        {
        case 1800:
            speed = B1800;
            break;
        case 2500:
            speed = B2400;
            break;
        case 4800:
            speed = B4800;
            break;
        case 9600:
            speed = B9600;
            break;
        case 38400:
            speed = B38400;
            break;
        case 57600:
            speed = B57600;
            break;
        case 115200:
            speed = B115200;
            break;
        case 230400:
            speed = B230400;
            break;
        case 460800:
            speed = B460800;
            break;
        case 921600:
            speed = B921600;
            break;
        default:
            speed = B9600;
            break;
        }
        cfsetospeed(&tty_attr, speed);
        cfsetispeed(&tty_attr, speed);

        // set databits
        switch (cfg->databits)
        {
        case TUYA_UART_DATA_LEN_5BIT:
            tty_attr.c_cflag = (tty_attr.c_cflag & ~CSIZE) | CS5;
            break;
        case TUYA_UART_DATA_LEN_6BIT:
            tty_attr.c_cflag = (tty_attr.c_cflag & ~CSIZE) | CS6;
            break;
        case TUYA_UART_DATA_LEN_7BIT:
            tty_attr.c_cflag = (tty_attr.c_cflag & ~CSIZE) | CS7;
            break;
        default: // TUYA_UART_DATA_BIT8
            tty_attr.c_cflag = (tty_attr.c_cflag & ~CSIZE) | CS8;
            break;
        }

        // set parity
        switch (cfg->parity)
        {
        case TUYA_UART_PARITY_TYPE_EVEN:
            tty_attr.c_cflag &= ~(PARODD /*| CMSPAR*/);
            tty_attr.c_cflag |= PARENB;
            break;
        case TUYA_UART_PARITY_TYPE_ODD:
            // tty_attr.c_cflag &= ~CMSPAR;
            tty_attr.c_cflag |= PARENB | PARODD;
            break;
        default: // TUYA_UART_PARITY_NONE
            tty_attr.c_cflag &= ~(PARENB | PARODD /*| CMSPAR*/);
            break;
        }

        // set stopbits
        switch (cfg->stopbits)
        {
        case TUYA_UART_STOP_LEN_2BIT:
            tty_attr.c_cflag |= CSTOPB;
            break;
        default: // TUYA_UART_STOP_BIT1
            tty_attr.c_cflag &= ~CSTOPB;
            break;
        }

        // set flowcntrl
        flowcntl = cfg->flowctrl;
        switch (flowcntl)
        {
        case TUYA_UART_FLOWCTRL_RTSCTS:
            tty_attr.c_cflag |= CRTSCTS;
            tty_attr.c_iflag &= ~(IXON | IXOFF | IXANY);
            break;
        case TUYA_UART_FLOWCTRL_XONXOFF:
            tty_attr.c_cflag &= ~(CRTSCTS);
            tty_attr.c_iflag |= IXON | IXOFF;
            break;
        default: // TUYA_UART_FLOWCTRL_NONE
            tty_attr.c_cflag &= ~(CRTSCTS);
            tty_attr.c_iflag &= ~(IXON | IXOFF | IXANY);
            break;
        }

        // set local: local or modem
        tty_attr.c_cflag |= CLOCAL;
        // tty_attr.c_cflag &= ~CLOCAL;

        // set hupcl: hup-on-close
        tty_attr.c_cflag |= HUPCL;
        // tty_attr.c_cflag &= ~HUPCL;

        tty_attr.c_cflag |= CREAD; // enable reception of characters
        tty_attr.c_cc[VMIN] = 1;

        if (tcsetattr(fd, TCSANOW, &tty_attr) < 0)
        {
            close(fd);
            tkl_log_output("Failed to config uart: %s\n", path);
            return OPRT_COM_ERROR;
        }

        // save fd into list
        s_uart_dev[port_id].fd = fd;
        PR_DEBUG("uart init success: %s s_uart_dev[port_id].fd =%d\n", path, s_uart_dev[port_id].fd);
    }

    return OPRT_OK;
}

STATIC VOID disable_flowcontrol(INT_T fd)
{
    struct termios tios;

    memset(&tios, 0, sizeof(tios));

    tcgetattr(fd, &tios);
    tios.c_cflag &= ~CRTSCTS;
    tcsetattr(fd, TCSANOW, &tios);
}

/**
 * @brief uart deinit
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_deinit(UINT32_T port_id)
{
    PR_DEBUG("uart deinit: %d\n", port_id);
    if (3 == port_id)
    {
        INT_T fd = -1;

        fd = s_uart_dev[port_id].fd;
        if (fd == -1 || fd == 0)
        {
            return OPRT_OK;
        }
        disable_flowcontrol(fd);
        close(fd);
        s_uart_dev[port_id].fd = -1;
    }
    else
    {
        close(s_uart_dev[port_id].fd);

        if (1 == port_id)
        {
            pthread_cancel(s_uart_dev[port_id].tid);
            pthread_join(s_uart_dev[port_id].tid, 0);
            if (s_uart_dev[port_id].readbuff)
                free(s_uart_dev[port_id].readbuff);
            s_uart_dev[port_id].readbuff = NULL;
        }
    }

    return OPRT_OK;
}

/**
 * @brief uart write data
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] data: write buff
 * @param[in] len:  buff len
 *
 * @return return > 0: number of data written; return <= 0: write errror
 */
INT_T tkl_uart_write(UINT32_T port_id, VOID_T *buff, UINT16_T len)
{

    printf("TKL_Uart_Write port_id =%d  len: %d\n", port_id, len);
    if (0 == port_id)
    {
        return write(s_uart_dev[port_id].fd, buff, len);
    }
    else if (1 == port_id)
    {
        int port = 7878;
        const char *ip = "172.16.61.117"; // IP地址字符串
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
        address.sin_addr.s_addr = inet_addr(ip);

        return sendto(s_uart_dev[port_id].fd, buff, len, 0, (struct sockaddr *)&address, sizeof(address));
    }
    else if (3 == port_id)
    {
        int ret = write(s_uart_dev[port_id].fd, buff, len);
        if (ret < 0)
        {
            tkl_log_output("uart send failed, port=%d, fd=%d\n", port_id, s_uart_dev[port_id].fd);
            return OPRT_SEND_ERR;
        }
        fsync(s_uart_dev[port_id].fd);

        return ret;
    }

    return -1;
}

/**
 * @brief enable uart rx interrupt and regist interrupt callback
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] rx_cb: receive callback
 *
 * @return none
 */
VOID_T tkl_uart_rx_irq_cb_reg(UINT32_T port_id, TUYA_UART_IRQ_CB rx_cb)
{
    s_uart_dev[port_id].rx_cb = rx_cb;
    return;
}

/**
 * @brief regist uart tx interrupt callback
 * If this function is called, it indicates that the data is sent asynchronously through interrupt,
 * and then write is invoked to initiate asynchronous transmission.
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] rx_cb: receive callback
 *
 * @return none
 */
VOID_T tkl_uart_tx_irq_cb_reg(UINT32_T port_id, TUYA_UART_IRQ_CB tx_cb)
{
    return;
}

/**
 * @brief uart read data
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[out] data: read data
 * @param[in] len:  buff len
 *
 * @return return >= 0: number of data read; return < 0: read errror
 */
INT_T tkl_uart_read(UINT32_T port_id, VOID_T *buff, UINT16_T len)
{
    PR_DEBUG("TKL_Uart_Read port_id =%d  len: %d\n", port_id, len);
    if (0 == port_id)
    {
        return read(s_uart_dev[port_id].fd, buff, len);
    }
    else if (1 == port_id)
    {
        *(uint8_t *)buff = s_uart_dev[port_id].readchar;
        return 1;
    }
    else if (3 == port_id)
    {
        if (s_uart_dev[port_id].fd == -1 || s_uart_dev[port_id].fd == 0)
            return OPRT_COM_ERROR;

        int ret = read(s_uart_dev[port_id].fd, buff, len);
        if (ret < 0)
        {
            tkl_log_output("uart recv failed, port=0x%X\n", port_id);
            return OPRT_RECV_ERR;
        }
        return ret;
    }

    return -1;
}

/**
 * @brief set uart transmit interrupt status
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] enable: TRUE-enalbe tx int, FALSE-disable tx int
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_set_tx_int(UINT32_T port_id, BOOL_T enable)
{
    return OPRT_NOT_SUPPORTED;
}

/**
 * @brief set uart receive flowcontrol
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] enable: TRUE-enalbe rx flowcontrol, FALSE-disable rx flowcontrol
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_set_rx_flowctrl(UINT32_T port_id, BOOL_T enable)
{
    return OPRT_NOT_SUPPORTED;
}

/**
 * @brief wait for uart data
 *
 * @param[in] port_id: uart port id, id index starts at 0
 *                     in linux platform,
 *                         high 16 bits aslo means uart type,
 *                                   it's value must be one of the TUYA_UART_TYPE_E type
 *                         the low 16bit - means uart port id
 *                         you can input like this TUYA_UART_PORT_ID(TUYA_UART_SYS, 2)
 * @param[in] timeout_ms: the max wait time, unit is millisecond
 *                        -1 : block indefinitely
 *                        0  : non-block
 *                        >0 : timeout in milliseconds
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_wait_for_data(UINT32_T port_id, INT_T timeout_ms)
{
    // PR_DEBUG("TKL_Uart_Wait_For_Data port_id =%d  timeout_ms: %d\n", port_id, timeout_ms);
    if (3 == port_id)
    {
        {
            INT_T fd = -1;
            fd_set read_set;
            INT_T ret = 0;
            struct timeval timeval_uart = {
                .tv_sec = timeout_ms / 1000,
                .tv_usec = (timeout_ms % 1000) * 1000};
            struct timeval *p_timeval = NULL;

            if (timeout_ms != -1)
            { // -1 : block indefinitely
                p_timeval = &timeval_uart;
            }

            fd = s_uart_dev[port_id].fd;
            if (fd == -1 || fd == 0)
            {
                return OPRT_COM_ERROR;
            }

            FD_ZERO(&read_set);
            FD_SET(fd, &read_set);

            if (select(fd + 1, &read_set, NULL, NULL, p_timeval) <= 0)
            {
                // tkl_log_output("uart recv timeout\n");
                return OPRT_TIMEOUT;
            }

            if (!FD_ISSET(fd, &read_set))
            {
                return OPRT_COM_ERROR;
            }
            PR_DEBUG("uart recv ok\n");
            return OPRT_OK;
        }
    }
    else
        return OPRT_NOT_SUPPORTED;
}

/**
 * @brief uart control
 *
 * @param[in] uart refer to tuya_uart_t
 * @param[in] cmd control command
 * @param[in] arg command argument
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_uart_ioctl(UINT32_T port_id, UINT32_T cmd, VOID *arg)
{
    return OPRT_NOT_SUPPORTED;
}
