#pragma once
#include <ares.h>
#include <iosfwd>
#include <panda/refcnt.h>
#include <panda/net/sockaddr.h>

namespace panda { namespace unievent {

// addrinfo extract, there are fields needed for hinting only
struct AddrInfoHints {
    static constexpr const int CANONNAME   = ARES_AI_CANONNAME;
    static constexpr const int NUMERICSERV = ARES_AI_NUMERICSERV;

    AddrInfoHints (int family = AF_UNSPEC, int socktype = 0, int proto = 0, int flags = 0) :
        family(family), socktype(socktype), protocol(proto), flags(flags) {}

    AddrInfoHints (const AddrInfoHints& oth) = default;

    bool operator== (const AddrInfoHints& oth) const {
        return family == oth.family && socktype == oth.socktype && protocol == oth.protocol && flags == oth.flags;
    }

    int family;
    int socktype;
    int protocol;
    int flags;
};

struct AddrInfo {
    AddrInfo ()                  : cur(nullptr) {}
    AddrInfo (ares_addrinfo* ai) : src(new DataSource(ai)), cur(ai ? ai->nodes : nullptr) {}

    int           flags     () const { return cur->ai_flags; }
    int           family    () const { return cur->ai_family; }
    int           socktype  () const { return cur->ai_socktype; }
    int           protocol  () const { return cur->ai_protocol; }
    net::SockAddr addr      () const { return net::SockAddr(cur->ai_addr, cur->ai_addrlen); }
    string_view   canonname () const { return (src->ai && src->ai->cnames) ? src->ai->cnames->name : ""; }
    int           ttl       () const { return cur->ai_ttl; }
    AddrInfo      next      () const { return cur ? AddrInfo(src, cur->ai_next) : AddrInfo{}; }
    AddrInfo      first     () const { return AddrInfo(src, src->ai->nodes); }

    explicit operator bool () const { return cur; }

    bool operator== (const AddrInfo& oth) const;
    bool operator!= (const AddrInfo& oth) const { return !operator==(oth); }

    bool is (const AddrInfo& oth) const { return cur == oth.cur; }

    std::string to_string ();

private:
    struct DataSource : Refcnt {
        ares_addrinfo* ai;
        DataSource (ares_addrinfo* ai) : ai(ai) {}
        ~DataSource () { if (ai) ares_freeaddrinfo(ai); }
    };

    iptr<DataSource> src;
    ares_addrinfo_node*  cur;

    AddrInfo (const iptr<DataSource>& src, ares_addrinfo_node*  nodes) : src(src), cur(nodes) {}
};

std::ostream& operator<< (std::ostream&, const AddrInfo&);
std::ostream& operator<< (std::ostream&, const AddrInfoHints&);

}}