/*********************************************************************************************
*
*   rnet - A simple and easy-to-use network module for raylib
*
*   FEATURES:
*       - Provides a simple and (hopefully) easy to use wrapper around the Berkeley socket API
*
*   DEPENDENCIES:
*       raylib.h    - TraceLog
*       rnet.h      - platform-specific network includes
*
*   CONTRIBUTORS:
*       Jak Barnes (github: @syphonx) (Feb. 2019) - Initial version
*
*
*   LICENSE: zlib/libpng
*
*   Copyright (c) 2019 Jak Barnes (github: @syphonx) and Ramon Santamaria (@raysan5)
*
*   This software is provided "as-is", without any express or implied warranty. In no event
*   will the authors be held liable for any damages arising from the use of this software.
*
*   Permission is granted to anyone to use this software for any purpose, including commercial
*   applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
*     1. The origin of this software must not be misrepresented; you must not claim that you
*     wrote the original software. If you use this software in a product, an acknowledgment
*     in the product documentation would be appreciated but is not required.
*
*     2. Altered source versions must be plainly marked as such, and must not be misrepresented
*     as being the original software.
*
*     3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/

//----------------------------------------------------------------------------------
// Check if config flags have been externally provided on compilation line
//----------------------------------------------------------------------------------

#include "rnet.h"

#include "raylib.h"

#include <assert.h>         // Required for: assert()
#include <stdio.h>          // Required for: FILE, fopen(), fclose(), fread()
#include <stdlib.h>         // Required for: malloc(), free()
#include <string.h>         // Required for: strcmp(), strncmp()

//----------------------------------------------------------------------------------
// Module defines
//----------------------------------------------------------------------------------

#define NET_DEBUG_ENABLED (1)

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------

typedef struct _SocketAddress
{
    struct sockaddr address;
} _SocketAddress;

typedef struct _SocketAddressIPv4
{
    struct sockaddr_in address;
} _SocketAddressIPv4;

typedef struct _SocketAddressIPv6
{
    struct sockaddr_in6 address;
} _SocketAddressIPv6;

typedef struct _SocketAddressStorage
{
    struct sockaddr_storage address;
} _SocketAddressStorage;

typedef struct _AddressInformation
{
    struct addrinfo addr;
} _AddressInformation;



//----------------------------------------------------------------------------------
// Global module forward declarations
//----------------------------------------------------------------------------------

static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol);
static const char *SocketAddressToString(struct sockaddr_storage *sockaddr);
static bool IsIPv4Address(const char *ip);
static bool IsIPv6Address(const char *ip);
static void *GetSocketPortPtr(struct sockaddr_storage *sa);
static void *GetSocketAddressPtr(struct sockaddr_storage *sa);
static bool IsSocketValid(Socket *sock);
static void SocketSetLastError(int err);
static int SocketGetLastError();
static char *SocketGetLastErrorString();
static char *SocketErrorCodeToString(int err);
static bool SocketSetDefaults(SocketConfig *config);
static bool InitSocket(Socket *sock, struct addrinfo *addr);
static bool CreateSocket(SocketConfig *config, SocketResult *outresult);
static bool SocketSetBlocking(Socket *sock);
static bool SocketSetNonBlocking(Socket *sock);
static bool SocketSetOptions(SocketConfig *config, Socket *sock);
static void SocketSetHints(SocketConfig *config, struct addrinfo *hints);

//----------------------------------------------------------------------------------
// Global module implementation
//----------------------------------------------------------------------------------

// Print socket information
static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol)
{
    switch (family)
    {
        case AF_UNSPEC: { TraceLog(LOG_DEBUG, "\tFamily: Unspecified");
        }
        break;
        case AF_INET:
        {
            TraceLog(LOG_DEBUG, "\tFamily: AF_INET (IPv4)");
            TraceLog(LOG_INFO, "\t- IPv4 address %s", SocketAddressToString(addr));
        }
        break;
        case AF_INET6:
        {
            TraceLog(LOG_DEBUG, "\tFamily: AF_INET6 (IPv6)");
            TraceLog(LOG_INFO, "\t- IPv6 address %s", SocketAddressToString(addr));
        }
        break;
        case AF_NETBIOS:
        {
            TraceLog(LOG_DEBUG, "\tFamily: AF_NETBIOS (NetBIOS)");
        }
        break;
        default: { TraceLog(LOG_DEBUG, "\tFamily: Other %ld", family);
        }
        break;
    }
    TraceLog(LOG_DEBUG, "\tSocket type:");
    switch (socktype)
    {
        case 0: TraceLog(LOG_DEBUG, "\t- Unspecified"); break;
        case SOCK_STREAM:
            TraceLog(LOG_DEBUG, "\t- SOCK_STREAM (stream)");
            break;
        case SOCK_DGRAM:
            TraceLog(LOG_DEBUG, "\t- SOCK_DGRAM (datagram)");
            break;
        case SOCK_RAW: TraceLog(LOG_DEBUG, "\t- SOCK_RAW (raw)"); break;
        case SOCK_RDM:
            TraceLog(LOG_DEBUG, "\t- SOCK_RDM (reliable message datagram)");
            break;
        case SOCK_SEQPACKET:
            TraceLog(LOG_DEBUG, "\t- SOCK_SEQPACKET (pseudo-stream packet)");
            break;
        default: TraceLog(LOG_DEBUG, "\t- Other %ld", socktype); break;
    }
    TraceLog(LOG_DEBUG, "\tProtocol:");
    switch (protocol)
    {
        case 0: TraceLog(LOG_DEBUG, "\t- Unspecified"); break;
        case IPPROTO_TCP: TraceLog(LOG_DEBUG, "\t- IPPROTO_TCP (TCP)"); break;
        case IPPROTO_UDP: TraceLog(LOG_DEBUG, "\t- IPPROTO_UDP (UDP)"); break;
        default: TraceLog(LOG_DEBUG, "\t- Other %ld", protocol); break;
    }
}

// Convert network ordered socket address to human readable string (127.0.0.1)
static const char *SocketAddressToString(struct sockaddr_storage *sockaddr)
{
    //static const char* ipv6[INET6_ADDRSTRLEN];
    assert(sockaddr != NULL);
    assert(sockaddr->ss_family == AF_INET || sockaddr->ss_family == AF_INET6);
    switch (sockaddr->ss_family)
    {
        case AF_INET:
        {
            //struct sockaddr_in *s = ((struct sockaddr_in *) sockaddr);
            //return inet_ntop(AF_INET, &s->sin_addr, ipv6, INET_ADDRSTRLEN);       // TODO.
        }
        break;
        case AF_INET6:
        {
            //struct sockaddr_in6 *s = ((struct sockaddr_in6 *) sockaddr);
            //return inet_ntop(AF_INET6, &s->sin6_addr, ipv6, INET6_ADDRSTRLEN);    // TODO.
        }
        break;
    }
    return NULL;
}

// Check if the null terminated string ip is a valid IPv4 address
static bool IsIPv4Address(const char *ip)
{
    /*
    struct sockaddr_in sa;
    int result = inet_pton(AF_INET, ip, &(sa.sin_addr));    // TODO.
    return (result != 0);
    */
    return false;
}

// Check if the null terminated string ip is a valid IPv6 address
static bool IsIPv6Address(const char *ip)
{
    /*
    struct sockaddr_in6 sa;
    int result = inet_pton(AF_INET6, ip, &(sa.sin6_addr));    // TODO.
    return result != 0;
    */
    return false;
}

