#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


// #include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
// #include <sys/select.h>


#include "comm_tcp.h"
#include "myLog.h"


static void tcp_sleep(int ticks)
{
    struct timespec rqtp;
    struct timespec rmtp;

    rqtp.tv_sec = ticks / 1000;
    rqtp.tv_nsec = (ticks % 1000) * 1000000L;
    
    while (nanosleep(&rqtp, &rmtp) == -1 && errno == EINTR)
    {
        rqtp = rmtp;
    }
}


static int tcp_state_register(void *p_this, comm_state_cb cb)
{
    if(NULL == p_this || NULL == cb)
    {
        LOG_E("p_this or cb is null");
        return -1;
    }
    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;
    p_tcp->state_cb = cb;

    return 0;
}

static int tcp_debug_show_register(void *p_this, comm_debug_show_cb cb)
{
    if(NULL == p_this || NULL == cb)
    {
        LOG_E("p_this or cb is null");
        return -1;
    }
    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;
    p_tcp->debug_show_cb = cb;

    return 0;
}

static int tcp_recv_register(void *p_this, comm_recv_cb cb)
{
    if(NULL == p_this || NULL == cb)
    {   
        LOG_E("p_this or cb is null");
        return -1;
    }
    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;
    p_tcp->recv_cb = cb;

    return 0;
}

static int tcp_open(int sockfd)
{
    if(sockfd < 0)
    {
        LOG_E("sockfd is invalid"); 
        return -1;
    }

    int optval = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
    {
        LOG_E("setsockopt failed");
        return -1;
    }

    int keepalive = 1;
    int keepidle = 30;
    int keepintvl = 5;
    int keepcnt = 3;

    if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) < 0)
    {
        LOG_E("setsockopt failed");
    }

    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) < 0)
    {
        LOG_E("setsockopt failed");
    }

    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) < 0)
    {
        LOG_E("setsockopt failed");
    }

    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) < 0)
    {
        LOG_E("setsockopt failed");
    }

    return 0;
}


static int tcp_connect(stru_comm_tcp *p_tcp)
{
    if(NULL == p_tcp)
    {
        LOG_E("p_tcp is null");
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        LOG_E("socket failed");
        return -1;
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(p_tcp->p_para->remote_port);
    serv_addr.sin_addr.s_addr = inet_addr(p_tcp->p_para->remote_ip.c_str());
    if(serv_addr.sin_addr.s_addr == INADDR_NONE)
    {
        LOG_E("inet_addr failed");
        close(sockfd);
        return -1;
    }

    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

    int ret = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret < 0 && errno != EINPROGRESS)
    {
        LOG_E("connect %s:%d failed, errno:%d", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port, errno);
        close(sockfd);
        return -1;
    }

    fd_set rset, wset;
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(sockfd, &rset);
    FD_SET(sockfd, &wset);

    struct timeval tmval = {3, 0};

    ret = select(sockfd + 1, &rset, &wset, NULL, &tmval);
    if(ret < 0)
    {
        LOG_E("connect time out %s,%d!", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port);
        close(sockfd);
        return -1;
    }

    int err = 0;
    socklen_t len = sizeof(err);
    getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len);
    if(err != 0)
    {
        LOG_E("connect %s:%d failed, err:%d", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port, err);
        close(sockfd);
        return -1;
    }

    fcntl(sockfd, F_SETFL, flags);

    if(tcp_open(sockfd) < 0)
    {
        LOG_E("tcp_open %s:%d failed", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port);
        close(sockfd);
        return -1;
    }

    LOG_I("connect %s:%d success", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port);

    return sockfd;
}


static int tcp_client_connect(int id, void *p_this)
{
    if(NULL == p_this)
    {
        LOG_E("p_this is null");
        return -1;
    }

    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;

    while (1)
    {
        if(p_tcp->sockfd < 0)
        {
            if(p_tcp->p_para->remote_ip.empty())
            {
                LOG_E("remote_ip is empty");
                tcp_sleep(5000);
                continue;
            }

            int sockfd = tcp_connect(p_tcp);
            if(sockfd < 0)
            {
                LOG_E("tcp_connect failed, wait 5s reconncet");
                tcp_sleep(5000);
                continue;
            }

            if(p_tcp->sockfd >= 0)
            {
                close(p_tcp->sockfd);

                if(p_tcp->state_cb)
                {
                    p_tcp->state_cb(id, p_tcp->sockfd, CommState::disconnected);
                }
                p_tcp->sockfd = -1;
            }

            p_tcp->sockfd = sockfd;

            if(p_tcp->state_cb)
            {
                p_tcp->state_cb(id, sockfd, CommState::connected);
            }

            LOG_I("tcp_client_connect %s:%d success, sockfd:%d", p_tcp->p_para->remote_ip.c_str(), p_tcp->p_para->remote_port, sockfd);
        }

        fd_set recvfdset;
        struct timeval tmval = {0, 100000};

        FD_ZERO(&recvfdset);
        FD_SET(p_tcp->sockfd, &recvfdset);

        int ret = select(p_tcp->sockfd + 1, &recvfdset, NULL, NULL, &tmval);
        if(ret > 0 && FD_ISSET(p_tcp->sockfd, &recvfdset))
        {
            uint8_t buffer[1024] = {0};
            int len = recv(p_tcp->sockfd, buffer, sizeof(buffer), 0);
            if(len > 0)
            {
                if(p_tcp->recv_cb)
                {
                    p_tcp->recv_cb(id, p_tcp->sockfd, buffer, len);
                }
                else
                {
                    LOG_E("recv_cb is null");
                }

                if(p_tcp->debug_show_cb && p_tcp->debug_show == CommDebugShow::on)
                {
                    std::string str = "tcp_client_id: " + std::to_string(id) + ", " + "fd: " + std::to_string(p_tcp->sockfd) + ",";
                    p_tcp->debug_show_cb(str.c_str(), CommDir::dir_recv, buffer, len);
                }
            }
            else
            {
                LOG_I("tcp_client_connect recv len:%d, errno:%d", len, errno);

                close(p_tcp->sockfd);
                
                if(p_tcp->state_cb)
                {
                    p_tcp->state_cb(id, p_tcp->sockfd, CommState::disconnected);
                }

                p_tcp->sockfd = -1;
            }
        }
        else if(ret < 0 && errno != EINTR)
        {
            LOG_E("select failed, errno:%d", errno);
        }
    }
    

    return 0;
}


