#include "ups.h"
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <linux/serial.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/param.h>
#include <termios.h>
#include "common.h"

int ups_connect_uart(ups_hd_t *hd)
{
    uart_attr *ctx_uart = (uart_attr *)hd->data;
    int fd = open_uart(ctx_uart);
    if (fd > 0)
    {
        hd->fd = fd;
        return 0;
    }
    return -1;
}

int ups_tx_uart(ups_hd_t *hd, const void *buff, uint32_t len)
{
    return write(hd->fd, buff, len);
}

int ups_rx_uart(ups_hd_t *hd, void *buff, uint32_t to_read)
{
    uint8_t *dest_buff = (uint8_t *)buff;
    int s_rc;
    int rx_len = 0;
    struct timeval tv;
    tv.tv_sec = hd->read_time_out / 1000;
    tv.tv_usec = (hd->read_time_out % 1000) * 1000;
    fd_set rset;
    FD_ZERO(&rset);
    FD_SET(hd->fd, &rset);

    do
    {
        while ((s_rc = select(hd->fd + 1, &rset, NULL, NULL, &tv)) == -1)
        {
            if (errno == EINTR)
            {
                proto_syslog(LOG_ERR, "A non blocked signal was caught\n");
                FD_ZERO(&rset);
                FD_SET(hd->fd, &rset);
            }
            else
            {
                return -1;
            }
        }

        if (s_rc == 0)
        {
            errno = ETIMEDOUT;
            break;
        }

        rx_len += read(hd->fd, dest_buff + rx_len, to_read - rx_len);
        tv.tv_sec = (hd->interval > 1000) ? hd->interval / 1000 : 0;
        tv.tv_usec = (hd->interval % 1000) * 1000;
    } while (to_read > rx_len);

    return rx_len;
}

int ups_dis_connect_uart(ups_hd_t *hd)
{
    uart_attr *ctx_uart = hd->data;
    tcsetattr(hd->fd, TCSANOW, &ctx_uart->old_tios);
    int re = close(hd->fd);
    if (re == 0)
    {
        hd->fd = -1;
    }
    return re;
}

int ups_free_uart(ups_hd_t *hd)
{
    if (hd == NULL)
    {
        return -1;
    }

    if (hd->fd > -1)
    {
        ups_dis_connect_uart(hd);
    }

    if (hd->data != NULL)
    {
        free(hd->data);
    }

    free(hd);
    return 0;
}

int ups_flush_uart(ups_hd_t *hd)
{
    return tcflush(hd->fd, TCIOFLUSH);
}
ups_if ups_uart_if =
    {
        .connect = ups_connect_uart,
        .tx = ups_tx_uart,
        .rx = ups_rx_uart,
        .flush = ups_flush_uart,
        .dis_connect = ups_dis_connect_uart,
        .free = ups_free_uart,
};

ups_hd_t *new_ups_uart(char *dev, int baud, uint8_t data_bit, uint8_t stop_bit, char parity)
{
    ups_hd_t *hd = calloc(sizeof(ups_hd_t), 1);
    if (hd == NULL)
    {
        proto_syslog(LOG_ERR, "calloc error for hd\n");
        return NULL;
    }

    uart_attr *uart_data = calloc(sizeof(uart_attr), 1);
    if (uart_data == NULL)
    {
        free(hd);
        proto_syslog(LOG_ERR, "calloc error for data\n");
        return NULL;
    }
    uart_data->baud = baud;
    uart_data->data_bit = data_bit;
    uart_data->stop_bit = stop_bit;
    uart_data->parity = parity;
    snprintf(uart_data->dev_f, sizeof(uart_data->dev_f), "%s", dev);

    hd->data = uart_data;
    hd->if_funs = &ups_uart_if;
    snprintf(hd->name, sizeof(hd->name), "%s", dev);
    return hd;
}