// Return a pointer to the port from the correct address family (IPv4, or IPv6)
static void *GetSocketPortPtr(struct sockaddr_storage *sa)
{
    if (sa->ss_family == AF_INET)
    {
        return &(((struct sockaddr_in *) sa)->sin_port);
    }

    return &(((struct sockaddr_in6 *) sa)->sin6_port);
}

// Return a pointer to the address from the correct address family (IPv4, or IPv6)
static void *GetSocketAddressPtr(struct sockaddr_storage *sa)
{
    if (sa->ss_family == AF_INET)
    {
        return &(((struct sockaddr_in *) sa)->sin_addr);
    }

    return &(((struct sockaddr_in6 *) sa)->sin6_addr);
}

// Is the socket in a valid state?
static bool IsSocketValid(Socket *sock)
{
    if (sock != NULL)
    {
        return (sock->channel != INVALID_SOCKET);
    }
    return false;
}

// Sets the error code that can be retrieved through the WSAGetLastError function.
static void SocketSetLastError(int err)
{
#if defined(_WIN32)
    WSASetLastError(err);
#else
    errno = err;
#endif
}

// Returns the error status for the last Sockets operation that failed
static int SocketGetLastError()
{
#if defined(_WIN32)
    return WSAGetLastError();
#else
    return errno;
#endif
}

// Returns a human-readable string representing the last error message
static char *SocketGetLastErrorString()
{
    return SocketErrorCodeToString(SocketGetLastError());
}

// Returns a human-readable string representing the error message (err)
static char *SocketErrorCodeToString(int err)
{
#if defined(_WIN32)
    static char gaiStrErrorBuffer[GAI_STRERROR_BUFFER_SIZE];
    sprintf(gaiStrErrorBuffer, "%s", gai_strerror(err));
    return gaiStrErrorBuffer;
#else
    return gai_strerror(err);
#endif
}

// Set the defaults in the supplied SocketConfig if they're not already set
static bool SocketSetDefaults(SocketConfig *config)
{
    if (config->backlog_size == 0)
    {
        config->backlog_size = SOCKET_MAX_QUEUE_SIZE;
    }

    return true;
}

// Create the socket channel
static bool InitSocket(Socket *sock, struct addrinfo *addr)
{
    switch (sock->type)
    {
        case SOCKET_TCP:
            if (addr->ai_family == AF_INET)
            {
                sock->channel = socket(AF_INET, SOCK_STREAM, 0);
            }
            else
            {
                sock->channel = socket(AF_INET6, SOCK_STREAM, 0);
            }
            break;
        case SOCKET_UDP:
            if (addr->ai_family == AF_INET)
            {
                sock->channel = socket(AF_INET, SOCK_DGRAM, 0);
            }
            else
            {
                sock->channel = socket(AF_INET6, SOCK_DGRAM, 0);
            }
            break;
        default: 
            TraceLog(LOG_WARNING, "Invalid socket type specified.");
            break;
    }
    return IsSocketValid(sock);
}