static int tcp_listen(stru_comm_tcp *p_tcp)
{
    if(NULL == p_tcp)
    {
        LOG_E("p_tcp is null");
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        LOG_E("socket failed");
        return -1;
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(p_tcp->p_para->local_port);

    if(p_tcp->p_para->local_ip.empty() || p_tcp->p_para->local_ip.compare("0.0.0.0") == 0)
    {
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        serv_addr.sin_addr.s_addr = inet_addr(p_tcp->p_para->local_ip.c_str());
        if(serv_addr.sin_addr.s_addr == INADDR_NONE)
        {
            LOG_E("inet_addr %s failed", p_tcp->p_para->local_ip.c_str());
            close(sockfd);
            return -1;
        }
    }

    if(bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
    {
        LOG_E("bind %s:%d failed, errno:%d", p_tcp->p_para->local_ip.c_str(), p_tcp->p_para->local_port, errno);
        close(sockfd);
        return -1;
    }

    if(tcp_open(sockfd) < 0)
    {
        LOG_E("tcp_open %s:%d failed", p_tcp->p_para->local_ip.c_str(), p_tcp->p_para->local_port);
        close(sockfd);
        return -1;
    }

    if(listen(sockfd, TCP_MAX_CONN_NUM) < 0)
    {
        LOG_E("listen %s:%d failed, errno:%d", p_tcp->p_para->local_ip.c_str(), p_tcp->p_para->local_port, errno);
        close(sockfd);
        return -1;
    }

    p_tcp->sock_listen_fd = sockfd;

    return sockfd;
}


static int tcp_server_connect(int id, void *p_this)
{
    if(NULL == p_this)
    {
        LOG_E("p_this is null");
        return -1;
    }

    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;

    int listen_fd = tcp_listen(p_tcp);
    if(listen_fd < 0)
    {
        LOG_E("tcp_listen %s:%d failed", p_tcp->p_para->local_ip.c_str(), p_tcp->p_para->local_port);
        return -1;
    }

    p_tcp->sockfd = listen_fd;

    fd_set readfdset;
    int max_sd, sd, activity;
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);

    while (1)
    {
        FD_ZERO(&readfdset);

        if(p_tcp->sockfd < 0)
        {
            LOG_E("sockfd is invalid");
            break;
        }

        FD_SET(p_tcp->sockfd, &readfdset);

        max_sd = p_tcp->sockfd;

        for(int i = 0; i < TCP_MAX_CONN_NUM; i++)
        {
            sd = p_tcp->client_fd[i];
            if(sd > 0)
            {
                FD_SET(sd, &readfdset);
            }

            if(sd > max_sd)
            {
                max_sd = sd;
            }
        }

        activity = select(max_sd + 1, &readfdset, NULL, NULL, NULL);
        if(activity < 0 && errno != EINTR)
        {
            LOG_E("select failed, errno:%d", errno);
            continue;
        }

        if(FD_ISSET(p_tcp->sockfd, &readfdset))
        {
            int new_sd = accept(p_tcp->sockfd, (struct sockaddr*)&client_addr, &addrlen);
            if(new_sd < 0)
            {
                LOG_E("accept failed, errno:%d", errno);
                continue;
            }

            tcp_open(new_sd);
            
            LOG_I("new connection from %s:%d, sockfd:%d", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_sd);

            int free_idx = -1;
            for(int i = 0; i < TCP_MAX_CONN_NUM; i++)
            {
                if(p_tcp->client_fd[i] < 0)
                {
                    free_idx = i;
                    break;
                }
            }

            if(free_idx < 0)
            {
                LOG_E("no free client_fd slot");
                close(new_sd);
                continue;
            }

            p_tcp->client_fd[free_idx] = new_sd;

            if(p_tcp->state_cb)
            {
                p_tcp->state_cb(id, new_sd, CommState::connected);
            }
        }
        
        for(int i = 0; i < TCP_MAX_CONN_NUM; i++)
        {
            sd = p_tcp->client_fd[i];
            if(sd > 0 && FD_ISSET(sd, &readfdset))
            {
                uint8_t buffer[1024] = {0};
                int len = recv(sd, buffer, sizeof(buffer), 0);
                if(len > 0)
                {
                    if(p_tcp->recv_cb)
                    {
                        p_tcp->recv_cb(id, sd, buffer, len);
                    }
                    else
                    {
                        LOG_E("recv_cb is null");
                    }

                    if(p_tcp->debug_show_cb && p_tcp->debug_show == CommDebugShow::on)
                    {
                        std::string str = "tcp_server_id: " + std::to_string(id) + ", " + "fd: " + std::to_string(sd) + ",";
                        p_tcp->debug_show_cb(str.c_str(), CommDir::dir_recv, buffer, len);
                    }
                }
                else
                {
                    getpeername(sd, (struct sockaddr*)&client_addr, &addrlen);

                    LOG_E("client %s:%d disconnected, socket id %d", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), sd);
                    if(len < 0)
                    {
                        LOG_E("recv failed, errno:%d", errno);
                    }

                    close(sd);
                    p_tcp->client_fd[i] = -1;

                    if(p_tcp->state_cb)
                    {
                        p_tcp->state_cb(id, sd, CommState::disconnected);
                    }
                }
            }
                   
        }
    }

    return 0;
}


