#pragma once
#include "common.hpp"
#include "config.hpp"
#include "test.hpp"
#include <sys/socket.h>
#include <netinet/in.h>


#ifndef SO_ORIGINAL_DST
    #define SO_ORIGINAL_DST 80
#endif

#define ADDRSTRLEN cstrlen("0000:0000:0000:0000:0000:0000:0000:0000:12345")+1

typedef union sockaddr_u {
    sa_family_t family; // for convenience
    struct sockaddr_in  addr4;
    struct sockaddr_in6 addr6;

    INLINE bool operator==(const sockaddr_u& that) const {
        return (memcmp(this, &that, sizeof(sockaddr_u)) == 0);
    }
    INLINE bool operator>=(const sockaddr_u& that) const {
        return family && family == that.family && family == AF_INET?
               (htonl(addr4.sin_addr.s_addr) >= htonl(that.addr4.sin_addr.s_addr)):
               false; // TODO: (memcmp(&addr6.sin6_addr, &that.addr6.sin6_addr, sizeof(struct in6_addr)) >= 0);
    }
    INLINE bool operator<=(const sockaddr_u& that) const {
        return family && family == that.family && family == AF_INET?
               (htonl(addr4.sin_addr.s_addr) <= htonl(that.addr4.sin_addr.s_addr)):
               false; // TODO: (memcmp(&addr6.sin6_addr, &that.addr6.sin6_addr, sizeof(struct in6_addr)) <= 0);
    }

    bool apply_mask_lower(unsigned);
    bool apply_mask_upper(unsigned);
} sockaddr_t;

typedef char ip_str_t[INET6_ADDRSTRLEN];

char* addr2str(const sockaddr_t&, char*, bool port); ///< w/ port, returns \0 pointer, requires at least #ADDRSTRLEN
const char* addr2str(const sockaddr_t&, bool port=false); ///< returns static pointer
bool str2addr(const char*, sockaddr_t&); ///< parses ipv4:port string
sockaddr_t str2addr(const char*);

ssize_t peek(int, char**); ///< peeks from socket to static buffer
void read_empty(int); ///< read all available data in order to prevent sending RST to the client upon close() with a non-empty receive-queue. should be immediately followed by close().

int uds_listen(const char*); ///< creates blocking listening UDS
int uds_connect(const char*); ///< non-blocking connect to UDS

int socket_listen(in_port_t); ///< non-blocking any listening socket fd on given port or -1
int socket_accept(int, sockaddr_t&, sockaddr_t&);
int socket_connect(const sockaddr_t* src, const sockaddr_t* dst);

bool find_mac(const char*);