//    CreateSocket() - Interally called by CreateSocket()
//
//    This here is the bread and butter of the socket API, This function will
//    attempt to open a socket, bind and listen to it based on the config passed in
//
//    SocketConfig* config - Configuration for which socket to open
//    SocketResult* result - The results of this function (if any, including errors)
//
//    e.g.
//    SocketConfig server_config = {                SocketConfig client_config = {
//        .host        = "127.0.0.1",                .host = "127.0.0.1",
//        .port        = 8080,                    .port = 8080,
//        .server      = true,                };
//        .nonblocking = true,
//    };
//    SocketResult server_res;                SocketResult client_res;
static bool CreateSocket(SocketConfig *config, SocketResult *outresult)
{
    bool             success = true;
    int              addrstatus;
    struct addrinfo  hints; // Address flags (IPV4, IPV6, UDP?)
    struct addrinfo *res;   // A pointer to the resulting address list
    outresult->socket->channel = INVALID_SOCKET;
    outresult->status          = RESULT_FAILURE;

    // Set the socket type
    outresult->socket->type = config->type;

    //    Set the hints based on information in the config
    //
    //    AI_CANONNAME    Causes the ai_canonname of the result to the filled  out with the host's canonical (real) name.
    //    AI_PASSIVE:        Causes the result's IP address to be filled out with INADDR_ANY (IPv4)or in6addr_any (IPv6);
    //    Note:    This causes a subsequent call to bind() to auto-fill the IP address
    //            of the struct sockaddr with the address of the current host.
    //
    SocketSetHints(config, &hints);

    // Populate address information
    addrstatus = getaddrinfo(config->host, // e.g. "www.example.com" or IP (Can be null if AI_PASSIVE flag is set
                             config->port, // e.g. "http" or port number
                             &hints,       // e.g. SOCK_STREAM/SOCK_DGRAM
                             &res          // The struct to populate
    );

    // Did we succeed?
    if (addrstatus != 0)
    {
        outresult->socket->status = SocketGetLastError();
        TraceLog(LOG_WARNING,
                 "Socket Error: %s",
                 SocketErrorCodeToString(outresult->socket->status));
        SocketSetLastError(0);
        TraceLog(LOG_WARNING,
                 "Failed to get resolve host %s:%s: %s",
                 config->host,
                 config->port,
                 SocketGetLastErrorString());
        return (success = false);
    }
    else
    {
        char hoststr[NI_MAXHOST];
        char portstr[NI_MAXSERV];
        //socklen_t client_len = sizeof(struct sockaddr_storage);
        //int rc = getnameinfo((struct sockaddr *) res->ai_addr, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
        TraceLog(LOG_INFO, "Successfully resolved host %s:%s", hoststr, portstr);
    }

    // Walk the address information linked-list
    struct addrinfo *it;
    for (it = res; it != NULL; it = it->ai_next)
    {
        // Initialise the socket
        if (!InitSocket(outresult->socket, it))
        {
            outresult->socket->status = SocketGetLastError();
            TraceLog(LOG_WARNING,
                     "Socket Error: %s",
                     SocketErrorCodeToString(outresult->socket->status));
            SocketSetLastError(0);
            continue;
        }

        // Set socket options
        if (!SocketSetOptions(config, outresult->socket))
        {
            outresult->socket->status = SocketGetLastError();
            TraceLog(LOG_WARNING,
                     "Socket Error: %s",
                     SocketErrorCodeToString(outresult->socket->status));
            SocketSetLastError(0);
            freeaddrinfo(res);
            return (success = false);
        }
    }

    if (!IsSocketValid(outresult->socket))
    {
        outresult->socket->status = SocketGetLastError();
        TraceLog(
            LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->status));
        SocketSetLastError(0);
        freeaddrinfo(res);
        return (success = false);
    }

    if (success)
    {
        outresult->status         = RESULT_SUCCESS;
        outresult->socket->ready  = 0;
        outresult->socket->status = 0;
        if (!(config->type == SOCKET_UDP))
        {
            outresult->socket->isServer = config->server;
        }
        switch (res->ai_addr->sa_family)
        {
            case AF_INET:
            {
                outresult->socket->addripv4 = (struct _SocketAddressIPv4 *) malloc(
                    sizeof(*outresult->socket->addripv4));
                if (outresult->socket->addripv4 != NULL)
                {
                    memset(outresult->socket->addripv4, 0,
                           sizeof(*outresult->socket->addripv4));
                    if (outresult->socket->addripv4 != NULL)
                    {
                        memcpy(&outresult->socket->addripv4->address,
                               (struct sockaddr_in *) res->ai_addr, sizeof(struct sockaddr_in));
                        outresult->socket->isIPv6 = false;
                        char      hoststr[NI_MAXHOST];
                        char      portstr[NI_MAXSERV];
                        socklen_t client_len = sizeof(struct sockaddr_storage);
                        getnameinfo(
                            (struct sockaddr *) &outresult->socket->addripv4->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
                        TraceLog(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr);
                    }
                }
            }
            break;
            case AF_INET6:
            {
                outresult->socket->addripv6 = (struct _SocketAddressIPv6 *) malloc(
                    sizeof(*outresult->socket->addripv6));
                if (outresult->socket->addripv6 != NULL)
                {
                    memset(outresult->socket->addripv6, 0,
                           sizeof(*outresult->socket->addripv6));
                    if (outresult->socket->addripv6 != NULL)
                    {
                        memcpy(&outresult->socket->addripv6->address,
                               (struct sockaddr_in6 *) res->ai_addr, sizeof(struct sockaddr_in6));
                        outresult->socket->isIPv6 = true;
                        char      hoststr[NI_MAXHOST];
                        char      portstr[NI_MAXSERV];
                        socklen_t client_len = sizeof(struct sockaddr_storage);
                        getnameinfo(
                            (struct sockaddr *) &outresult->socket->addripv6->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
                        TraceLog(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr);
                    }
                }
            }
            break;
        }
    }
    freeaddrinfo(res);
    return success;
}

// Set the state of the Socket sock to blocking
static bool SocketSetBlocking(Socket *sock)
{
    bool ret = true;
#if defined(_WIN32)
    unsigned long mode = 0;
    ret                = ioctlsocket(sock->channel, FIONBIO, &mode);
#else
    const int flags = fcntl(sock->channel, F_GETFL, 0);
    if (!(flags & O_NONBLOCK))
    {
        TraceLog(LOG_DEBUG, "Socket was already in blocking mode");
        return ret;
    }

    ret = (0 == fcntl(sock->channel, F_SETFL, (flags ^ O_NONBLOCK)));
#endif
    return ret;
}

// Set the state of the Socket sock to non-blocking
static bool SocketSetNonBlocking(Socket *sock)
{
    bool ret = true;
#if defined(_WIN32)
    unsigned long mode = 1;
    ret                = ioctlsocket(sock->channel, FIONBIO, &mode);
#else
    const int flags = fcntl(sock->channel, F_GETFL, 0);
    if ((flags & O_NONBLOCK))
    {
        TraceLog(LOG_DEBUG, "Socket was already in non-blocking mode");
        return ret;
    }
    ret = (0 == fcntl(sock->channel, F_SETFL, (flags | O_NONBLOCK)));
#endif
    return ret;
}

// Set options specified in SocketConfig to Socket sock
static bool SocketSetOptions(SocketConfig *config, Socket *sock)
{
    for (int i = 0; i < SOCKET_MAX_SOCK_OPTS; i++)
    {
        SocketOpt *opt = &config->sockopts[i];
        if (opt->id == 0)
        {
            break;
        }

        if (setsockopt(sock->channel, SOL_SOCKET, opt->id, opt->value, opt->valueLen) < 0)
        {
            return false;
        }
    }

    return true;
}

// Set "hints" in an addrinfo struct, to be passed to getaddrinfo.
static void SocketSetHints(SocketConfig *config, struct addrinfo *hints)
{
    if (config == NULL || hints == NULL)
    {
        return;
    }
    memset(hints, 0, sizeof(*hints));

    // Check if the ip supplied in the config is a valid ipv4 ip ipv6 address
    if (IsIPv4Address(config->host))
    {
        hints->ai_family = AF_INET;
        hints->ai_flags |= AI_NUMERICHOST;
    }
    else
    {
        if (IsIPv6Address(config->host))
        {
            hints->ai_family = AF_INET6;
            hints->ai_flags |= AI_NUMERICHOST;
        }
        else
        {
            hints->ai_family = AF_UNSPEC;
        }
    }

    if (config->type == SOCKET_UDP)
    {
        hints->ai_socktype = SOCK_DGRAM;
    }
    else
    {
        hints->ai_socktype = SOCK_STREAM;
    }

    // Set passive unless UDP client
    if (!(config->type == SOCKET_UDP) || config->server)
    {
        hints->ai_flags = AI_PASSIVE;
    }
}

//----------------------------------------------------------------------------------
// Module implementation
//----------------------------------------------------------------------------------

//    Initialise the network (requires for windows platforms only)
bool InitNetwork()
{
#if defined(_WIN32)
    WORD    wVersionRequested;
    WSADATA wsaData;
    int     err;

    wVersionRequested = MAKEWORD(2, 2);
    err               = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
        TraceLog(LOG_WARNING, "WinSock failed to initialise.");
        return false;
    }
    else
    {
        TraceLog(LOG_INFO, "WinSock initialised.");
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        TraceLog(LOG_WARNING, "WinSock failed to initialise.");
        WSACleanup();
        return false;
    }

    return true;
#else
    return true;
#endif
}

//    Cleanup, and close the network
void CloseNetwork()
{
#if defined(_WIN32)
    WSACleanup();
#endif
}

//    Protocol-independent name resolution from an address to an ANSI host name
//    and from a port number to the ANSI service name.
//
//    The flags parameter can be used to customize processing of the getnameinfo function
//
//    The following flags are available:
//
//    NAME_INFO_DEFAULT        0x00    // No flags set
//    NAME_INFO_NOFQDN        0x01    // Only return nodename portion for local hosts
//    NAME_INFO_NUMERICHOST    0x02    // Return numeric form of the host's address
//    NAME_INFO_NAMEREQD        0x04    // Error if the host's name not in DNS
//    NAME_INFO_NUMERICSERV    0x08    // Return numeric form of the service (port #)
//    NAME_INFO_DGRAM            0x10    // Service is a datagram service
void ResolveIP(const char *ip, const char *port, int flags, char *host, char *serv)
{
    // Variables
    int              status; // Status value to return (0) is success
    struct addrinfo  hints;  // Address flags (IPV4, IPV6, UDP?)
    struct addrinfo *res;    // A pointer to the resulting address list

    // Set the hints
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6 (AF_INET, AF_INET6)
    hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP)

    // Populate address information
    status = getaddrinfo(ip,     // e.g. "www.example.com" or IP
                         port,   // e.g. "http" or port number
                         &hints, // e.g. SOCK_STREAM/SOCK_DGRAM
                         &res    // The struct to populate
    );

    // Did we succeed?
    if (status != 0)
    {
        TraceLog(LOG_WARNING, "Failed to get resolve host %s:%s: %s", ip, port, gai_strerror(errno));
    }
    else
    {
        TraceLog(LOG_DEBUG, "Resolving... %s::%s", ip, port);
    }

    // Attempt to resolve network byte order ip to hostname
    switch (res->ai_family)
    {
        case AF_INET:
            status = getnameinfo(&*((struct sockaddr *) res->ai_addr),
                                 sizeof(*((struct sockaddr_in *) res->ai_addr)),
                                 host,
                                 NI_MAXHOST,
                                 serv,
                                 NI_MAXSERV,
                                 flags);
            break;
        case AF_INET6:
        /*
            status = getnameinfo(&*((struct sockaddr_in6 *) res->ai_addr),          // TODO.
                                 sizeof(*((struct sockaddr_in6 *) res->ai_addr)),
                                 host, NI_MAXHOST, serv, NI_MAXSERV, flags);
        */
            break;
        default: break;
    }

    if (status != 0)
    {
        TraceLog(LOG_WARNING, "Failed to resolve ip %s: %s", ip, SocketGetLastErrorString());
    }
    else
    {
        TraceLog(LOG_DEBUG, "Successfully resolved %s::%s to %s", ip, port, host);
    }

    // Free the pointer to the data returned by addrinfo
    freeaddrinfo(res);
}

