#include "its_switch.h"

#include "async_resolver.h"
#include "resolver.h"

#include <balancer/kernel/fs/shared_files.h>


namespace NSrvKernel::NDns {

namespace {
const char ItsSetAsync    = '1';
const char ItsSetNotAsync = '0';
}

// ----------------------------------------------------------------------------
// TModeChecker
//
class TModeChecker : public IModeChecker {
public:
    TModeChecker(const TResolverConfig& config, TSharedFiles* sharedFiles);

    ECurrentMode GetCurrentMode() override;

private:
    TSharedFileReReader ModeFileReader_;
    TSharedFileReReader::TData ModeFileData_;
    ECurrentMode CurrentMode_ = ECurrentMode::NotSet;
};

TModeChecker::TModeChecker(const TResolverConfig& config, TSharedFiles* sharedFiles)
{
    Y_ASSERT(sharedFiles);
    Y_ASSERT(!config.SwitchDnsFile.Empty());
    Y_ASSERT(config.SwitchDnsCheckTimeout != TDuration::Max() && config.SwitchDnsCheckTimeout != TDuration::Zero());

    ModeFileReader_ = sharedFiles->FileReReader(config.SwitchDnsFile, config.SwitchDnsCheckTimeout);
}

TModeChecker::ECurrentMode TModeChecker::GetCurrentMode()
{
    const auto& data = ModeFileReader_.Data();
    if (data.Id() != ModeFileData_.Id() && data.Data().Size() > 0) {
        if (data.Data()[0] == ItsSetAsync) {
            ModeFileData_ = data;
            CurrentMode_ = ECurrentMode::Async;
        } else if (data.Data()[0] == ItsSetNotAsync) {
            ModeFileData_ = data;
            CurrentMode_ = ECurrentMode::Threaded;
        } else {
            Y_ASSERT(false);
        }
    }

    return CurrentMode_;
}

// ----------------------------------------------------------------------------
// TItsSwitch
//
TItsSwitch::TItsSwitch(THolder<IResolver>&& async, THolder<IResolver>&& threaded,
                       THolder<IModeChecker>&& checker, const TResolverConfig& config)
    : AsyncResolver_(std::move(async))
    , Resolver_(std::move(threaded))
    , Checker_(std::move(checker))
{
    if (config.UseAsync) {
        Current_ =  AsyncResolver_.Get();
    } else {
        Current_ = Resolver_.Get();
    }
}

TItsSwitch::~TItsSwitch()
{
}

TErrorOr<TSockAddrInfo> TItsSwitch::DoResolve(const TResolveInfo& info, bool onlyCache, TInstant deadline)
{
    Y_ASSERT(AsyncResolver_ && Resolver_ && Current_);

    SetMode();

    return Current_->Resolve(info, onlyCache, deadline);
}

void TItsSwitch::DoResetCache()
{
    Y_ASSERT(AsyncResolver_ && Resolver_ && Current_);

    AsyncResolver_->ResetCache();
    Resolver_->ResetCache();
}

void TItsSwitch::SetMode() noexcept
{
    Y_ASSERT(AsyncResolver_ && Resolver_ && Current_);
    switch(Checker_->GetCurrentMode())
    {
        case IModeChecker::ECurrentMode::Async:
            Current_ = AsyncResolver_.Get();
            break;
        case IModeChecker::ECurrentMode::Threaded:
            Current_ = Resolver_.Get();
            break;
        default:
            break;
    }
}

THolder<TItsSwitch> MakeItsSwitchResolver(TContExecutor* executor, TStatsCounters& counters,
        TSharedFiles* sharedFiles, const TResolverConfig& config, TThreadedQueue* queue)
{
    Y_ASSERT(executor);

    auto async = MakeHolder<TAsyncResolver>(executor, counters, config);
    auto threaded = MakeHolder<TContResolver>(executor, counters, queue, config.DnsTimeout, config.RecordTtl);
    auto checker = MakeHolder<TModeChecker>(config, sharedFiles);

    return MakeHolder<TItsSwitch>(std::move(async), std::move(threaded), std::move(checker), config);
}

}