#ifndef SOCKET_HPP_
#define SOCKET_HPP_

#include <string>

// Include OS-specific headers to get sockets.
#ifdef _WIN32
#   undef _WIN32_WINNT
#   define _WIN32_WINNT 0x0601
#   include <winsock2.h>
#   include <ws2tcpip.h>
#   define lastSocketError() WSAGetLastError()
#   define SOCKET_EINPROGRESS WSAEINPROGRESS
#   define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
#else
#   include <netinet/in.h>
#   include <netinet/tcp.h>
#   include <sys/socket.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#   include <fcntl.h>
#   include <unistd.h>
    typedef int SOCKET;
#   define	closesocket close
#   define	SOCKET_ERROR -1
#   define  INVALID_SOCKET -1
#   define  lastSocketError() errno
#   define  SOCKET_EINPROGRESS EINPROGRESS
#   define  SOCKET_EWOULDBLOCK EWOULDBLOCK
#endif

/**
 * @brief Parse a network address string into hostname and port strings.
 * 
 * @param addr The address string.
 * @param hostname The host name will be written here.
 * @param portstr The port string will be written here.
 */
void parseNetAddr(std::string addr, std::string &hostname, std::string &portstr);

/**
 * @brief Try connecting to the specified network address.
 *
 * Returns the socket on success, or INVALID_SOCKET on error, and sets `error` to the error
 * message.
 */
SOCKET connectSocket(std::string addr, std::string &error);

/**
 * @brief Set whether a socket should be in blocking mode.
 * 
 * @param sock The socket.
 * @param blocking Whether it should be blocking.
 * @return true If successful.
 * @return false If failed.
 */
bool setSocketBlocking(SOCKET sock, bool blocking);

/**
 * @brief Read bytes from a socket.
 * 
 * This function only returns once the buffer is filled with the specified number
 * of bytes, or when the connection is closed or timed out.
 * 
 * @param sock The socket.
 * @param buffer The buffer.
 * @param size The number of bytes to read.
 * 
 * @return true If successful.
 * @return false If the connection was closed or timed out.
 */
bool readSocket(SOCKET sock, char *buffer, uint32_t size);

/**
 * @brief Write bytes to a socket, returns true if successful.
 */
bool writeSocket(SOCKET sock, const char *buffer, uint32_t size);

/**
 * @brief Returns a socket's remote address, as a "[IPv6]:port" string.
 * 
 * The socket must be in a connected state.
 */
std::string getSocketRemoteAddr(SOCKET sock);

#endif