//    Protocol-independent translation from an ANSI host name to an address
//
//    e.g.
//    const char* address    = "127.0.0.1" (local address)
//    const char* port    = "80"
//
//  Parameters:
//      const char* address - A pointer to a NULL-terminated ANSI string that contains a host (node) name or a numeric host address string.
//      const char* service - A pointer to a NULL-terminated ANSI string that contains either a service name or port number represented as a string.
//
//    Returns:
//        The total amount of addresses found, -1 on error
//
int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr)
{
    // Variables
    int              status; // Status value to return (0) is success
    struct addrinfo  hints;  // Address flags (IPV4, IPV6, UDP?)
    struct addrinfo *res;    // will point to the results
    struct addrinfo *iterator;
    assert(((address != NULL || address != 0) || (service != NULL || service != 0)));
    assert(((addressType == AF_INET) || (addressType == AF_INET6) || (addressType == AF_UNSPEC)));

    // Set the hints
    memset(&hints, 0, sizeof hints);
    hints.ai_family = addressType; // Either IPv4 or IPv6 (ADDRESS_TYPE_IPV4, ADDRESS_TYPE_IPV6)
    hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP)
    hints.ai_flags = flags;
    assert((hints.ai_addrlen == 0) || (hints.ai_addrlen == 0));
    assert((hints.ai_canonname == 0) || (hints.ai_canonname == 0));
    assert((hints.ai_addr == 0) || (hints.ai_addr == 0));
    assert((hints.ai_next == 0) || (hints.ai_next == 0));

    // When the address is NULL, populate the IP for me
    if (address == NULL)
    {
        if ((hints.ai_flags & AI_PASSIVE) == 0)
        {
            hints.ai_flags |= AI_PASSIVE;
        }
    }

    TraceLog(LOG_INFO, "Resolving host...");

    // Populate address information
    status = getaddrinfo(address, // e.g. "www.example.com" or IP
                         service, // e.g. "http" or port number
                         &hints,  // e.g. SOCK_STREAM/SOCK_DGRAM
                         &res     // The struct to populate
    );

    // Did we succeed?
    if (status != 0)
    {
        int error = SocketGetLastError();
        SocketSetLastError(0);
        TraceLog(LOG_WARNING, "Failed to get resolve host: %s", SocketErrorCodeToString(error));
        return -1;
    }
    else
    {
        TraceLog(LOG_INFO, "Successfully resolved host %s:%s", address, service);
    }

    // Calculate the size of the address information list
    int size = 0;
    for (iterator = res; iterator != NULL; iterator = iterator->ai_next)
    {
        size++;
    }

    // Validate the size is > 0, otherwise return
    if (size <= 0)
    {
        TraceLog(LOG_WARNING, "Error, no addresses found.");
        return -1;
    }

    // If not address list was allocated, allocate it dynamically with the known address size
    if (outAddr == NULL)
    {
        outAddr = (AddressInformation *) malloc(size * sizeof(AddressInformation));
    }

    // Dynamically allocate an array of address information structs
    if (outAddr != NULL)
    {
        int i;
        for (i = 0; i < size; ++i)
        {
            outAddr[i] = AllocAddress();
            if (outAddr[i] == NULL)
            {
                break;
            }
        }
        outAddr[i] = NULL;
        if (i != size)
        {
            outAddr = NULL;
        }
    }
    else
    {
        TraceLog(LOG_WARNING,
                 "Error, failed to dynamically allocate memory for the address list");
        return -1;
    }

    // Copy all the address information from res into outAddrList
    int i = 0;
    for (iterator = res; iterator != NULL; iterator = iterator->ai_next)
    {
        if (i < size)
        {
            outAddr[i]->addr.ai_flags    = iterator->ai_flags;
            outAddr[i]->addr.ai_family   = iterator->ai_family;
            outAddr[i]->addr.ai_socktype = iterator->ai_socktype;
            outAddr[i]->addr.ai_protocol = iterator->ai_protocol;
            outAddr[i]->addr.ai_addrlen  = iterator->ai_addrlen;
            *outAddr[i]->addr.ai_addr    = *iterator->ai_addr;
#if NET_DEBUG_ENABLED
            TraceLog(LOG_DEBUG, "GetAddressInformation");
            TraceLog(LOG_DEBUG, "\tFlags: 0x%x", iterator->ai_flags);
            //PrintSocket(outAddr[i]->addr.ai_addr, outAddr[i]->addr.ai_family, outAddr[i]->addr.ai_socktype, outAddr[i]->addr.ai_protocol);
            TraceLog(LOG_DEBUG, "Length of this sockaddr: %d", outAddr[i]->addr.ai_addrlen);
            TraceLog(LOG_DEBUG, "Canonical name: %s", iterator->ai_canonname);
#endif
            i++;
        }
    }

    // Free the pointer to the data returned by addrinfo
    freeaddrinfo(res);

    // Return the total count of addresses found
    return size;
}

//    This here is the bread and butter of the socket API, This function will
//    attempt to open a socket, bind and listen to it based on the config passed in
//
//    SocketConfig* config - Configuration for which socket to open
//    SocketResult* result - The results of this function (if any, including errors)
//
//    e.g.
//    SocketConfig server_config = {            SocketConfig client_config = {
//        .host        = "127.0.0.1",                .host = "127.0.0.1",
//        .port        = 8080,                    .port = 8080,
//        .server      = true,                };
//        .nonblocking = true,
//    };
//    SocketResult server_res;                SocketResult client_res;
bool SocketCreate(SocketConfig *config, SocketResult *result)
{
    // Socket creation result
    bool success = true;

    // Make sure we've not received a null config or result pointer
    if (config == NULL || result == NULL)
    {
        return (success = false);
    }

    // Set the defaults based on the config
    if (!SocketSetDefaults(config))
    {
        TraceLog(LOG_WARNING, "Configuration Error.");
        success = false;
    }
    else
    {
        // Create the socket
        if (CreateSocket(config, result))
        {
            if (config->nonblocking)
            {
                SocketSetNonBlocking(result->socket);
            }
            else
            {
                SocketSetBlocking(result->socket);
            }
        }
        else
        {
            success = false;
        }
    }
    return success;
}

