#ifndef HV_SOCKET_H_
#define HV_SOCKET_H_

#include "hplatform.h"
#include "hdef.h"

#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#endif

#define LOCALHOST   "127.0.0.1"
#define ANYADDR     "0.0.0.0"

BEGIN_EXTERN_C

static inline int socket_errno() {
#ifdef OS_WIN
    return WSAGetLastError();
#else
    return errno;
#endif
}
const char* socket_strerror(int err);

#ifdef OS_WIN
typedef int socklen_t;
static inline int blocking(int sockfd) {
    unsigned long nb = 0;
    return ioctlsocket(sockfd, FIONBIO, &nb);
}
static inline int nonblocking(int sockfd) {
    unsigned long nb = 1;
    return ioctlsocket(sockfd, FIONBIO, &nb);
}
#define poll        WSAPoll
#undef  EAGAIN
#define EAGAIN      WSAEWOULDBLOCK
#undef  EINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#undef  ENOTSOCK
#define ENOTSOCK    WSAENOTSOCK
#else
#define blocking(s)     fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
#define nonblocking(s)  fcntl(s, F_SETFL, fcntl(s, F_GETFL) |  O_NONBLOCK)
typedef int         SOCKET;
#define INVALID_SOCKET  -1
#define closesocket close
#endif

typedef union {
    struct sockaddr     sa;
    struct sockaddr_in  sin;
    struct sockaddr_in6 sin6;
} sockaddr_un;

// @param host: domain or ip
// @retval 0:succeed
int Resolver(const char* host, sockaddr_un* addr);

static inline socklen_t sockaddrlen(sockaddr_un* addr) {
    if (addr->sa.sa_family == AF_INET) {
        return sizeof(struct sockaddr_in);
    }
    else if (addr->sa.sa_family == AF_INET6) {
        return sizeof(struct sockaddr_in6);
    }
    return sizeof(sockaddr_un);
}

static inline const char* sockaddr_ip(sockaddr_un* addr, char *ip, int len) {
    if (addr->sa.sa_family == AF_INET) {
        return inet_ntop(AF_INET, &addr->sin.sin_addr, ip, len);
    }
    else if (addr->sa.sa_family == AF_INET6) {
        return inet_ntop(AF_INET6, &addr->sin6.sin6_addr, ip, len);
    }
    return ip;
}

static inline uint16_t sockaddr_port(sockaddr_un* addr) {
    uint16_t port = 0;
    if (addr->sa.sa_family == AF_INET) {
        port = htons(addr->sin.sin_port);
    }
    else if (addr->sa.sa_family == AF_INET6) {
        port = htons(addr->sin6.sin6_port);
    }
    return port;
}

static inline void sockaddr_set_port(sockaddr_un* addr, int port) {
    if (addr->sa.sa_family == AF_INET) {
        addr->sin.sin_port = ntohs(port);
    }
    else if (addr->sa.sa_family == AF_INET6) {
        addr->sin6.sin6_port = ntohs(port);
    }
}

//#define INET_ADDRSTRLEN   16
//#define INET6_ADDRSTRLEN  46
#define SOCKADDR_STRLEN     64 // ipv4:port | [ipv6]:port
#define SOCKADDR_STR(addr, buf) sockaddr_str((sockaddr_un*)addr, buf, sizeof(buf))
// NOTE: typeof(addr)=[sockaddr*, sockaddr_in*, sockaddr_in6*, sockaddr_un*]
// char buf[SOCKADDR_STRLEN] = {0};
// SOCKADDR_STR(addr, buf);

static inline const char* sockaddr_str(sockaddr_un* addr, char* buf, int len) {
    char ip[SOCKADDR_STRLEN] = {0};
    uint16_t port = 0;
    if (addr->sa.sa_family == AF_INET) {
        inet_ntop(AF_INET, &addr->sin.sin_addr, ip, len);
        port = htons(addr->sin.sin_port);
        snprintf(buf, len, "%s:%d", ip, port);
    }
    else if (addr->sa.sa_family == AF_INET6) {
        inet_ntop(AF_INET6, &addr->sin6.sin6_addr, ip, len);
        port = htons(addr->sin6.sin6_port);
        snprintf(buf, len, "[%s]:%d", ip, port);
    }
    return buf;
}

static inline void sockaddr_print(sockaddr_un* addr) {
    char buf[SOCKADDR_STRLEN] = {0};
    sockaddr_str(addr, buf, sizeof(buf));
    puts(buf);
}

static inline int sockaddr_assign(sockaddr_un* addr, const char* host, int port) {
    if (host) {
        int ret = Resolver(host, addr);
        if (ret != 0) return ret;
    }
    else {
        addr->sin.sin_family = AF_INET;
        addr->sin.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    sockaddr_set_port(addr, port);
    return 0;
}

// socket -> setsockopt -> bind
// @param type: SOCK_STREAM(tcp) SOCK_DGRAM(udp)
// @return sockfd
int Bind(int port, const char* host DEFAULT(ANYADDR), int type DEFAULT(SOCK_STREAM));

// Bind -> listen
// @return sockfd
int Listen(int port, const char* host DEFAULT(ANYADDR));

// @return sockfd
// Resolver -> socket -> nonblocking -> connect
int Connect(const char* host, int port, int nonblock DEFAULT(0));
// Connect(host, port, 1)
int ConnectNonblock(const char* host, int port);
// Connect(host, port, 1) -> select -> blocking
#define DEFAULT_CONNECT_TIMEOUT 5000 // ms
int ConnectTimeout(const char* host, int port, int ms DEFAULT(DEFAULT_CONNECT_TIMEOUT));

// Just implement Socketpair(AF_INET, SOCK_STREAM, 0, sv);
int Socketpair(int family, int type, int protocol, int sv[2]);

static inline int tcp_nodelay(int sockfd, int on DEFAULT(1)) {
    return setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(int));
}

static inline int tcp_nopush(int sockfd, int on DEFAULT(1)) {
#ifdef TCP_NOPUSH
    return setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, (const char*)&on, sizeof(int));
#elif defined(TCP_CORK)
    return setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, (const char*)&on, sizeof(int));
#else
    return -10;
#endif
}

static inline int tcp_keepalive(int sockfd, int on DEFAULT(1), int delay DEFAULT(60)) {
    if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(int)) != 0) {
        return socket_errno();
    }

#ifdef TCP_KEEPALIVE
    return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (const char*)&delay, sizeof(int));
#elif defined(TCP_KEEPIDLE)
    // TCP_KEEPIDLE     => tcp_keepalive_time
    // TCP_KEEPCNT      => tcp_keepalive_probes
    // TCP_KEEPINTVL    => tcp_keepalive_intvl
    return setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (const char*)&delay, sizeof(int));
#else
    return 0;
#endif
}

static inline int udp_broadcast(int sockfd, int on DEFAULT(1)) {
    return setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(int));
}

// send timeout
static inline int so_sndtimeo(int sockfd, int timeout) {
#ifdef OS_WIN
    return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(int));
#else
    struct timeval tv = {timeout/1000, (timeout%1000)*1000};
    return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
#endif
}

// recv timeout
static inline int so_rcvtimeo(int sockfd, int timeout) {
#ifdef OS_WIN
    return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(int));
#else
    struct timeval tv = {timeout/1000, (timeout%1000)*1000};
    return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif
}

END_EXTERN_C

#endif // HV_SOCKET_H_
