#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>

#include "serial.h"
#include "debug.h"

int serial_band_convert(unsigned int baudrate)
{
    switch (baudrate)
    {
    case 2400:
        return B2400;

    case 4800:
        return B4800;

    case 9600:
        return B9600;

    case 19200:
        return B19200;

    case 38400:
        return B38400;

    case 57600:
        return B57600;

    case 115200:
        return B115200;
        
    case 921600:
        return B921600;

    case 1152000:
        return B1152000;

    default:
        return B115200;
    }
}

int serial_attr_set(int fd, serial_attr_t * attr)
{
    unsigned char  databit  = 0;
    unsigned char  stopbit  = 0;
    unsigned char  parity   = 0;
    unsigned char  fctl     = 0;
    int  ret = -1;
    unsigned int baudrate = 0;

    struct termios termios_old;
    struct termios termios_new;

    memset(&termios_old, 0, sizeof(termios_old));
    memset(&termios_new, 0, sizeof(termios_new));

    //CHECK_SERIAL_PORT_FD;

    cfmakeraw(&termios_new);

    if (tcgetattr(fd, &termios_old) != 0)
    {
        perror("Setup Serial!\r\n");
        return ret;
    }

    //baudrates
    baudrate = serial_band_convert(attr->baudrate);
    cfsetispeed(&termios_new, baudrate);
    cfsetospeed(&termios_new, baudrate);
    termios_new.c_cflag |= CLOCAL;
    termios_new.c_cflag |= CREAD;

    //flow control
    fctl = attr-> fctl;
    switch (fctl)
    {
    case 0:
        termios_new.c_cflag &= ~CRTSCTS;        //no flow control
        break;
    case 1:
        termios_new.c_cflag |= CRTSCTS;         //hardware flow control
        break;

    case 2:
        termios_new.c_iflag |= IXON | IXOFF | IXANY; //software flow control
        break;
    }

    //data bits
    termios_new.c_cflag &= ~CSIZE;      //控制模式，屏蔽字符大小位
    databit = attr->databit;
    switch (databit)
    {
    case 5:
        termios_new.c_cflag |= CS5;
        break;

    case 6:
        termios_new.c_cflag |= CS6;
        break;

    case 7:
        termios_new.c_cflag |= CS7;
        break;

    case 8:
        termios_new.c_cflag |= CS8;
        break;

    default:
        termios_new.c_cflag |= CS8;
        break;
    }

    //parity check
    parity = attr->parity;
    switch (parity)
    {
    case 0:             //no parity check
        termios_new.c_cflag &= ~PARENB;
        break;

    case 1:             //odd check
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag &= ~PARODD;
        break;

    case 2:             //even check
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag |= PARODD;
        break;
    }

    //stop bits
    stopbit = attr->stopbit;
    if (stopbit == 2)
    {
        termios_new.c_cflag |= CSTOPB;
    }
    else if (stopbit == 1)
    {
        termios_new.c_cflag &= ~CSTOPB;
    }

    //other attributions default
    termios_new.c_oflag &= ~OPOST;
    termios_new.c_cc[VMIN]  = 1;
    termios_new.c_cc[VTIME] = 1;
    tcflush(fd, TCIFLUSH);
    ret = tcsetattr(fd, TCSANOW, &termios_new);

    return ret;
}

int serial_open(const char * devname, int baudrate)
{
    int fd;
    serial_attr_t attr;
    fd = open(devname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0)
    {
        LOG("Open SerialPort(%s) failed[error = %d, %s]!!!!!!!\n\n",
            devname, errno, strerror(errno));
        return -1;
    }

    strcpy(attr.tty, devname);
    attr.baudrate = baudrate;

    serial_attr_set(fd, &attr);

    LOG("Open SerialPort(%s:%d) port success!!!\n", devname, baudrate);

    return fd;
}

#if 1
int serial_read(int fd, unsigned char * data, int len)
{
    int ret = -1;
    fd_set fs_read;
    int maxfd = -1;

    //CHECK_SERIAL_PORT_FD;
    if (fd < 0)
    {
        LOG("error fd = %d\n", fd);
        return -1;
    }

    if (data == NULL || len <= 0)
    {
        LOG("Param Error, NULL == data || len <= 0.\n");
        return -1;
    }
//#if 0
    FD_ZERO(&fs_read);
    FD_SET(fd, &fs_read);
    maxfd = fd + 1;
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 1000 * 1000;

    ret = select(maxfd, &fs_read, NULL, NULL, &tv);
    if (ret < 0)
    {
        LOG("select error[%d, %s]!!!\n", errno, strerror(errno));
        return -1;
    }

    if (ret == 0)
    {
        LOG("Timeout......\n\n\n");
        return -1;
    }
//#endif

    ret = read(fd, data, len);
    if (ret < 0)
    {
        LOG("read error[%d, %s]\n", errno, strerror(errno));
        return -1;
    }    

    return ret;
}
#else
int serial_read(int fd, unsigned char * data, int len)
{
    int ret, fs_sel;
    fd_set fs_read;

    struct timeval time;

    FD_ZERO(&fs_read);
    FD_SET(fd, &fs_read);
    time.tv_sec = 1;
    time.tv_usec = 0; //1s
    //使用select实现串口的多路通信
    fs_sel = select(fd + 1, &fs_read, NULL, NULL, &time);
    if(fs_sel)
    {
        ret = read(fd, data, len);
        printf("read len=%d\n", ret);
        print_raw_data(data, len);
        return ret;
    }
    return 0;
    // return read(fd, rcv_buf, rcv_len);
}

#endif

#if 1
int serial_write(int fd, unsigned char * data, int len)
{
    //CHECK_SERIAL_PORT_FD;
    if (fd < 0)
    {
        LOG("error fd = %d\n", fd);
        return -1;
    }

    if (NULL == data || len <= 0)
    {
        LOG("Param Error, NULL == data or len = %d.\n", len);
        return -1;
    }

    fd_set  fs_write;
    FD_ZERO(&fs_write);
    FD_SET(fd, &fs_write);

    int maxfd = fd + 1;
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 1000 * 1000;

    int ret = select(maxfd, NULL, &fs_write, NULL, &tv);
    if (ret < 0)
    {
        LOG("select Error[errno = %d, %s]", errno, strerror(errno));
        return -1;
    }
    else if (ret == 0)
    {
        LOG("Timeout!\n");
        return -1;
    }

    if (FD_ISSET(fd, &fs_write))
    {
        int pos = 0;

        while (len > 0)
        {
            ret = write(fd, data + pos, len);
            if (ret < 0)
            {
                LOG("write com[fd = %d] error[errno = %d, %s]\n", fd, errno, strerror(errno));
                return -1;
            }
            len -= ret;
            pos += ret;
        }
    }

    return len;
}
#else
int serial_write(int fd, unsigned char * data, int len)
{
    int ret;
    print_raw_data(data, len);
    ret = write(fd, data, len);
    if (len == ret)
    {
        return 1;
    }
    else
    {
        tcflush(fd, TCOFLUSH);
        return 0;
    }
    // return write(fd, send_buf, data_len);
}

#endif

void serial_close(int fd)
{
    if (fd > 0)
    {
        close(fd);
    }
}