// Bind a socket to a local address
// Note: The bind function is required on an unconnected socket before subsequent calls to the listen function.
bool SocketBind(SocketConfig *config, SocketResult *result)
{
    bool success                       = false;
    result->status                     = RESULT_FAILURE;
    struct sockaddr_storage *sock_addr = NULL;

    // Don't bind to a socket that isn't configured as a server
    if (!IsSocketValid(result->socket) || !config->server)
    {
        TraceLog(LOG_WARNING,
                 "Cannot bind to socket marked as \"Client\" in SocketConfig.");
        success = false;
    }
    else
    {
        if (result->socket->isIPv6)
        {
            sock_addr = (struct sockaddr_storage *) &result->socket->addripv6->address;
        }
        else
        {
            sock_addr = (struct sockaddr_storage *) &result->socket->addripv4->address;
        }
        if (sock_addr != NULL)
        {
            if (bind(result->socket->channel, (struct sockaddr *) sock_addr, sizeof(*sock_addr)) != SOCKET_ERROR)
            {
                TraceLog(LOG_INFO, "Successfully bound socket.");
                success = true;
            }
            else
            {
                result->socket->status = SocketGetLastError();
                TraceLog(LOG_WARNING, "Socket Error: %s",
                         SocketErrorCodeToString(result->socket->status));
                SocketSetLastError(0);
                success = false;
            }
        }
    }
    // Was the bind a success?
    if (success)
    {
        result->status         = RESULT_SUCCESS;
        result->socket->ready  = 0;
        result->socket->status = 0;
        socklen_t sock_len     = sizeof(*sock_addr);
        if (getsockname(result->socket->channel, (struct sockaddr *) sock_addr, &sock_len) < 0)
        {
            TraceLog(LOG_WARNING, "Couldn't get socket address");
        }
        else
        {
            struct sockaddr_in *s = (struct sockaddr_in *) sock_addr;
            // result->socket->address.host = s->sin_addr.s_addr;
            // result->socket->address.port = s->sin_port;

            //
            result->socket->addripv4
                = (struct _SocketAddressIPv4 *) malloc(sizeof(*result->socket->addripv4));
            if (result->socket->addripv4 != NULL)
            {
                memset(result->socket->addripv4, 0, sizeof(*result->socket->addripv4));
            }
            memcpy(&result->socket->addripv4->address, (struct sockaddr_in *) &s->sin_addr, sizeof(struct sockaddr_in));
            //
        }
    }
    return success;
}

// Listens (and queues) incoming connections requests for a bound port.
bool SocketListen(SocketConfig *config, SocketResult *result)
{
    bool success   = false;
    result->status = RESULT_FAILURE;

    // Don't bind to a socket that isn't configured as a server
    if (!IsSocketValid(result->socket) || !config->server)
    {
        TraceLog(LOG_WARNING,
                 "Cannot listen on socket marked as \"Client\" in SocketConfig.");
        success = false;
    }
    else
    {
        // Don't listen on UDP sockets
        if (!(config->type == SOCKET_UDP))
        {
            if (listen(result->socket->channel, config->backlog_size) != SOCKET_ERROR)
            {
                TraceLog(LOG_INFO, "Started listening on socket...");
                success = true;
            }
            else
            {
                success = false;
                result->socket->status = SocketGetLastError();
                TraceLog(LOG_WARNING, "Socket Error: %s",
                         SocketErrorCodeToString(result->socket->status));
                SocketSetLastError(0);
            }
        }
        else
        {
            TraceLog(LOG_WARNING,
                     "Cannot listen on socket marked as \"UDP\" (datagram) in SocketConfig.");
            success = false;
        }
    }

    // Was the listen a success?
    if (success)
    {
        result->status         = RESULT_SUCCESS;
        result->socket->ready  = 0;
        result->socket->status = 0;
    }
    return success;
}

// Connect the socket to the destination specified by "host" and "port" in SocketConfig
bool SocketConnect(SocketConfig *config, SocketResult *result)
{
    bool success   = true;
    result->status = RESULT_FAILURE;

    // Only bind to sockets marked as server
    if (config->server)
    {
        TraceLog(LOG_WARNING,
                 "Cannot connect to socket marked as \"Server\" in SocketConfig.");
        success = false;
    }
    else
    {
        if (IsIPv4Address(config->host))
        {
            struct sockaddr_in ip4addr;
            ip4addr.sin_family = AF_INET;
            unsigned long hport;
            hport            = strtoul(config->port, NULL, 0);
            ip4addr.sin_port = htons(hport);
            
            // TODO: Changed the code to avoid the usage of inet_pton and inet_ntop replacing them with getnameinfo (that should have a better support on windows).
            
            //inet_pton(AF_INET, config->host, &ip4addr.sin_addr);
            int connect_result = connect(result->socket->channel, (struct sockaddr *) &ip4addr, sizeof(ip4addr));
            if (connect_result == SOCKET_ERROR)
            {
                result->socket->status = SocketGetLastError();
                SocketSetLastError(0);
                switch (result->socket->status)
                {
                    case WSAEWOULDBLOCK:
                    {
                        success = true;
                        break;
                    }
                    default:
                    {
                        TraceLog(LOG_WARNING, "Socket Error: %s",
                                 SocketErrorCodeToString(result->socket->status));
                        success = false;
                        break;
                    }
                }
            }
            else
            {
                TraceLog(LOG_INFO, "Successfully connected to socket.");
                success = true;
            }
        }
        else
        {
            if (IsIPv6Address(config->host))
            {
                struct sockaddr_in6 ip6addr;
                ip6addr.sin6_family = AF_INET6;
                unsigned long hport;
                hport             = strtoul(config->port, NULL, 0);
                ip6addr.sin6_port = htons(hport);
                //inet_pton(AF_INET6, config->host, &ip6addr.sin6_addr);    // TODO.
                int connect_result = connect(result->socket->channel, (struct sockaddr *) &ip6addr, sizeof(ip6addr));
                if (connect_result == SOCKET_ERROR)
                {
                    result->socket->status = SocketGetLastError();
                    SocketSetLastError(0);
                    switch (result->socket->status)
                    {
                        case WSAEWOULDBLOCK:
                        {
                            success = true;
                            break;
                        }
                        default:
                        {
                            TraceLog(LOG_WARNING, "Socket Error: %s",
                                     SocketErrorCodeToString(result->socket->status));
                            success = false;
                            break;
                        }
                    }
                }
                else
                {
                    TraceLog(LOG_INFO, "Successfully connected to socket.");
                    success = true;
                }
            }
        }
    }

    if (success)
    {
        result->status         = RESULT_SUCCESS;
        result->socket->ready  = 0;
        result->socket->status = 0;
    }

    return success;
}

//    Closes an existing socket
//
//    SocketChannel socket - The id of the socket to close
void SocketClose(Socket *sock)
{
    if (sock != NULL)
    {
        if (sock->channel != INVALID_SOCKET)
        {
            closesocket(sock->channel);
        }
    }
}

// Returns the sockaddress for a specific socket in a generic storage struct
SocketAddressStorage SocketGetPeerAddress(Socket *sock)
{
    // TODO.
    /*
    if (sock->isServer) return NULL;
    if (sock->isIPv6) return sock->addripv6;
    else return sock->addripv4;
    */
    
    return NULL;
}

// Return the address-type appropriate host portion of a socket address
char *GetSocketAddressHost(SocketAddressStorage storage)
{
    assert(storage->address.ss_family == AF_INET || storage->address.ss_family == AF_INET6);
    return SocketAddressToString((struct sockaddr_storage *) storage);
}

// Return the address-type appropriate port(service) portion of a socket address
short GetSocketAddressPort(SocketAddressStorage storage)
{
    //return ntohs(GetSocketPortPtr(storage));      // TODO.
    
    return 0;
}

