#include "final_index.h"

#include <search/base/yx_search.h>

#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/stream_messages.h>
#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/components/fullarchive/disk_manager.h>
#include <saas/rtyserver/components/special_keys/manager.h>
#include <saas/rtyserver/components/special_keys/const.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/search/common_search_request.h>
#include <saas/rtyserver/search/external_search/rty_index_data.h>
#include <saas/rtyserver/indexer_core/index_component_storage.h>
#include <saas/rtyserver/indexer_core/index_metadata_processor.h>
#include <saas/rtyserver/unistat_signals/signals.h>
#include <saas/util/logging/exception_process.h>

#include <search/request/data/reqdata.h>

#include <ysite/yandex/srchmngr/arcmgr.h>

#include <kernel/index_mapping/index_mapping.h>

#include <kernel/keyinv/hitlist/positerator.h>
#include <library/cpp/logger/global/global.h>

bool TFinalIndex::IsSearching() const {
    return Managers.GetIndexManager()->IsSearching();
}

ERequestType TFinalIndex::RequestType(const TCgiParameters& cgi) const {
    auto searcher = Managers.GetIndexManager()->GetSearcher();
    return searcher ? searcher->RequestType(cgi) : RT_Search;
}

TCommonSearch* TFinalIndex::GetCommonSearch() const {
    return Managers.GetIndexManager()->GetSearcher();
}

const IKeysAndPositions* TFinalIndex::GetIndexData() const {
    return GetYndex();
}

TAutoPtr<TPruningConfig::ICalcer> TFinalIndex::CreatePruningCalcer() const {
    return TIndexComponentsStorage::Instance().CreatePruningCalcer(&Managers);
}

ui32 TFinalIndex::RemoveKps(ui64 kps) {
    TVector<ui32> removingDocIds;
    if (Managers.GetIndexManager()->GetDocumentsByKeyPrefix(kps, removingDocIds)) {
        return RemoveDocIds(removingDocIds);
    } else {
        return 0;
    }
}

const IKeysAndPositions* TFinalIndex::GetYndex() const {
    auto searcher = dynamic_cast<TBaseSearch*>(Managers.GetIndexManager()->GetSearcher());
    return searcher ? &searcher->BaseIndexData->GetTextYR()->YMain() : nullptr;
}

NGroupingAttrs::TDocsAttrs* TFinalIndex::GetGroupsAttrs() const {
    auto searcher = dynamic_cast<TBaseSearch*>(Managers.GetIndexManager()->GetSearcher());
    return searcher ? const_cast<NGroupingAttrs::TDocsAttrs*>(searcher->BaseIndexData->GetDocsAttrs()) : nullptr;
}

void TFinalIndex::FillDocIds(TVector<ui32>& docIds) const {
    const ui32 count = GetDocumentsCount();
    docIds.reserve(count);
    for (ui32 i = 0; i < count; i++) {
        if (!IsRemoved(i))
            docIds.push_back(i);
    }
}

ui32 TFinalIndex::DecodeFromTempDocId(ui32 docIdTemp) const {
    VERIFY_WITH_LOG(Decoder.size(), "Incorrect DecodeFromTempDocId case usage");
    if (Decoder.size() <= docIdTemp || Decoder[docIdTemp] == REMAP_NOWHERE)
        return -1;
    return Decoder[docIdTemp];
}

ui32 TFinalIndex::DoRemoveDocIdsUnsafe(const TVector<ui32>& docIds) {
    DEBUG_LOG << "Patch removing for " << Directory().BaseName() << "/" << docIds.size() << Endl;
    ui32 result = Managers.RemoveDocids(docIds);
    Updater.RemoveDocs(docIds);
    DEBUG_LOG << "Patch removing for " << Directory().BaseName() << "/" << docIds.size() << "...OK" << Endl;
    return result;
}

ui32 TFinalIndex::MarkDocIdsForDeleteUnsafe(const TVector<ui32>& docIds, ui32 marker) {
    DEBUG_LOG << "Patch marking for " << IndexDir.BaseName() << "/" << marker << "/" << docIds.size() << Endl;
    ui32 result = Managers.MarkDocIdsForDeleteUnsafe(docIds, marker);
    {
        TWriteGuard g(MutexDocIdsByNextVersionSourceHash);
        for (auto&& i : docIds) {
            DocIdsByNextVersionSourceHash[marker].push_back(i);
        }
    }
    DEBUG_LOG << "Patch marking for " << IndexDir.BaseName() << "/" << marker << "...OK" << Endl;
    return result;
}