/*********************************************************************************/
ups_data_t analysis_ups_type(char *t)
{
    char *type[UPS_DATA_MAX] = {"num", "bits"};
    for (ups_data_t i = 0; i < UPS_DATA_MAX; i++)
    {
        if (strcmp(t, type[i]) == 0)
        {
            return i;
        }
    }
    return UPS_DATA_MAX;
}

int ups_connect(ups_hd_t *hd)
{
    return hd->if_funs->connect(hd);
}

int ups_dis_connect(ups_hd_t *hd)
{
    return hd->if_funs->dis_connect(hd);
}
int ups_free(ups_hd_t *hd)
{
    return hd->if_funs->free(hd);
}

int ups_set_time(ups_hd_t *hd, uint32_t timeout_ms, uint32_t interval)
{
    hd->read_time_out = timeout_ms;
    hd->interval = interval;
    return 0;
}

int ups_set_name(ups_hd_t *hd, char *new_name)
{
    return snprintf(hd->name, sizeof(hd->name), "%s", new_name);
}

int ups_read(ups_hd_t *hd, const char *cmd, void *buff, int buff_len)
{
    char tmp[64] = {0};
    char send_cmd[32] = {0};
    int cmd_len = snprintf(send_cmd, sizeof(send_cmd), "%s\n", cmd);
    snprintf(tmp, sizeof(tmp), "[%s]->Tx(%d):", hd->name, cmd_len);
    proto_syslog_hex(LOG_DEBUG, send_cmd, cmd_len, "%s", tmp);
    proto_syslog(LOG_DEBUG, "[%s]->Tx ascii:%s", hd->name, send_cmd);

    hd->if_funs->flush(hd);
    if (hd->if_funs->tx(hd, cmd, strlen(cmd)) < 0)
    {
        proto_syslog(LOG_ERR, "channel[%s], ups send data error!", hd->name);
        return -1;
    }


    int rx_len = hd->if_funs->rx(hd, buff, buff_len);
    if (rx_len >= 0)
    {
        ((uint8_t *)buff)[rx_len] = '\0';
        snprintf(tmp, sizeof(tmp), "[%s]->Rx(%d):", hd->name, rx_len);
        proto_syslog_hex(LOG_DEBUG, buff, rx_len, "%s", tmp);
        proto_syslog(LOG_DEBUG, "[%s]->Rx ascii:%s", hd->name, (char*)buff);

        if (((uint8_t *)buff)[rx_len] != '\n')
        {
            proto_syslog(LOG_ERR, "[%s] the received data has an error", hd->name);
            rx_len = -3;
        }
    }

    return rx_len;
}


int ups_write(ups_hd_t *hd, const char *cmd)
{
    char tmp[64] = {0};
    char temp_buff[64] = {0};
    int cmd_len = snprintf(temp_buff, sizeof(temp_buff), "%s\n", cmd);
    snprintf(tmp, sizeof(tmp), "[%s]->Tx(%d):", hd->name, cmd_len);
    proto_syslog_hex(LOG_DEBUG, temp_buff, cmd_len, "%s", tmp);
    proto_syslog(LOG_DEBUG, "[%s]->Tx ascii:%s", hd->name, temp_buff);

    hd->if_funs->flush(hd);
    if (hd->if_funs->tx(hd, cmd, strlen(cmd)) < 0)
    {
        proto_syslog(LOG_ERR, "channel[%s], ups send data error!", hd->name);
        return -1;
    }


    int rx_len = hd->if_funs->rx(hd, temp_buff, sizeof(temp_buff));
    if (rx_len >= 0)
    {
        ((uint8_t *)temp_buff)[rx_len] = '\0';
        snprintf(tmp, sizeof(tmp), "[%s]->Rx(%d):", hd->name, rx_len);
        proto_syslog_hex(LOG_DEBUG, temp_buff, rx_len, "%s", tmp);
        proto_syslog(LOG_DEBUG, "[%s]->Rx ascii:%s", hd->name, temp_buff);
        return (strstr(temp_buff, "ACK") != NULL) ? 0 : -2;
    }
    else
    {
        return -3;
    }
}