//    The accept function permits an incoming connection attempt on a socket.
//
//    SocketChannel    listener    - The socket to listen for incoming connections on (i.e. server)
//    SocketResult*    out            - The result of this function (if any, including errors)
//
//    e.g.
//
//    SocketResult connection;
//    bool         connected = false;
//    if (!connected)
//    {
//        if (SocketAccept(server_res.socket.channel, &connection))
//        {
//            connected = true;
//        }
//    }
Socket *SocketAccept(Socket *server, SocketConfig *config)
{
    if (!server->isServer || server->type == SOCKET_UDP)
    {
        return NULL;
    }
    struct sockaddr_storage sock_addr;
    socklen_t               sock_alen;
    Socket *                sock;
    sock          = AllocSocket();
    server->ready = 0;
    sock_alen     = sizeof(sock_addr);
    sock->channel = accept(server->channel, (struct sockaddr *) &sock_addr, &sock_alen);
    if (sock->channel == INVALID_SOCKET)
    {
        sock->status = SocketGetLastError();
        TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
        SocketSetLastError(0);
        SocketClose(sock);
        return NULL;
    }
    (config->nonblocking) ? SocketSetNonBlocking(sock) : SocketSetBlocking(sock);
    sock->isServer = false;
    sock->ready    = 0;
    sock->type     = server->type;
    switch (sock_addr.ss_family)
    {
        case AF_INET:
        {
            struct sockaddr_in *s = ((struct sockaddr_in *) &sock_addr);
            sock->addripv4 = (struct _SocketAddressIPv4 *) malloc(sizeof(*sock->addripv4));
            if (sock->addripv4 != NULL)
            {
                memset(sock->addripv4, 0, sizeof(*sock->addripv4));
                memcpy(&sock->addripv4->address, (struct sockaddr_in *) &s->sin_addr, sizeof(struct sockaddr_in));
                TraceLog(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *) s),
                         ntohs(sock->addripv4->address.sin_port));
            }
        }
        break;
        case AF_INET6:
        {
            struct sockaddr_in6 *s = ((struct sockaddr_in6 *) &sock_addr);
            sock->addripv6 = (struct _SocketAddressIPv6 *) malloc(sizeof(*sock->addripv6));
            if (sock->addripv6 != NULL)
            {
                memset(sock->addripv6, 0, sizeof(*sock->addripv6));
                memcpy(&sock->addripv6->address, (struct sockaddr_in6 *) &s->sin6_addr, sizeof(struct sockaddr_in6));
                TraceLog(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *) s),
                         ntohs(sock->addripv6->address.sin6_port));
            }
        }
        break;
    }
    return sock;
}

// Verify that the channel is in the valid range
static int ValidChannel(int channel)
{
    if ((channel < 0) || (channel >= SOCKET_MAX_UDPCHANNELS))
    {
        TraceLog(LOG_WARNING, "Invalid channel");
        return 0;
    }
    return 1;
}

// Set the socket channel
int SocketSetChannel(Socket *socket, int channel, const IPAddress *address)
{
    struct UDPChannel *binding;
    if (socket == NULL)
    {
        TraceLog(LOG_WARNING, "Passed a NULL socket");
        return (-1);
    }
    if (channel == -1)
    {
        for (channel = 0; channel < SOCKET_MAX_UDPCHANNELS; ++channel)
        {
            binding = &socket->binding[channel];
            if (binding->numbound < SOCKET_MAX_UDPADDRESSES)
            {
                break;
            }
        }
    }
    else
    {
        if (!ValidChannel(channel))
        {
            return (-1);
        }
        binding = &socket->binding[channel];
    }
    if (binding->numbound == SOCKET_MAX_UDPADDRESSES)
    {
        TraceLog(LOG_WARNING, "No room for new addresses");
        return (-1);
    }
    binding->address[binding->numbound++] = *address;
    return (channel);
}

// Remove the socket channel
void SocketUnsetChannel(Socket *socket, int channel)
{
    if ((channel >= 0) && (channel < SOCKET_MAX_UDPCHANNELS))
    {
        socket->binding[channel].numbound = 0;
    }
}

/* Allocate/free a single UDP packet 'size' bytes long.
   The new packet is returned, or NULL if the function ran out of memory.
 */
SocketDataPacket *AllocPacket(int size)
{
    SocketDataPacket *packet;
    int               error;

    error  = 1;
    packet = (SocketDataPacket *) malloc(sizeof(*packet));
    if (packet != NULL)
    {
        packet->maxlen = size;
        packet->data   = (uint8_t *) malloc(size);
        if (packet->data != NULL)
        {
            error = 0;
        }
    }
    if (error)
    {
        FreePacket(packet);
        packet = NULL;
    }
    return (packet);
}

int ResizePacket(SocketDataPacket *packet, int newsize)
{
    uint8_t *newdata;

    newdata = (uint8_t *) malloc(newsize);
    if (newdata != NULL)
    {
        free(packet->data);
        packet->data   = newdata;
        packet->maxlen = newsize;
    }
    return (packet->maxlen);
}

void FreePacket(SocketDataPacket *packet)
{
    if (packet)
    {
        free(packet->data);
        free(packet);
    }
}

/* Allocate/Free a UDP packet vector (array of packets) of 'howmany' packets,
   each 'size' bytes long.
   A pointer to the packet array is returned, or NULL if the function ran out
   of memory.
 */
SocketDataPacket **AllocPacketList(int howmany, int size)
{
    SocketDataPacket **packetV;

    packetV = (SocketDataPacket **) malloc((howmany + 1) * sizeof(*packetV));
    if (packetV != NULL)
    {
        int i;
        for (i = 0; i < howmany; ++i)
        {
            packetV[i] = AllocPacket(size);
            if (packetV[i] == NULL)
            {
                break;
            }
        }
        packetV[i] = NULL;

        if (i != howmany)
        { 
            FreePacketList(packetV);
            packetV = NULL;
        }
    }
    return (packetV);
}

void FreePacketList(SocketDataPacket **packetV)
{
    if (packetV)
    {
        int i;
        for (i = 0; packetV[i]; ++i)
        {
            FreePacket(packetV[i]);
        }
        free(packetV);
    }
}

//    Send 'len' bytes of 'data' over the non-server socket 'sock'
//
//    Example
int SocketSend(Socket *sock, const void *datap, int length)
{
    int                  sent    = 0;
    int                  left    = length;
    int                  status  = -1;
    int                  numsent = 0;
    const unsigned char *data    = (const unsigned char *) datap;

    // Server sockets are for accepting connections only
    if (sock->isServer)
    {
        TraceLog(LOG_WARNING, "Cannot send information on a server socket");
        return -1;
    }

    // Which socket are we trying to send data on
    switch (sock->type)
    {
        case SOCKET_TCP:
        {
            SocketSetLastError(0);
            do
            {
                length = send(sock->channel, (const char *) data, left, 0);
                if (length > 0)
                {
                    sent += length;
                    left -= length;
                    data += length;
                }
            } while ((left > 0) && // While we still have bytes left to send
                     ((length > 0) || // The amount of bytes we actually sent is > 0
                      (SocketGetLastError() == WSAEINTR)) // The socket was interupted
            );

            if (length == SOCKET_ERROR)
            {
                sock->status = SocketGetLastError();
                TraceLog(LOG_DEBUG, "Socket Error: %s", SocketErrorCodeToString(sock->status));
                SocketSetLastError(0);
            }
            else
            {
                TraceLog(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, sent);
            }

            return sent;
        }
        break;
        case SOCKET_UDP:
        {
            SocketSetLastError(0);
            if (sock->isIPv6)
            {
                status = sendto(sock->channel, (const char *) data, left, 0,
                                (struct sockaddr *) &sock->addripv6->address,
                                sizeof(sock->addripv6->address));
            }
            else
            {
                status = sendto(sock->channel, (const char *) data, left, 0,
                                (struct sockaddr *) &sock->addripv4->address,
                                sizeof(sock->addripv4->address));
            }
            if (sent >= 0)
            {
                sock->status = 0;
                ++numsent;
                TraceLog(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, status);
            }
            else
            {
                sock->status = SocketGetLastError();
                TraceLog(LOG_DEBUG, "Socket Error: %s", SocketGetLastErrorString(sock->status));
                SocketSetLastError(0);
                return 0;
            }
            return numsent;
        }
        break;
        default: break;
    }
    return -1;
}

