#ifndef TILE_BASE_NET_ENDPOINT_H
#define TILE_BASE_NET_ENDPOINT_H

#pragma once

#include "tile/base/expected.h"
#include "tile/base/likely.h"
#include "tile/base/logging.h"
#include "tile/base/optional.h"
#include "tile/base/slice.h"

#include <netinet/in.h>
#include <sys/socket.h>

#include <cstring>
#include <ostream>
#include <string>
#include <vector>

namespace tile {
class Endpoint;

class EndpointRetriever {
public:
    sockaddr *RetrieveAddr();
    socklen_t *RetrieveLength();
    Endpoint Build();

private:
    sockaddr_storage storage_;
    socklen_t length_ = sizeof(sockaddr_storage);
};

class alignas(alignof(sockaddr_storage)) Endpoint {
public:
    Endpoint() : length_(0) {}

    ~Endpoint();

    Endpoint(const Endpoint &ep);
    Endpoint &operator=(const Endpoint &ep);
    Endpoint(Endpoint &&ep) noexcept;
    Endpoint &operator=(Endpoint &&ep) noexcept;

    bool Empty() const noexcept { return length_ == 0; }

    const sockaddr *Get() const noexcept
    {
        if (TILE_LIKELY(IsTriviallyCopyable())) { return reinterpret_cast<const sockaddr *>(&storage_); }
        return SlowGet();
    }

    template<typename T>
    const T *UnsafeGet() const noexcept
    {
        return reinterpret_cast<const T *>(Get());
    }

    socklen_t Length() const noexcept { return length_; }

    sa_family_t Family() const noexcept { return Get()->sa_family; }

    std::string ToString() const;

private:
    friend class EndpointRetriever;
    Endpoint(const sockaddr *addr, socklen_t len);

    bool IsTriviallyCopyable() const { return length_ <= kOptimizedSize; }

    void SlowDestroy();
    void SlowCopyUninitialized(const Endpoint &ep);
    void SlowCopy(const Endpoint &ep);
    const sockaddr *SlowGet() const;

private:
    static constexpr auto kOptimizedSize = sizeof(sockaddr_in6);

    // Storaege MUST be the first element of Endpoint
    using Storage = typename std::aligned_storage<kOptimizedSize, 1>::type;

    Storage storage_;
    socklen_t length_;
};

bool operator==(const Endpoint &lhs, const Endpoint &rhs);
Endpoint EndpointFromIpv4(const std::string &ip, std::uint16_t port);
Endpoint EndpointFromIpv6(const std::string &ip, std::uint16_t port);
Endpoint EndpointFromUnix(const std::string &path);

std::string EndpointGetIp(const Endpoint &ep);
std::uint16_t EndpointGetPort(const Endpoint &ep);

std::vector<Endpoint> GetInterfaceAddresses();

std::expected<std::vector<Endpoint>, int> ResolveDomain(const std::string &domain, std::uint16_t port = 0);

bool IsPrivateIpv4AddressRfc(const Endpoint &addr);
bool IsPrivateIpv4AddressCorp(const Endpoint &addr);

bool IsGuaIpv6Address(const Endpoint &addr);

struct from_ipv4_t {
    constexpr explicit from_ipv4_t() = default;
};

struct from_ipv6_t {
    constexpr explicit from_ipv6_t() = default;
};

struct from_unix_t {
    constexpr explicit from_unix_t() = default;
};

constexpr from_ipv4_t from_ipv4 = from_ipv4_t();
constexpr from_ipv6_t from_ipv6 = from_ipv6_t();
constexpr from_unix_t from_unix = from_unix_t();

std::ostream &operator<<(std::ostream &os, const Endpoint &ep);

inline Endpoint::~Endpoint()
{
    if (TILE_LIKELY(IsTriviallyCopyable())) { return; }
    SlowDestroy();
}

inline Endpoint::Endpoint(const Endpoint &ep)
{
    if (TILE_LIKELY(ep.IsTriviallyCopyable())) {
        memcpy(reinterpret_cast<void *>(this), reinterpret_cast<const void *>(&ep), sizeof(Endpoint));
    } else {
        SlowCopyUninitialized(ep);
    }
}

inline Endpoint &
Endpoint::operator=(const Endpoint &ep)
{
    if (TILE_LIKELY(ep.IsTriviallyCopyable())) {
        memcpy(reinterpret_cast<void *>(this), reinterpret_cast<const void *>(&ep), sizeof(Endpoint));
    } else {
        SlowCopy(ep);
    }
    return *this;
}

inline Endpoint::Endpoint(Endpoint &&ep) noexcept
{
    if (TILE_LIKELY(ep.IsTriviallyCopyable())) {
        memcpy(reinterpret_cast<void *>(this), reinterpret_cast<void *>(&ep), sizeof(Endpoint));
    } else {
        SlowCopyUninitialized(ep);
    }
}

inline Endpoint &
Endpoint::operator=(Endpoint &&ep) noexcept
{
    if (TILE_LIKELY(ep.IsTriviallyCopyable())) {
        memcpy(reinterpret_cast<void *>(this), reinterpret_cast<void *>(&ep), sizeof(Endpoint));
    } else {
        SlowCopy(ep);
    }
    return *this;
}

template<typename T, typename>
struct TryParseTraits;

template<>
struct TryParseTraits<Endpoint, void> {
    static std::optional<Endpoint> TryParse(Slice s, from_ipv4_t);
    static std::optional<Endpoint> TryParse(Slice s, from_ipv6_t);
    static std::optional<Endpoint> TryParse(Slice s, from_unix_t);
    static std::optional<Endpoint> TryParse(Slice s);
};

Endpoint EndpointFromString(Slice ip_port);

}// namespace tile

#endif// TILE_BASE_NET_ENDPOINT_H