ui32 TFinalIndex::FixDocumentsCount(const ui32 deadlineCount) {
    const ui32 maxDocId = GetDocumentsCount(true);
    ui32 docId = 0;
    ui32 alive = 0;
    while (alive < deadlineCount && docId < maxDocId) {
        if (!IsRemoved(docId++)) {
            alive++;
        }
    }

    TVector<ui32> docIdsForRemove;
    docIdsForRemove.reserve(maxDocId - docId);
    for (; docId < maxDocId; ++docId) {
        docIdsForRemove.push_back(docId);
    }

    return RemoveDocIds(docIdsForRemove);
}

bool TFinalIndex::IsRemoved(const ui32 docid) const {
    TGuard<TMutex> g(MutexFrqWrite);
    return DocumentIsRemoved(docid);
}

const TPathName& TFinalIndex::Directory() const {
    return IndexDir;
}

void TFinalIndex::LinkIndexData() {
    if (IsLinked()) {
        return;
    }
    RTY_MEM_LOG("LinkIndexData for " + IndexDir.BaseName() + " start");
    TRY
        NOTICE_LOG << "TFinalIndex::LinkIndexData " << IndexDir.BaseName() << Endl;
        if (IndexType == EIndexType::PREPARED) {
            SetSearchable(false);
        }
        IsHasSearcher = Config.GetSearcherConfig().Enabled && IsSearchable();
        if (IsHasSearcher) {
            IndexData.Reset(new TRTYIndexData(Self, &Config.GetSearcherConfig(), IndexManager));
            IndexManager->LinkIndexData(*this, IndexData.Get());
        }
        TFinalIndexProcessor::LinkIndexData();
        RTY_MEM_LOG("LinkIndexData for " + IndexDir.BaseName() + " finished");
        if (Config.GetMergerConfig().MaxDeadlineDocs && (GetDocumentsCount() > Config.GetMergerConfig().MaxDeadlineDocs)) {
            // FIXME: FixDocumentsCount removes documents by ids, not by deadlines
            ui32 nRemoved = FixDocumentsCount(Config.GetMergerConfig().MaxDeadlineDocs);
            TSaasRTYServerSignals::UpdateSurplusDocCount(nRemoved, TInstant::Zero());
        }
        NextDeadline = RemoveOldDocuments(Now().Minutes());
        RTY_MEM_LOG("RemoveOldDocuments for " + IndexDir.BaseName() + " finished");
        return;
    CATCH("On TFinalIndex::LinkIndexData");
    AbortFromCorruptedIndex("TFinalIndex::LinkIndexData failed");
}

TFinalIndex::~TFinalIndex() {
    UnregisterGlobalMessageProcessor(this);
    if (IsStarted) {
        CHECK_WITH_LOG(Managers.IsOpened());
        UnRegisterIndex();
        if (Managers.GetIndexManager()->IsLinked()) {
            UnlinkSelfIndexData();
        }
    }
}

namespace {
    TIndexPrefetchResult PrefetchMappedIndex(const TFsPath& path, const TIndexFileEntry& file, bool lock, size_t limit) {
        TIndexPrefetchOptions ipo;
        ipo.Lower = file.Lower;
        ipo.Upper = file.Upper;
        if (lock)
            ipo.TryLock = true;
        else
            ipo.PrefetchLimit = limit;
        ipo.Deadline = TInstant::Max();
        return PrefetchMappedIndex(path, ipo);
    }

    TFsPath FormatIndexFilePath(const TPathName& indexDir, const TString& entryName) {
        return TFsPath(indexDir.PathName() + "/" + entryName).Fix();
    }

    void ParseAdditionalEntries(TStringBuf entriesStr, const TPathName& indexDir, TIndexFileEntries& result) {
        ParseIndexFileEntries(entriesStr, result);
        EraseIf(result, [&indexDir](TIndexFileEntry& file) {
            return !FormatIndexFilePath(indexDir, file.Name).Exists();
        });
    }
}