//    Receive up to 'maxlen' bytes of data over the non-server socket 'sock',
//    and store them in the buffer pointed to by 'data'.
//    This function returns the actual amount of data received.  If the return
//    value is less than or equal to zero, then either the remote connection was
//    closed, or an unknown socket error occurred.
int SocketReceive(Socket *sock, void *data, int maxlen)
{
    int len = 0;
    int numrecv = 0;
    int status = 0;
    socklen_t sock_len;
    struct sockaddr_storage sock_addr;
    //char ip[INET6_ADDRSTRLEN];

    // Server sockets are for accepting connections only
    if (sock->isServer && sock->type == SOCKET_TCP)
    {
        sock->status = SocketGetLastError();
        TraceLog(LOG_DEBUG, "Socket Error: %s", "Server sockets cannot be used to receive data");
        SocketSetLastError(0);
        return 0;
    }

    // Which socket are we trying to send data on
    switch (sock->type)
    {
        case SOCKET_TCP:
        {
            SocketSetLastError(0);
            do
            {
                len = recv(sock->channel, (char *) data, maxlen, 0);
            } while (SocketGetLastError() == WSAEINTR);

            if (len > 0)
            {
                // Who sent the packet?
                if (sock->type == SOCKET_UDP)
                {
                    //TraceLog(LOG_DEBUG, "Received data from: %s", inet_ntop(sock_addr.ss_family, GetSocketAddressPtr((struct sockaddr *) &sock_addr), ip, sizeof(ip)));
                }
                
                ((unsigned char *) data)[len] = '\0'; // Add null terminating character to the end of the stream
                TraceLog(LOG_DEBUG, "Received \"%s\" (%d bytes)", data, len);
            }
            
            sock->ready = 0;
            return len;
        }
        break;
        case SOCKET_UDP:
        {
            SocketSetLastError(0);
            sock_len = sizeof(sock_addr);
            status = recvfrom(sock->channel, // The receving channel
                              data,   // A pointer to the data buffer to fill
                              maxlen, // The max length of the data to fill
                              0,      // Flags
                              (struct sockaddr *) &sock_addr, // The address of the recevied data
                              &sock_len // The length of the received data address
            );
            if (status >= 0)
            {
                ++numrecv;
            }
            else
            {
                sock->status = SocketGetLastError();
                switch (sock->status)
                {
                    case WSAEWOULDBLOCK: { break;
                    }
                    default:
                    {
                        TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
                        break;
                    }
                }
                SocketSetLastError(0);
                return 0;
            }
            sock->ready = 0;
            return numrecv;
        }
        break;
    }
    return -1;
}

// Does the socket have it's 'ready' flag set?
bool IsSocketReady(Socket *sock)
{
    return (sock != NULL) && (sock->ready);
}

// Check if the socket is considered connected
bool IsSocketConnected(Socket *sock)
{
#if defined(_WIN32)
    FD_SET writefds;
    FD_ZERO(&writefds);
    FD_SET(sock->channel, &writefds);
    struct timeval timeout;
    timeout.tv_sec  = 1;
    timeout.tv_usec = 1000000000UL;
    int total       = select(0, NULL, &writefds, NULL, &timeout);
    if (total == -1)
    { // Error
        sock->status = SocketGetLastError();
        TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
        SocketSetLastError(0);
    }
    else if (total == 0)
    { // Timeout
        return false;
    }
    else
    {
        if (FD_ISSET(sock->channel, &writefds))
        {
            return true;
        }
    }
    return false;
#else
    return true;
#endif
}

// Allocate and return a SocketResult struct
SocketResult *AllocSocketResult()
{
    struct SocketResult *res;
    res = (struct SocketResult *) malloc(sizeof(*res));
    if (res != NULL)
    {
        memset(res, 0, sizeof(*res));
        if ((res->socket = AllocSocket()) == NULL)
        {
            free(res);
            res = NULL;
        }
    }
    return res;
}

// Free an allocated SocketResult
void FreeSocketResult(SocketResult **result)
{
    if (*result != NULL)
    {
        if ((*result)->socket != NULL)
        {
            FreeSocket(&((*result)->socket));
        }
        free(*result);
        *result = NULL;
    }
}

// Allocate a Socket
Socket *AllocSocket()
{
    // Allocate a socket if one already hasn't been
    struct Socket *sock;
    sock = (Socket *) malloc(sizeof(*sock));
    if (sock != NULL)
    {
        memset(sock, 0, sizeof(*sock));
    }
    else
    {
        TraceLog(
            LOG_WARNING, "Ran out of memory attempting to allocate a socket");
        SocketClose(sock);
        free(sock);
        sock = NULL;
    }
    return sock;
}

// Free an allocated Socket
void FreeSocket(Socket **sock)
{
    if (*sock != NULL)
    {
        free(*sock);
        *sock = NULL;
    }
}

// Allocate a SocketSet
SocketSet *AllocSocketSet(int max)
{
    struct SocketSet *set;
    int               i;

    set = (struct SocketSet *) malloc(sizeof(*set));
    if (set != NULL)
    {
        set->numsockets = 0;
        set->maxsockets = max;
        set->sockets = (struct Socket **) malloc(max * sizeof(*set->sockets));
        if (set->sockets != NULL)
        {
            for (i = 0; i < max; ++i)
            {
                set->sockets[i] = NULL;
            }
        }
        else
        {
            free(set);
            set = NULL;
        }
    }
    return (set);
}

// Free an allocated SocketSet
void FreeSocketSet(SocketSet *set)
{
    if (set)
    {
        free(set->sockets);
        free(set);
    }
}

// Add a Socket "sock" to the SocketSet "set"
int AddSocket(SocketSet *set, Socket *sock)
{
    if (sock != NULL)
    {
        if (set->numsockets == set->maxsockets)
        {
            TraceLog(LOG_DEBUG, "Socket Error: %s", "SocketSet is full");
            SocketSetLastError(0);
            return (-1);
        }
        set->sockets[set->numsockets++] = (struct Socket *) sock;
    }
    else
    {
        TraceLog(LOG_DEBUG, "Socket Error: %s", "Socket was null");
        SocketSetLastError(0);
        return (-1);
    }
    return (set->numsockets);
}

// Remove a Socket "sock" to the SocketSet "set"
int RemoveSocket(SocketSet *set, Socket *sock)
{
    int i;

    if (sock != NULL)
    {
        for (i = 0; i < set->numsockets; ++i)
        {
            if (set->sockets[i] == (struct Socket *) sock)
            {
                break;
            }
        }
        if (i == set->numsockets)
        {
            TraceLog(LOG_DEBUG, "Socket Error: %s", "Socket not found");
            SocketSetLastError(0);
            return (-1);
        }
        --set->numsockets;
        for (; i < set->numsockets; ++i)
        {
            set->sockets[i] = set->sockets[i + 1];
        }
    }
    return (set->numsockets);
}

