#include "async_entry.h"

#include <util/string/cast.h>


namespace NSrvKernel::NDns {

TEntry::TEntry(TContExecutor* e, TDuration recordTtl)
    : RecordTtl_(recordTtl)
    , State_(RS_NOT_RESOLVED)
    , ResolvedEvent_(e)
{
}

void TEntry::AddRequestInProgress() noexcept
{
    Y_ASSERT(State_ == RS_IN_PROGRESS || State_ == RS_RESOLVED);

    ++RequestsInProgress_;
}

bool TEntry::SetFailedIfDone() noexcept
{
    Y_ASSERT(State_ != RS_NOT_RESOLVED);

    if (!RequestsInProgress_ || !(--RequestsInProgress_)) { // 0 can be when AddRequestInProgress() was not called
        if (State_ != RS_RESOLVED) {
            Y_ASSERT(Addrs_.Addresses.empty());
            State_ = RS_FAILED;
            ResolveTimestamp_ = TInstant::Now();
        }
        ResolvedEvent_.BroadCast();
        return true;
    }
    return false;
}

void TEntry::AddResult(TSockAddr&& result) noexcept try
{
    Y_ASSERT(State_ == RS_IN_PROGRESS || State_ == RS_RESOLVED);

    if (!result.Addr()) {
        return; // no new data, do nothing
    }
    result.SetPort(0); // don't store ports to avoid duplicates

    if (!Addrs_.Addresses.empty() && IsExpired(RecordTtl_)) {
        Addrs_.Addresses.clear(); // new data received, remove obsolete
    }

    Y_ASSERT(result.Addr()->sa_family == AF_INET6 || result.Addr()->sa_family == AF_INET);
    if (Addrs_.Addresses.empty()) {
        Addrs_.Addresses.push_back(std::move(result));
    } else {
        bool alreadyPresent = false;
        auto insertPos = Addrs_.Addresses.cend();
        for (auto iter = Addrs_.Addresses.cbegin(); iter != Addrs_.Addresses.cend(); ++iter) {
            if (result == *iter) {
                alreadyPresent = true;
                break;
            }
            if (result.Addr()->sa_family == AF_INET6 && insertPos == Addrs_.Addresses.end()) {
                Y_ASSERT(iter->Addr()->sa_family == AF_INET6 || iter->Addr()->sa_family == AF_INET);
                if (iter->Addr()->sa_family == AF_INET) {
                    insertPos = iter; // insert before first IPv4
                }
            }
        }
        if (!alreadyPresent) {
            Addrs_.Addresses.insert(insertPos, std::move(result));
        }
    }

    State_ = RS_RESOLVED;
    ResolveTimestamp_ = TInstant::Now();
    ResolvedEvent_.BroadCast();
} catch(...) {
}

bool TEntry::WaitD(TInstant deadline, int& waitResultCode) noexcept
{
    Y_ASSERT(State_ == RS_IN_PROGRESS);

    if (!RequestsInProgress_ || !Addrs_.Addresses.empty()) {
        Y_ASSERT(false);
        if (Addrs_.Addresses.empty()) {
            waitResultCode = ECANCELED; // nothing to wait
            return false;
        } else {
            waitResultCode = EWAKEDUP; // already has result
            return true;
        }
    }

    waitResultCode = ResolvedEvent_.WaitD(deadline);

    return waitResultCode == EWAKEDUP;
}

void TEntry::SetInProgress() noexcept
{
    Y_ASSERT(State_ == RS_NOT_RESOLVED);
    Y_ASSERT(!RequestsInProgress_ && Addrs_.Addresses.empty());

    State_ = RS_IN_PROGRESS;
}

bool TEntry::ResetFailedIfExpired(TDuration ttl) noexcept
{
    Y_ASSERT(!RequestsInProgress_ && Addrs_.Addresses.empty());

    if (State_ != RS_FAILED) { // just in case
        return false;
    }

    if (IsExpired(ttl)) {
        State_ = RS_NOT_RESOLVED;
        return true;
    }
    return false;
}


// ----------------------------------------------------------------------------
// TEntryBuilder
//
TEntryHolder TEntryBuilder::GetNewEntry(TContExecutor* e, TDuration ttl) const
{
    return MakeHolder<TEntry>(e, ttl);
}


}