void TFinalIndex::PrefetchFiles(const TIndexFileEntries& files) const {
    size_t prefetched = 0;
    for (const TIndexFileEntry& file : files) {
        if (prefetched >= Config.GetSearcherConfig().PrefetchSizeBytes)
            break;
        const TFsPath path = FormatIndexFilePath(IndexDir, file.Name);
        AssertCorrectIndex(path.Exists(), "%s is missing, cannot prefetch: %s", path.Basename().data(), path.GetPath().data());

        prefetched += PrefetchMappedIndex(path, file, false, Config.GetSearcherConfig().PrefetchSizeBytes - prefetched).Prefetched;
    }
}

void TFinalIndex::LockFiles(const TIndexFileEntries& files) const {
    for (const TIndexFileEntry& file : files) {
        const TFsPath path = FormatIndexFilePath(IndexDir, file.Name);
        AssertCorrectIndex(path.Exists(), "%s is missing, cannot lock: %s", path.Basename().data(), path.GetPath().data());

        auto result = PrefetchMappedIndex(path, file, true, -1);
        if (result.Locked) {
            INFO_LOG << "Lock file " << path.GetPath() << " OK: " << result.Prefetched << " bytes" << Endl;
            LockedIndexFiles.push_back(path);
            AtomicAdd(LockedIndexFilesSize, result.Prefetched);
        } else {
            ERROR_LOG << "Lock file " << path.GetPath() << " FAIL" << Endl;
        }
    }
}

void TFinalIndex::Prefetch() const {
    if (Config.GetSearcherConfig().PrefetchSizeBytes || Config.GetSearcherConfig().LockIndexFiles) {
        const auto& components = TIndexComponentsStorage::Instance().GetComponents();
        TIndexFileEntries lockIndexFiles;
        TIndexFileEntries prefetchIndexFiles;
        ParseAdditionalEntries(Config.GetSearcherConfig().AdditionalLockedFiles, IndexDir, lockIndexFiles);
        ParseAdditionalEntries(Config.GetSearcherConfig().AdditionalPrefetchedFiles, IndexDir, prefetchIndexFiles);

        for (const auto& component: components) {
            PrefetchFilesGroup(component.first, *component.second, lockIndexFiles, prefetchIndexFiles);
        }
        LockFiles(lockIndexFiles);
        PrefetchFiles(prefetchIndexFiles);
    }
    Managers.Prefetch();
    RTY_MEM_LOG("FinalIndex " + IndexDir.BaseName() + " prefetched");
}

void TFinalIndex::PrefetchFilesGroup(const TStringBuf name, const NRTYServer::IIndexFilesGroup& component, TIndexFileEntries& lockIndexFiles, TIndexFileEntries& prefetchIndexFiles) const {
    const auto& indexFiles = component.GetIndexFiles();
    for (const auto& file : indexFiles) {
        TFsPath path = TFsPath(IndexDir.PathName() + "/" + file.Name).Fix();
        if (!path.Exists()) {
            INFO_LOG << name << ": file " << file.Name << " does not exist" << Endl;
            continue;
        }
        if (file.PrefetchPolicy == NRTYServer::IIndexComponent::TIndexFile::ppDisable) {
            continue;
        }

        TIndexFileEntry newEntry;
        newEntry.Name = file.Name;
        newEntry.Upper = file.PrefetchUpper;
        newEntry.Lower = file.PrefetchLower;

        auto policy = file.PrefetchPolicy;
        if (!Config.GetSearcherConfig().LockIndexFiles && policy == NRTYServer::IIndexComponent::TIndexFile::ppLock) {
            policy = NRTYServer::IIndexComponent::TIndexFile::ppPrefetch;
        }

        switch (policy) {
            case NRTYServer::IIndexComponent::TIndexFile::ppLock:
                lockIndexFiles.push_back(newEntry);
                INFO_LOG << "Adding " << path << " to memlock list" << Endl;
                break;
            case NRTYServer::IIndexComponent::TIndexFile::ppPrefetch:
                prefetchIndexFiles.push_back(newEntry);
                INFO_LOG << "Adding " << path << " to prefetch list" << Endl;
                break;
            default:
                FAIL_LOG("unexpected behaviour");
        }
    }
}

void TFinalIndex::UnlinkSelfIndexData() {
    for (auto&& i : LockedIndexFiles) {
        INFO_LOG << "Unlock index file " << i << Endl;
        ReleaseMappedIndexFile(i);
    }
    LockedIndexFiles.clear();
    AtomicSet(LockedIndexFilesSize, 0);
    if (Managers.GetIndexManager()->IsLinked()) {
        Managers.GetIndexManager()->UnlinkIndexData();
    }
    IsHasSearcher = false;
    IndexData.Reset(nullptr);
}