// Check the sockets in the socket set for pending information
int CheckSockets(SocketSet *set, unsigned int timeout)
{
    int            i;
    SOCKET         maxfd;
    int            retval;
    struct timeval tv;
    fd_set         mask;

    /* Find the largest file descriptor */
    maxfd = 0;
    for (i = set->numsockets - 1; i >= 0; --i)
    {
        if (set->sockets[i]->channel > maxfd)
        {
            maxfd = set->sockets[i]->channel;
        }
    }

    // Check the file descriptors for available data
    do
    {
        SocketSetLastError(0);

        // Set up the mask of file descriptors
        FD_ZERO(&mask);
        for (i = set->numsockets - 1; i >= 0; --i)
        {
            FD_SET(set->sockets[i]->channel, &mask);
        } // Set up the timeout
        tv.tv_sec  = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;

        /* Look! */
        retval = select(maxfd + 1, &mask, NULL, NULL, &tv);
    } while (SocketGetLastError() == WSAEINTR);

    // Mark all file descriptors ready that have data available
    if (retval > 0)
    {
        for (i = set->numsockets - 1; i >= 0; --i)
        {
            if (FD_ISSET(set->sockets[i]->channel, &mask))
            {
                set->sockets[i]->ready = 1;
            }
        }
    }
    return (retval);
} 

// Allocate an AddressInformation
AddressInformation AllocAddress()
{
    AddressInformation addressInfo = NULL;
    addressInfo = (AddressInformation) calloc(1, sizeof(*addressInfo));
    if (addressInfo != NULL)
    {
        addressInfo->addr.ai_addr = (struct sockaddr *) calloc(1, sizeof(struct sockaddr));
        if (addressInfo->addr.ai_addr == NULL)
        {
            TraceLog(LOG_WARNING,
                     "Failed to allocate memory for \"struct sockaddr\"");
        }
    }
    else
    {
        TraceLog(LOG_WARNING,
                 "Failed to allocate memory for \"struct AddressInformation\"");
    }
    return addressInfo;
}

// Free an AddressInformation struct
void FreeAddress(AddressInformation *addressInfo)
{
    if (*addressInfo != NULL)
    {
        if ((*addressInfo)->addr.ai_addr != NULL)
        {
            free((*addressInfo)->addr.ai_addr);
            (*addressInfo)->addr.ai_addr = NULL;
        }
        free(*addressInfo);
        *addressInfo = NULL;
    }
}

// Allocate a list of AddressInformation
AddressInformation *AllocAddressList(int size)
{
    AddressInformation *addr;
    addr = (AddressInformation *) malloc(size * sizeof(AddressInformation));
    return addr;
}

// Opaque datatype accessor addrinfo->ai_family
int GetAddressFamily(AddressInformation address)
{
    return address->addr.ai_family;
}

// Opaque datatype accessor addrinfo->ai_socktype
int GetAddressSocketType(AddressInformation address)
{
    return address->addr.ai_socktype;
}

// Opaque datatype accessor addrinfo->ai_protocol
int GetAddressProtocol(AddressInformation address)
{
    return address->addr.ai_protocol;
}

// Opaque datatype accessor addrinfo->ai_canonname
char *GetAddressCanonName(AddressInformation address)
{
    return address->addr.ai_canonname;
}

// Opaque datatype accessor addrinfo->ai_addr
char *GetAddressHostAndPort(AddressInformation address, char *outhost, int *outport)
{
    //char *ip[INET6_ADDRSTRLEN];
    char *result = NULL;
    struct sockaddr_storage *storage = (struct sockaddr_storage *) address->addr.ai_addr;
    switch (storage->ss_family)
    {
        case AF_INET:
        {
            struct sockaddr_in *s = ((struct sockaddr_in *) address->addr.ai_addr);
            //result = inet_ntop(AF_INET, &s->sin_addr, ip, INET_ADDRSTRLEN);           // TODO.
            *outport = ntohs(s->sin_port);
        }
        break;
        case AF_INET6:
        {
            struct sockaddr_in6 *s = ((struct sockaddr_in6 *) address->addr.ai_addr);
            //result = inet_ntop(AF_INET6, &s->sin6_addr, ip, INET6_ADDRSTRLEN);          // TODO.
            *outport = ntohs(s->sin6_port);
        }
        break;
    }
    if (result == NULL)
    {
        TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(SocketGetLastError()));
        SocketSetLastError(0);
    }
    else
    {
        strcpy(outhost, result);
    }
    return result;
}

//
void PacketSend(Packet *packet)
{
    printf("Sending packet (%s) with size %d\n", packet->data, packet->size);
}

//
void PacketReceive(Packet *packet)
{
    printf("Receiving packet (%s) with size %d\n", packet->data, packet->size);
}

//
void PacketWrite16(Packet *packet, uint16_t value)
{
    printf("Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value);
    uint8_t *data = packet->data + packet->offs;
    *data++       = (uint8_t)(value >> 8);
    *data++       = (uint8_t)(value);
    packet->size += sizeof(uint16_t);
    packet->offs += sizeof(uint16_t);
    printf("Network: 0x%04" PRIX16 " - %" PRIu16 "\n", (uint16_t) *data, (uint16_t) *data);
}

//
void PacketWrite32(Packet *packet, uint32_t value)
{
    printf("Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value);
    uint8_t *data = packet->data + packet->offs;
    *data++       = (uint8_t)(value >> 24);
    *data++       = (uint8_t)(value >> 16);
    *data++       = (uint8_t)(value >> 8);
    *data++       = (uint8_t)(value);
    packet->size += sizeof(uint32_t);
    packet->offs += sizeof(uint32_t);
    printf("Network: 0x%08" PRIX32 " - %" PRIu32 "\n",
           (uint32_t)(((intptr_t) packet->data) - packet->offs),
           (uint32_t)(((intptr_t) packet->data) - packet->offs));
}

//
void PacketWrite64(Packet *packet, uint64_t value)
{
    printf("Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value);
    uint8_t *data = packet->data + packet->offs;
    *data++       = (uint8_t)(value >> 56);
    *data++       = (uint8_t)(value >> 48);
    *data++       = (uint8_t)(value >> 40);
    *data++       = (uint8_t)(value >> 32);
    *data++       = (uint8_t)(value >> 24);
    *data++       = (uint8_t)(value >> 16);
    *data++       = (uint8_t)(value >> 8);
    *data++       = (uint8_t)(value);
    packet->size += sizeof(uint64_t);
    packet->offs += sizeof(uint64_t);
    printf("Network: 0x%016" PRIX64 " - %" PRIu64 "\n",
           (uint64_t)(packet->data - packet->offs),
           (uint64_t)(packet->data - packet->offs));
}

//
uint16_t PacketRead16(Packet *packet)
{
    uint8_t *data = packet->data + packet->offs;
    packet->size += sizeof(uint16_t);
    packet->offs += sizeof(uint16_t);
    uint16_t value = ((uint16_t) data[0] << 8) | data[1];
    printf("Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value);
    return value;
}

//
uint32_t PacketRead32(Packet *packet)
{
    uint8_t *data = packet->data + packet->offs;
    packet->size += sizeof(uint32_t);
    packet->offs += sizeof(uint32_t);
    uint32_t value = ((uint32_t) data[0] << 24) | ((uint32_t) data[1] << 16) | ((uint32_t) data[2] << 8) | data[3];
    printf("Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value);
    return value;
}

//
uint64_t PacketRead64(Packet *packet)
{
    uint8_t *data = packet->data + packet->offs;
    packet->size += sizeof(uint64_t);
    packet->offs += sizeof(uint64_t);
    uint64_t value = ((uint64_t) data[0] << 56) | ((uint64_t) data[1] << 48) | ((uint64_t) data[2] << 40) | ((uint64_t) data[3] << 32) | ((uint64_t) data[4] << 24) | ((uint64_t) data[5] << 16) | ((uint64_t) data[6] << 8) | data[7];
    printf("Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value);
    return value;
}
