#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <resolv.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>
#include <netinet/in.h>

#include "dy_socket_session.h"
#include "lnxall_list.h"
#include "dy_common.h"

#define BUF_SZ 4096

extern void *socket_client_loop(void *param);

static int add_pkt_data(socket_session_t *session, uint8_t *buf, int len)
{
    socket_data_node_t *socket_data = NULL;

    socket_data = malloc(sizeof(socket_data_node_t) + len);
    if (socket_data == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed");
        return -1;
    }
    socket_data->buf_len = len;
    memcpy(socket_data->buf, buf, len);

    // 将数据加入LIST
    pthread_mutex_lock(&session->sock_lock);
    list_add_tail(&socket_data->list, &session->rcv_list);
    session->op_code |= SOCKET_DO_RCV_MASK;
    pthread_mutex_unlock(&session->sock_lock);

    pthread_cond_signal(&session->sock_cond);
    return 0;
}

/*
handle_message - 处理每个 socket 上的消息收发
*/
static int handle_message(socket_session_t *session, int socket)
{
    uint8_t buf[BUF_SZ + 1];
    int len;

    /* 开始处理每个新连接上的数据收发 */
    bzero(buf, BUF_SZ + 1);

    /* 接收客户端的消息 */
    while (1)
    {
        if (session->type == SOCKET_TCP)
            len = recv(socket, buf, BUF_SZ, 0);
        else
            len = recvfrom(socket, buf, BUF_SZ, 0, (struct sockaddr *)&session->server_addr, &session->socklen);
        if (len > 0)
        {
            add_pkt_data(session, buf, len);
            if (len < MAXBUF)
            {
                break;
            }
        }
        else
        {
            if (len < 0)
            {
                dy_syslog(LOG_ERR, "receive failed, socketfd:%d %d:'%s'", socket, errno, strerror(errno));
                if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINTR)) //SOCKET 错误
                {
                    dy_syslog(LOG_ERR, "SOCKET connection error");
                    return -2;
                }
            }
            else
            {
                dy_syslog(LOG_ERR, "%d disconnect!", socket);
                return -2;
            }
            return -1;
        }
    }
    /* 处理每个新连接上的数据收发结束 */
    return len;
}