void TFinalIndex::UnlinkIndexData() {
     if (!IsLinked()) {
        return;
    }
    UnlinkSelfIndexData();
    TFinalIndexProcessor::UnlinkIndexData();
}

void TFinalIndex::Initialize() {
    TFinalIndexProcessor::Initialize();
    AssertCorrectIndex(TIndexComponentsStorage::Instance().AllRight(NRTYServer::TNormalizerContext(this, IndexDir, Managers, Config, IndexType)),
                       "%s index check failed for %s", Y_FUNC_SIGNATURE , IndexDir.PathName().data());

    const ui32 count = GetDocumentsCount();
    Managers.CheckDocumentsNumber(count);
    TWriteGuard g(MutexDocIdsByNextVersionSourceHash);
    for (ui32 i = 0; i < count; ++i) {
        ui32 src = DDKManager->GetSourceWithNewVersion(i);
        if (src && !IsRemoved(i)) {
            DocIdsByNextVersionSourceHash[src].push_back(i);
        }
    }

    LoadHistograms();
}

ui64 TFinalIndex::GetLockedMemorySize() const {
    return AtomicGet(LockedIndexFilesSize);
}

ui64 TFinalIndex::GetFilesSize() const {
    return FileSizes->GetTotalSize();
}

void TFinalIndex::Start() {
    NOTICE_LOG << "TFinalIndex for " << IndexDir.BaseName() << " starting..." << Endl;
    FileSizes.ConstructInPlace(Directory().PathName());

    RegisterGlobalMessageProcessor(this);
    RegisterIndex();
    IsStarted = true;
    NOTICE_LOG << "TFinalIndex for " << IndexDir.BaseName() << " started. DocumentsCount is equal to " << GetDocumentsCount(false) << Endl;
}

TFinalIndex::TFinalIndex(const TString& indexDir, const TRTYServerConfig& config, const TString& originDir, TVector<ui32>* decoder, NRTYServer::EExecutionContext execCtx)
    : TFinalIndexProcessor(TPathName{indexDir}, config, config.GetSearcherConfig().Enabled, execCtx)
    , Timestamp(indexDir)
    , Positions(indexDir)
    , OriginDir(originDir)
    , IsHasSearcher(false)
    , IsStarted(false)
    , NextDeadline(0)
    , Updater(*this, nullptr)
{
    IsMergedIndex = TFsPath(indexDir + "/source_indexes").Exists();
    if (decoder)
        Decoder = *decoder;
}

bool TFinalIndex::UpdateDoc(ui32 docId, const TParsedDocument::TPtr doc) {
    CHECK_WITH_LOG(Managers.IsOpened());
    DEBUG_LOG << "Update for url=" << doc->GetDocSearchInfo().GetUrl() << " index=" << Directory().PathName() << Endl;
    bool result = Managers.UpdateDoc(docId, doc);
    DEBUG_LOG << "Update for url=" << doc->GetDocSearchInfo().GetUrl() << " index=" << Directory().PathName() << "... OK" << Endl;
    return result;
}

void TFinalIndex::OnSearchActivated() {
    if (!IsMergedIndex && !!OriginDir) {
        NOTICE_LOG << "RemoveCatch: " << OriginDir << " ..." << Endl;
        SendGlobalMessage<TMessageRemoveCatchInfo>(IndexDir.BaseName());
        NOTICE_LOG << "RemoveCatch: " << OriginDir << " done" << Endl;
    }
    IIndexController::OnSearchActivated();
}

bool TFinalIndex::RestoreDoc(ui32 docId, TParsedDocument::TPtr& doc) {
    CHECK_WITH_LOG(Managers.IsOpened());
    const TDiskFAManager* fullArc = Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
    if (fullArc) {
        TParsedDocument::TPtr oldDoc = fullArc->GetDoc(docId);
        if (!!oldDoc) {
            doc = oldDoc;
            return true;
        }
    }
    return false;
}

TFinalIndex* TFinalIndex::Create(const TString& indexDir, const TRTYServerConfig& config, const TString& originDir, TVector<ui32>* decoder, NRTYServer::EExecutionContext execCtx) {
    TFinalIndex* result = new TFinalIndex(indexDir, config, originDir, decoder, execCtx);
    RTY_MEM_LOG("Final index construction, initialize " + result->Directory().BaseName() + " start");
    result->Initialize();
    RTY_MEM_LOG("Final index construction, initialize " + result->Directory().BaseName() + " finished");
    return result;
}

