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

#include "comm_udp.h"
#include "myLog.h"

static int udp_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_udp *p_udp = (stru_comm_udp *)p_this;
    p_udp->state_cb = cb;

    return 0;
}

static int udp_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_udp *p_udp = (stru_comm_udp *)p_this;
    p_udp->recv_cb = cb;

    return 0;
}

static int udp_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_udp *p_udp = (stru_comm_udp *)p_this;
    p_udp->debug_show_cb = cb;

    return 0;
}

static void udp_run(int id, const char *udp_type, int sockfd, stru_comm_udp *p_udp)
{
    if(NULL == p_udp)
    {
        LOG_E("p_udp is null");
        return;
    }

    uint8_t buffer[2048] = {0};
    struct sockaddr_in src_addr;
    socklen_t addrlen = sizeof(src_addr);

    while (1)
    {
        int len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&src_addr, &addrlen);
        if(len > 0)
        {
            if(p_udp->recv_cb)
            {
                p_udp->recv_cb(id, sockfd, buffer, len);
            }
            else
            {
                LOG_E("recv_cb is null");
            }

            if(p_udp->debug_show_cb && p_udp->debug_show == CommDebugShow::on)
            {
                std::string str = "udp" + std::string(udp_type) + "id: " + std::to_string(id) + ", " + "fd: " + std::to_string(sockfd) + ",";
                p_udp->debug_show_cb(str.c_str(), CommDir::dir_recv, buffer, len);
            }
        }
        else
        {
            LOG_E("recvfrom failed, errno:%d", errno);
            continue;
        }
    }
}

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

    stru_comm_udp *p_udp = (stru_comm_udp *)p_this;

    if(p_udp->sockfd >= 0)
    {
        LOG_E("udp client already connected, sockfd:%d", p_udp->sockfd);
        return p_udp->sockfd;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        LOG_E("create udp socket failed, errno:%d", errno);
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;

    addr.sin_port = htons(p_udp->p_para->remote_port);

    if(p_udp->p_para->remote_ip.empty())
    {
        LOG_E("remote_ip is empty");
        close(sockfd);
        return -1;
    }
    if(inet_pton(AF_INET, p_udp->p_para->remote_ip.c_str(), &addr.sin_addr) < 0)
    {
        LOG_E("inet_pton failed for ip:%s", p_udp->p_para->remote_ip.c_str());
        close(sockfd);
        return -1;
    }

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

    p_udp->sockfd = sockfd;

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

    udp_run(id, "client", sockfd, p_udp);

    return sockfd;
}

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

    stru_comm_udp *p_udp = (stru_comm_udp *)p_this;

    if(p_udp->sockfd >= 0)
    {
        LOG_E("udp server already connected, sockfd:%d", p_udp->sockfd);
        return p_udp->sockfd;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        LOG_E("create udp socket failed, errno:%d", errno);
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;

    addr.sin_port = htons(p_udp->p_para->local_port);

    if(p_udp->p_para->local_ip.empty() || p_udp->p_para->local_ip.compare("0.0.0.0") == 0)
    {
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        if(inet_pton(AF_INET, p_udp->p_para->local_ip.c_str(), &addr.sin_addr) < 0)
        {
            LOG_E("inet_pton failed for ip:%s", p_udp->p_para->local_ip.c_str());
            close(sockfd);
            return -1;
        }
    }

    if(bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        LOG_E("bind udp socket failed, errno:%d", errno);
        close(sockfd);
        return -1;
    }

    LOG_I("udp server bind %s:%d success", p_udp->p_para->local_ip.c_str(), p_udp->p_para->local_port);

    p_udp->sockfd = sockfd;

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

    udp_run(id, "server", sockfd, p_udp);

    return sockfd;
}

static int udp_send(int id, int fd, const char *udp_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_udp *p_udp = (stru_comm_udp *)p_this;

    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(p_udp->p_para->remote_port);
    if(inet_pton(AF_INET, p_udp->p_para->remote_ip.c_str(), &dest_addr.sin_addr) < 0)
    {
        LOG_E("inet_pton failed for ip:%s", p_udp->p_para->remote_ip.c_str());
        return -1;
    }

    int sent_len = sendto(fd, data, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if(sent_len < 0)
    {
        LOG_E("sendto failed, errno:%d", errno);
        return -1;
    }

    if(p_udp->debug_show_cb && p_udp->debug_show == CommDebugShow::on)
    {
        std::string str = "udp" + std::string(udp_type) + "id: " + std::to_string(id) + ", " + "fd: " + std::to_string(fd) + ",";
        p_udp->debug_show_cb(str.c_str(), CommDir::dir_send, data, len);
    }

    return 0;
}

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

    stru_comm_udp *p_udp = (stru_comm_udp *)p_this;

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

    close(p_udp->sockfd);
    p_udp->sockfd = -1;

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

    return 0;
}

int comm_udp_create(stru_comm_udp *p_udp)
{
    if(NULL == p_udp)
    {
        LOG_E("p_udp is null");
        return -1;
    }

    p_udp->udp_state_register = udp_state_register;
    p_udp->udp_recv_register = udp_recv_register;
    p_udp->udp_debug_show_register = udp_debug_show_register;

    p_udp->udp_client_connect = udp_client_connect;
    p_udp->udp_server_connect = udp_server_connect;
    p_udp->udp_send = udp_send;

    p_udp->udp_close = udp_close;

    p_udp->sockfd = -1;

    return 0;
}