#include <stdio.h>  /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>   /*文件控制定义*/
#include <termios.h> /*PPSIX 终端控制定义*/
#include <errno.h>   /*错误号定义*/
#include <string.h>
#include <pthread.h>
#include <limits.h>
#include <getopt.h>

#include "uart.h"

enum mode
{
    STRING = 0,
    HEX = 1,
};

int print_mode = HEX;

#define SELECT_INIT()   \
    do                  \
    {                   \
        maxfd = 0;      \
        FD_ZERO(&rset); \
    } while (0)

#define SELECT_ADD_FD(fd)      \
    do                         \
    {                          \
        if (fd > 0)            \
        {                      \
            FD_SET(fd, &rset); \
            if (maxfd < fd)    \
                maxfd = fd;    \
        }                      \
    } while (0)

void *recv_loop(void *param)
{
    int fd = *((int *)param);
    int ret = -1, maxfd, i;
    fd_set rset;
    struct timeval timeout;
    unsigned char rcv_buf[1024];
    unsigned char print_buf[2048];
    int len = 0;
    int count = 0;

    while (1)
    {
        SELECT_INIT();
        SELECT_ADD_FD(fd);

        timeout.tv_usec = 0;
        timeout.tv_sec = 10;

        ret = select(maxfd + 1, &rset, 0, 0, &timeout);
        if (ret <= 0)
        {
            break;
        }
        else if (ret > 0)
        {
            if (fd > 0 && FD_ISSET(fd, &rset))
            {
                FD_CLR(fd, &rset);
                while (1)
                {
                    ret = read(fd, rcv_buf + len, 1024 - len);
                    if (ret > 0)
                    {
                        len += ret;
                        count = 0;
                    }
                    else
                    {
                        count++;
                        if (count < 10)
                        {
                            usleep(20 * 1000);
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                if (print_mode == HEX)
                {
                    for (i = 0; i < len; i++)
                    {
                        printf("%02X", rcv_buf[i]);
                    }
                }
                else
                {
                    printf(rcv_buf);
                }
            }
        }
        break;
    }

    return NULL;
}

static int hex_str_to_data_array(char *hex_str, unsigned char *buf, int buf_len)
{
    char *p = NULL;
    int i = 0;
    int len = 0;

    while (i < strlen(hex_str))
    {
        char c = 0;

        if (hex_str[i] >= '0' && hex_str[i] <= '9')
        {
            c = (hex_str[i] - '0');
        }
        else if (hex_str[i] >= 'A' && hex_str[i] <= 'F')
        {
            c = (hex_str[i] - 'A' + 10);
        }
        else if (hex_str[i] >= 'a' && hex_str[i] <= 'f')
        {
            c = (hex_str[i] - 'a' + 10);
        }

        c = c << 4;

        i++;
        if (hex_str[i] >= '0' && hex_str[i] <= '9')
        {
            c += (hex_str[i] - '0');
        }
        else if (hex_str[i] >= 'A' && hex_str[i] <= 'F')
        {
            c += (hex_str[i] - 'A' + 10);
        }
        else if (hex_str[i] >= 'a' && hex_str[i] <= 'f')
        {
            c += (hex_str[i] - 'a' + 10);
        }
        i++;

        if (len >= buf_len)
        {
            break;
        }
        buf[len] = c;
        len++;
    }

    return len;
}

// show help
void usage(void)
{
    printf("uart_tool --- \n");
    printf("Usage: uart_tool [options]...\n");
    printf("\n");
    printf("Options:\n");
    printf("--speed   9600,115200\n");
    printf("--parity  0 - None; 1 - odd; 2 - even\n");
}

static struct option long_options[] = {
    {"speed", required_argument, NULL, 0},
    {"stop", required_argument, NULL, 0},
    {"parity", required_argument, NULL, 0},
    {"bits", required_argument, NULL, 0},
    {"dev", required_argument, NULL, 'd'},
    {"mode", required_argument, NULL, 'm'},
    {"data", required_argument, NULL, 0},
    {"pmode", required_argument, NULL, 0},
    {"help", no_argument, NULL, 'h'},
    {0, 0, 0, 0},
};

int main(int argc, char **argv)
{
    extern char *optarg;
    extern int optind, opterr, optopt;
    char dev[32];
    int mode = HEX;
    int c;
    char *endptr;
    int speed;
    int stop;
    int parity;
    int bits;
    char tmp[256];
    unsigned char send_buf[256];
    int len;
    int fd = -1;
    pthread_t thread_rcv;

    while (1)
    {
        int this_option_optind = optind ? optind : 1;
        int option_index = 0;
        c = getopt_long(argc, argv, "d:m:h", long_options, &option_index);
        if (c == -1)
            break;
        switch (c)
        {
        case 0:
            if (strcmp(long_options[option_index].name, "speed") == 0)
            {
                errno = 0; /* To distinguish success/failure after call */
                speed = strtol(optarg, &endptr, 10);
                if ((errno == ERANGE && (speed == LONG_MAX || speed == LONG_MIN)) || (errno != 0 && speed == 0))
                {
                    perror("strtol");
                    exit(EXIT_FAILURE);
                }

                if (endptr == optarg)
                {
                    fprintf(stderr, "No digits were found\n");
                    exit(EXIT_FAILURE);
                }
            }
            else if (strcmp(long_options[option_index].name, "stop") == 0)
            {
                errno = 0; /* To distinguish success/failure after call */
                stop = strtol(optarg, &endptr, 10);
                if ((errno == ERANGE && (stop == LONG_MAX || stop == LONG_MIN)) || (errno != 0 && stop == 0))
                {
                    perror("strtol");
                    exit(EXIT_FAILURE);
                }

                if (endptr == optarg)
                {
                    fprintf(stderr, "No digits were found\n");
                    exit(EXIT_FAILURE);
                }
            }
            else if (strcmp(long_options[option_index].name, "parity") == 0)
            {
                errno = 0; /* To distinguish success/failure after call */
                parity = strtol(optarg, &endptr, 10);
                if ((errno == ERANGE && (parity == LONG_MAX || parity == LONG_MIN)) || (errno != 0 && parity == 0))
                {
                    perror("strtol");
                    exit(EXIT_FAILURE);
                }

                if (endptr == optarg)
                {
                    fprintf(stderr, "No digits were found\n");
                    exit(EXIT_FAILURE);
                }
            }
            else if (strcmp(long_options[option_index].name, "bits") == 0)
            {
                errno = 0; /* To distinguish success/failure after call */
                bits = strtol(optarg, &endptr, 10);
                if ((errno == ERANGE && (bits == LONG_MAX || bits == LONG_MIN)) || (errno != 0 && bits == 0))
                {
                    perror("strtol");
                    exit(EXIT_FAILURE);
                }

                if (endptr == optarg)
                {
                    fprintf(stderr, "No digits were found\n");
                    exit(EXIT_FAILURE);
                }
            }
            else if (strcmp(long_options[option_index].name, "data") == 0)
            {
                strcpy(tmp, optarg);
            }
            else if (strcmp(long_options[option_index].name, "pmode") == 0)
            {
                strcpy(tmp, optarg);
                if (strcmp(optarg, "hex") == 0)
                {
                    print_mode = HEX;
                }
                else if (strcmp(optarg, "string") == 0)
                {
                    print_mode = STRING;
                }
                break;
            }
            break;
        case 'd':
            // printf("receive d with argument :%s\n", optarg);
            strcpy(dev, optarg);
            break;
        case 'm':
            // printf("receive m with argument :%s\n", optarg);
            if (strcmp(optarg, "hex") == 0)
            {
                mode = HEX;
            }
            else if (strcmp(optarg, "string") == 0)
            {
                mode = STRING;
            }
            break;
        case 'h':
            printf("help\n");
            usage();
            break;
        default:
            printf("?? getopt returned character code 0%o ??\n", c);
        }
    }

    if (mode == HEX)
    {
        len = hex_str_to_data_array(tmp, send_buf, sizeof(send_buf));
    }
    else if (mode == STRING)
    {
        strcpy(send_buf, tmp);
        len = strlen(send_buf);
    }
    // printf("write dev:%s speed:%d data:%s buf[0]:%02X buf[%d]=%02X\n", dev, speed, tmp, send_buf[0], len - 1, send_buf[len - 1]);

    fd = uart_init(dev, speed, stop, parity, bits, 1);
    if (fd > 0)
    {
        sleep(1);
    }
    else
    {
        fprintf(stderr, "dev:%s open failed\n", dev);
        exit(EXIT_FAILURE);
    }

    pthread_create(&thread_rcv, NULL, recv_loop, (void *)&fd);

    len = write(fd, send_buf, len);
    if (len > 0)
    {
        // printf("send %d data successful\n", len);
    }
    else
    {
        printf("send data failed!\n");
    }
    pthread_join(thread_rcv, NULL);

    uart_uninit(fd);
    return 0;
}