bool TFinalIndex::Process(IMessage* message) {
    TMessageCollectServerInfo* messCollect = dynamic_cast<TMessageCollectServerInfo*>(message);
    if (messCollect) {
        messCollect->AddDocsInFinalIndexes(GetDocumentsCount(false));

        messCollect->AddSizesInfo(*FileSizes);
        messCollect->AddLockedMemorySizeInBytes(GetLockedMemorySize());
        messCollect->AddIndexInfo(*this);
        const TDiskFAManager* fullArc = Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
        if (!!fullArc) {
            messCollect->AddMultipartArchiveInfo(fullArc->GetArchiveInfo());
            messCollect->AddLockedMemorySizeInBytes(fullArc->GetLockedMemorySize());
        }
        return true;
    }

    TMessageRemoveSpecial* messRemoveSpecial = dynamic_cast<TMessageRemoveSpecial*>(message);
    if (messRemoveSpecial) {
        if (messRemoveSpecial->GetType() == TMessageRemoveSpecial::rtRemoveKps)
            messRemoveSpecial->AddDocuments(RemoveKps(messRemoveSpecial->GetKps()));
        else if (messRemoveSpecial->GetType() == TMessageRemoveSpecial::rtRemoveAll) {
            messRemoveSpecial->AddDocuments(RemoveAll());
        }
        return true;
    }

    TMessageRemoveDeadDocuments* messRemove = dynamic_cast<TMessageRemoveDeadDocuments*>(message);
    if (messRemove) {
        if (NextDeadline <= messRemove->GetTimeInMinutesGM()) {
            NextDeadline = RemoveOldDocuments(messRemove->GetTimeInMinutesGM());
        } else {
            DEBUG_LOG << "NextDeadline == " << NextDeadline << " and > " << messRemove->GetTimeInMinutesGM() << Endl;
        }
        return true;
    }

    TMessageGetIndexTimestamp* messIndexTimestamp = dynamic_cast<TMessageGetIndexTimestamp*>(message);
    if (messIndexTimestamp) {
        messIndexTimestamp->Merge(*this);
        return true;
    }

    TMessageSetTimestamp* messSetTimestamp = dynamic_cast<TMessageSetTimestamp*>(message);
    if (messSetTimestamp) {
        Timestamp.Update(messSetTimestamp->Stream, messSetTimestamp->Timestamp);
        Timestamp.Set(messSetTimestamp->Stream, messSetTimestamp->Timestamp);
        Timestamp.Flush();
        return true;
    }

    TMessageGetIndexPositions* messIndexPositions = dynamic_cast<TMessageGetIndexPositions*>(message);
    if (messIndexPositions) {
        messIndexPositions->Merge(*this);
        return true;
    }

    TMessageRemoveCatchInfo* messRemoveCatch = dynamic_cast<TMessageRemoveCatchInfo*>(message);
    if (messRemoveCatch) {
        ui32 hashData = messRemoveCatch->GetHash();
        NOTICE_LOG << "RemoveCatchInfo message received for " << IndexDir.BaseName() << ", data = " << messRemoveCatch->GetTempName() << "/" << hashData << Endl;
        if (!messRemoveCatch->GetExcludedIndexes().contains(IndexDir.BaseName())) {
            TWriteGuard g(MutexDocIdsByNextVersionSourceHash);
            auto it = DocIdsByNextVersionSourceHash.find(hashData);
            if (it != DocIdsByNextVersionSourceHash.end()) {
                TVector<ui32> docIds = it->second;
                DocIdsByNextVersionSourceHash.erase(it);
                g.Release();
                RemoveDocIds(docIds);
            }
        } else {
            NOTICE_LOG << "Index " << IndexDir.BaseName() << " excluded for remove cached data = " << messRemoveCatch->GetTempName() << "/" << hashData << Endl;
        }
        return true;
    }

    return false;
}

TString TFinalIndex::Name() const {
    return TString("FinalIndex: ") + IndexDir.BaseName();
}

IIndexController::EIndexType TFinalIndex::GetType() const {
    CHECK_WITH_LOG(IndexType == IIndexController::EIndexType::FINAL || IndexType == IIndexController::EIndexType::PREPARED); //expectation of the callers
    return IndexType;
}

