#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/SlotImp.h>
#include <xrpld/peerfinder/detail/Tuning.h>

namespace xrpl {
namespace PeerFinder {

SlotImp::SlotImp(
    beast::IP::Endpoint const& local_endpoint,
    beast::IP::Endpoint const& remote_endpoint,
    bool fixed,
    clock_type& clock)
    : recent(clock)
    , m_inbound(true)
    , m_fixed(fixed)
    , m_reserved(false)
    , m_state(accept)
    , m_remote_endpoint(remote_endpoint)
    , m_local_endpoint(local_endpoint)
    , m_listening_port(unknownPort)
    , checked(false)
    , canAccept(false)
    , connectivityCheckInProgress(false)
{
}

SlotImp::SlotImp(beast::IP::Endpoint const& remote_endpoint, bool fixed, clock_type& clock)
    : recent(clock)
    , m_inbound(false)
    , m_fixed(fixed)
    , m_reserved(false)
    , m_state(connect)
    , m_remote_endpoint(remote_endpoint)
    , m_listening_port(unknownPort)
    , checked(true)
    , canAccept(true)
    , connectivityCheckInProgress(false)
{
}

void
SlotImp::state(State state_)
{
    // Must go through activate() to set active state
    XRPL_ASSERT(state_ != active, "xrpl::PeerFinder::SlotImp::state : input state is not active");

    // The state must be different
    XRPL_ASSERT(
        state_ != m_state,
        "xrpl::PeerFinder::SlotImp::state : input state is different from "
        "current");

    // You can't transition into the initial states
    XRPL_ASSERT(
        state_ != accept && state_ != connect,
        "xrpl::PeerFinder::SlotImp::state : input state is not an initial");

    // Can only become connected from outbound connect state
    XRPL_ASSERT(
        state_ != connected || (!m_inbound && m_state == connect),
        "xrpl::PeerFinder::SlotImp::state : input state is not connected an "
        "invalid state");

    // Can't gracefully close on an outbound connection attempt
    XRPL_ASSERT(
        state_ != closing || m_state != connect,
        "xrpl::PeerFinder::SlotImp::state : input state is not closing an "
        "invalid state");

    m_state = state_;
}

void
SlotImp::activate(clock_type::time_point const& now)
{
    // Can only become active from the accept or connected state
    XRPL_ASSERT(
        m_state == accept || m_state == connected,
        "xrpl::PeerFinder::SlotImp::activate : valid state");

    m_state = active;
    whenAcceptEndpoints = now;
}

//------------------------------------------------------------------------------

Slot::~Slot() = default;

//------------------------------------------------------------------------------

SlotImp::recent_t::recent_t(clock_type& clock) : cache(clock)
{
}

void
SlotImp::recent_t::insert(beast::IP::Endpoint const& ep, std::uint32_t hops)
{
    auto const result(cache.emplace(ep, hops));
    if (!result.second)
    {
        // NOTE Other logic depends on this <= inequality.
        if (hops <= result.first->second)
        {
            result.first->second = hops;
            cache.touch(result.first);
        }
    }
}

bool
SlotImp::recent_t::filter(beast::IP::Endpoint const& ep, std::uint32_t hops)
{
    auto const iter(cache.find(ep));
    if (iter == cache.end())
        return false;
    // We avoid sending an endpoint if we heard it
    // from them recently at the same or lower hop count.
    // NOTE Other logic depends on this <= inequality.
    return iter->second <= hops;
}

void
SlotImp::recent_t::expire()
{
    beast::expire(cache, Tuning::liveCacheSecondsToLive);
}

}  // namespace PeerFinder
}  // namespace xrpl