static int tcp_close(int id, void *p_this)
{
    if(NULL == p_this)
    {
        LOG_E("p_this is null");
        return -1;
    }

    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;

    if(p_tcp->sockfd < 0)
    {
        LOG_E("sockfd is already closed %d", p_tcp->sockfd);
        return 0;
    }

    shutdown(p_tcp->sockfd, SHUT_RDWR);
    close(p_tcp->sockfd);

    if(p_tcp->state_cb)
    {
        p_tcp->state_cb(id, p_tcp->sockfd, CommState::disconnected);
    }
    p_tcp->sockfd = -1;

    if(p_tcp->sock_listen_fd >= 0)
    {
        for(int i = 0; i < TCP_MAX_CONN_NUM; i++)
        {
            if(p_tcp->client_fd[i] >= 0)
            {
                close(p_tcp->client_fd[i]);
                if(p_tcp->state_cb)
                {
                    p_tcp->state_cb(id, p_tcp->client_fd[i], CommState::disconnected);
                }
                p_tcp->client_fd[i] = -1;
            }
        }

        p_tcp->sock_listen_fd = -1;
    }

    return 0;
}


static int tcp_send(int id, int fd, const char *tcp_type, void *p_this, const uint8_t *data, uint16_t len)
{
    if(NULL == p_this)
    {
        LOG_E("p_this is null");
        return -1;
    }

    if(fd < 0)
    {
        LOG_E("fd is invalid:%d", fd);
        return -1;
    }

    stru_comm_tcp *p_tcp = (stru_comm_tcp *)p_this;

    int send_len = send(fd, data, len, MSG_NOSIGNAL);
    if(send_len < 0)
    {
        LOG_E("send failed, sockfd:%d, errno:%d", fd, errno);
        if(p_tcp->sock_listen_fd >= 0 && fd != p_tcp->sock_listen_fd)
        {
            for(int i = 0; i < TCP_MAX_CONN_NUM; i++)
            {
                if(p_tcp->client_fd[i] == fd)
                {
                    close(fd);
                    p_tcp->client_fd[i] = -1;

                    if(p_tcp->state_cb)
                    {
                        p_tcp->state_cb(id, fd, CommState::disconnected);
                    }
                    break;
                }
            }
        }
        else if(p_tcp->sockfd == fd)
        {
            close(fd);
            p_tcp->sockfd = -1;

            if(p_tcp->state_cb)
            {
                p_tcp->state_cb(id, fd, CommState::disconnected);
            }
        }

        return -1;
    }

    if(p_tcp->debug_show_cb && p_tcp->debug_show == CommDebugShow::on)
    {
        std::string str = "tcp" + std::string(tcp_type) + "id: " + std::to_string(id) + ", " + "fd: " + std::to_string(fd) + ",";
        p_tcp->debug_show_cb(str.c_str(), CommDir::dir_send, data, send_len);
    }

    return send_len;
}


int comm_tcp_create(stru_comm_tcp *p_tcp)
{
    if(NULL == p_tcp)
    {
        LOG_E("tcp is null");
        return -1;
    }

    p_tcp->sockfd = -1;
    memset(p_tcp->client_fd, -1, sizeof(p_tcp->client_fd));

    p_tcp->tcp_state_register = tcp_state_register;
    p_tcp->tcp_debug_show_register = tcp_debug_show_register;
    p_tcp->tcp_recv_register = tcp_recv_register;

    p_tcp->tcp_client_connect = tcp_client_connect;
    p_tcp->tcp_server_connect = tcp_server_connect;

    p_tcp->tcp_send = tcp_send;

    p_tcp->tcp_close = tcp_close;

    return 0;

}