NRTYServer::ERealm TFinalIndex::GetRealm() const {
    return TFinalIndexProcessor::GetRealm();
}

TString TFinalIndex::GetRealmName() const {
    return TFinalIndexProcessor::GetRealmName();
}

TCommonSearch* TFinalIndex::GetSearcher() {
    return Managers.GetIndexManager()->GetSearcher();
}

IIndexUpdater* TFinalIndex::GetUpdater() {
    return &Updater;
}

void TFinalIndex::SetDeferredUpdatesStorage(TDeferredUpdatesStorage::TPtr storage) {
    INFO_LOG << "Deferred updates storage initialized for " << Directory().PathName() << Endl;
    Updater.SetDeferredUpdatesStorage(storage);
}

bool TFinalIndex::RemapUrl2DocIdCandidate(const TDocSearchInfo& docInfo, TDocIdCandidate& docId) const {
     VERIFY_WITH_LOG(UrlToDocIdManager, "Invalid usage of UrlToDocIdManager : %s", Config.UrlToDocIdManager.c_str());
     auto res = UrlToDocIdManager->GetIdCandidateByDocInfo(docInfo);
     if (!res.IsDocIdSet()) {
         return false;
     }
     docId = res;
     return true;
}

bool TFinalIndex::RemapUrl2DocId(const TDocSearchInfo& docInfo, ui32& docId) const {
     VERIFY_WITH_LOG(UrlToDocIdManager, "Invalid usage of UrlToDocIdManager : %s", Config.UrlToDocIdManager.c_str());
     ui32 res = UrlToDocIdManager->GetIdByDocInfo(docInfo);
     if (res == UrlToDocIdManager->NotFound) {
         return false;
     }
     docId = res;
     return true;
}

bool TFinalIndex::RemapIdentifier2DocId(const TDocSearchInfo::THash& identifier, ui32& docId) const {
    VERIFY_WITH_LOG(UrlToDocIdManager, "Invalid usage of UrlToDocIdManager : %s", Config.UrlToDocIdManager.c_str());
     ui32 res = UrlToDocIdManager->GetIdByHash(identifier);
     if (res == UrlToDocIdManager->NotFound) {
         return false;
     }
     docId = res;
     return true;
}

bool TFinalIndex::GetDocInfo(const ui32 docId, NJson::TJsonValue& result) const {
    if (docId >= GetDocumentsCount()) {
        return false;
    }
    if (!GetManagers().GetDocInfo(docId, result)) {
        return false;
    }
    {
        THolder<TPruningConfig::ICalcer> calcer(TIndexComponentsStorage::Instance().CreatePruningCalcer(&Managers));
        if (calcer) {
            result["GLOBAL"].InsertValue("pruning_rank", calcer->PruningRank(docId));
        }
    }
    return true;
}

void TFinalIndex::LoadHistograms() try {
    const auto file = TFsPath(Directory().PathName()) / "indexhistogram";
    if (NFs::Exists(file)) {
        Histograms.Deserialize(file);
        INFO_LOG << "Loaded histograms from " << file << Endl;
    }
} catch (...) {
    ERROR_LOG << "Cannot load histograms: " << CurrentExceptionMessage() << Endl;
}

ui32 TFinalIndex::GetDocsCountInInterval(const TInterval<double>& interval) const
{
    if (Config.Pruning) {
        auto full = GetPruneInterval();
        TInterval<double> cross;
        if (full.Intersection(interval, cross)) {
            if (full.GetLength() < 1e-10) {
                return GetDocumentsCount();
            } else {
                return GetDocumentsCount() / full.GetLength() * cross.GetLength();
            }
        } else {
            return 0;
        }
    } else {
        return GetDocumentsCount();
    }
}

ui32 TFinalIndex::GetState() const {
    return Managers.HasIndexManager() ? Managers.GetIndexManager()->GetState() : 0;
}

TInterval<double> TFinalIndex::GetPruneInterval() const
{
    if (Config.Pruning) {
        THolder<TPruningConfig::ICalcer> calcer(TIndexComponentsStorage::Instance().CreatePruningCalcer(&Managers));
        double minInt = calcer->PruningRank(GetDocumentsCount() - 1);
        double maxInt = calcer->PruningRank(0);
        if (minInt <= maxInt) {
            return TInterval<double>(minInt, maxInt);
        } else {
            return TInterval<double>(0, Max<double>());
        }
    } else {
        return TInterval<double>(0, Max<double>());
    }
}