static int start_socket_client_loop(socket_session_t *session)
{
    struct hostent *host;
    int port = session->socket_server_port;
    struct sockaddr_in *server_addr = &session->server_addr;
    int sockfd = -1;
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd = -1;

    if ((host = gethostbyname(session->socket_server_addr)) == NULL)
    {
        dy_syslog(LOG_ERR, "Get host name error\n");
        return -1;
    }

    if (session->type == SOCKET_TCP && (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        dy_syslog(LOG_ERR, "Socket TCP Error:%s\a\n", strerror(errno));
        goto out;
    }

    if (session->type == SOCKET_UDP && (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        dy_syslog(LOG_ERR, "Socket UDP Error:%s\a\n", strerror(errno));
        goto out;
    }

    session->sockfd = sockfd;
    session->socklen = sizeof(session->server_addr);
    dy_syslog(LOG_DEBUG, " %s:%d session->type %d sockfd %d", session->socket_server_addr, session->socket_server_port, session->type, sockfd);

    bzero(server_addr, sizeof(struct sockaddr_in));
    server_addr->sin_family = AF_INET;
    server_addr->sin_port = htons(port);
    bcopy((char *)host->h_addr, (char *)&server_addr->sin_addr.s_addr, host->h_length);

    if (session->type == SOCKET_TCP && connect(sockfd, (struct sockaddr *)(server_addr), sizeof(struct sockaddr)) == -1)
    {
        dy_syslog(LOG_ERR, "Connect %s:%d error:%s\n", session->socket_server_addr, session->socket_server_port, strerror(errno));
        goto out;
    }
    session->state = SOCKET_CONNECTED;
    session->connect_state_change(session, SOCKET_CONNECTED);
    while (1)
    {
        FD_ZERO(&rfds);
        maxfd = 0;
        FD_SET(sockfd, &rfds);
        if (sockfd > maxfd)
        {
            maxfd = sockfd;
        }

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1)
        {
            dy_syslog(LOG_ERR, "select is error! %s", strerror(errno));
            if (errno == EINTR)
            {
                continue;
            }
            else
            {
                break;
            }
            break;
        }
        else if (retval > 0)
        {
            if (FD_ISSET(sockfd, &rfds))
            {
                int ret;

                ret = handle_message(session, sockfd);
                if (ret == -2)
                {
                    break;
                }
            }
        }
    }
out:
    if (sockfd != -1)
    {
        close(sockfd);
    }
    session->sockfd = -1;
    session->connect_state_change(session->user_data, SOCKET_DISCONNECTED);
    session->state = SOCKET_DISCONNECTED;
    return -1;
}

int dy_socket_session_snd(socket_session_t *session, char *buf, int len)
{
    socket_data_node_t *socket_data = NULL;

    if (session->send_cnt > 100)
    {
        return -1;
    }

    socket_data = malloc(sizeof(socket_data_node_t) + len);
    if (socket_data == NULL)
    {
        dy_syslog(LOG_ERR, "malloc failed");
        return -1;
    }
    socket_data->buf_len = len;
    memcpy(socket_data->buf, buf, len);

    // 将数据加入LIST
    pthread_mutex_lock(&session->sock_lock);
    list_add_tail(&socket_data->list, &session->snd_list);
    session->op_code |= SOCKET_DO_SND_MASK;
    session->send_cnt++;
    pthread_mutex_unlock(&session->sock_lock);

    pthread_cond_signal(&session->sock_cond);

    return 0;
}

static void *start_socket_client(void *param)
{
    socket_session_t *session = (socket_session_t *)param;
    while (1)
    {
        start_socket_client_loop(session);
        sleep(1);
    }
    return NULL;
}

socket_session_t *dy_socket_session_init(socket_type_e type, char *addr, int port,
                                         int (*handle_recv_msg)(void *obj, char *buf, int len),
                                         int (*connect_state_change)(void *obj, socket_state_e state),
                                         void *user_data)
{
    pthread_t thread_sock;
    pthread_t thread_loop;
    socket_session_t *session = calloc(1, sizeof(socket_session_t));

    if (session == NULL)
    {
        dy_syslog(LOG_ERR, "malloc error");
        return NULL;
    }

    session->user_data = user_data;
    session->type = type;
    strcpy(session->socket_server_addr, addr);
    session->socket_server_port = port;
    session->handle_recv_msg = handle_recv_msg;
    session->connect_state_change = connect_state_change;

    INIT_LIST_HEAD(&session->rcv_list);
    INIT_LIST_HEAD(&session->snd_list);
    pthread_mutex_init(&session->sock_lock, NULL);
    lnxall_condvar_init(&session->sock_cond);

    pthread_create(&thread_loop, 0, socket_client_loop, (void *)session);
    pthread_create(&thread_sock, 0, start_socket_client, (void *)session);

    return session;
}

socket_state_e dy_socket_session_get_state(socket_session_t *session)
{
    return session->state;
}

int dy_socket_creat_conn(socket_type_e type, char *addr, int port)
{
    struct hostent *host;
    struct sockaddr_in _server_addr;
    struct sockaddr_in *server_addr = &_server_addr;
    int sockfd = -1;

    if ((host = gethostbyname(addr)) == NULL)
    {
        dy_syslog(LOG_ERR, "Get host name error\n");
        return -1;
    }

    if (type == SOCKET_TCP && (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        dy_syslog(LOG_ERR, "Socket TCP Error:%s\a\n", strerror(errno));
        goto out;
    }

    if (type == SOCKET_UDP && (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        dy_syslog(LOG_ERR, "Socket UDP Error:%s\a\n", strerror(errno));
        goto out;
    }

    dy_syslog(LOG_DEBUG, " %s:%d session->type %d sockfd %d", addr, port, type, sockfd);

    bzero(server_addr, sizeof(struct sockaddr_in));
    server_addr->sin_family = AF_INET;
    server_addr->sin_port = htons(port);
    if (type == SOCKET_TCP)
        server_addr->sin_addr = *((struct in_addr *)host->h_addr);
    else
        server_addr->sin_addr.s_addr = inet_addr(addr);

    if (type == SOCKET_TCP && connect(sockfd, (struct sockaddr *)(server_addr), sizeof(struct sockaddr)) == -1)
    {
        dy_syslog(LOG_ERR, "Connect %s:%d error:%s\n", addr, port, strerror(errno));
        goto out;
    }

    return sockfd;

out:
    if (sockfd != -1)
    {
        close(sockfd);
    }

    return -1;
}
