#include "standalone_indexer.h"
#include "merge_move_task.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/indexer_disk/indexer.h>
#include <saas/rtyserver/merger/index_merger.h>
#include <saas/rtyserver/merger/m2n_decoder.h>
#include <saas/util/logging/exception_process.h>
#include <saas/library/mapping/mapping.h>

#include <util/generic/yexception.h>
#include <util/folder/filelist.h>

struct TDaemonModulesRunner {
    TDaemonModulesRunner(const IServerConfig& serverConfig)
        : Modules(serverConfig)
    {
        Modules.Start(StartContext);
    }
    ~TDaemonModulesRunner() {
        try {
            Modules.Stop(StopContext);
        } catch (...) {
            ERROR_LOG << "Cannot stop modules properly: " << CurrentExceptionMessage() << Endl;
        }
    }

private:
    IDaemonModule::TStartContext StartContext;
    IDaemonModule::TStopContext StopContext{0, nullptr};
    TDaemonModules Modules;
};

TStandaloneIndexer::TStandaloneIndexer(const TString& textOfRTYServerConfig, const THashMap<TString, TString>& configVariables, bool runModules)
    : Config(ParseRtyConfig(textOfRTYServerConfig, configVariables))
    , IndexStorage(TIndexStorage::Create(TIndexStorageConstructionContext(*Config)))
{
    Y_VERIFY(Config->IsReadOnly == false);
    NRTYServer::EnableGlobalMapping(Config->IsReadOnly);


    if (runModules && !Config->GetModulesVector().empty()) {
        Modules = MakeHolder<TDaemonModulesRunner>(*Config);
    }
    IndexStorage->Init();
}

TStandaloneIndexer::~TStandaloneIndexer() {
    if (IndexingEnabled()) {
        DisableIndexing();
    }
    if (MergingEnabled()) {
        DisableMerging();
    }
    TIndexComponentsStorage::Instance().ReleaseComponents();
    IndexStorage->Stop();
}

void TStandaloneIndexer::EnableIndexing() {
    CHECK_WITH_LOG(!IndexingEnabled());
    CHECK_WITH_LOG(!MergingEnabled());
    TString indexDirName, tempDirName;
    const NRTYServer::TRealmConfig& mainRealmConfig = Config->GetRealmListConfig().GetMainRealmConfig();
    IndexStorage->AllocateIndex(indexDirName, tempDirName, 0, true, mainRealmConfig.GetIndexerConfigDisk().PreparatesMode);
    Indexer.Reset(new TIndexer(indexDirName, tempDirName, mainRealmConfig.RealmDirectory, mainRealmConfig.GetIndexerConfigDisk(), *IndexStorage, 0));
}

void TStandaloneIndexer::DisableIndexing() {
    CHECK_WITH_LOG(IndexingEnabled());
    Indexer->CloseIndex(nullptr, NRTYServer::EExecutionContext::Close);
    Indexer.Destroy();
}

bool TStandaloneIndexer::IndexingEnabled() const {
    return !!Indexer;
}

TIndexResult TStandaloneIndexer::Index(TParsedDocument& document) {
    CHECK_WITH_LOG(IndexingEnabled());
    try {
        return Indexer->Index(document);
    } catch (...) {
        ERROR_LOG << "Error while indexing TParsedDocument " << document.GetDocSearchInfo().GetUrl() << ", kps=" << document.GetDocSearchInfo().GetKeyPrefix() << ": " << CurrentExceptionMessage();
        return CurrentExceptionMessage();
    }
}

TIndexResult TStandaloneIndexer::Index(const NRTYServer::TMessage::TDocument &msgDoc) {
    try {
        TParsedDocument parsedDocument(*Config);
        NRTYServer::TDocParseContext c;
        TIndexComponentsStorage::Instance().GetDocumentParser().Parse(parsedDocument, msgDoc,
            NRTYServer::TMessage::ADD_DOCUMENT, c);
        return Index(parsedDocument);
    } catch (...) {
        ERROR_LOG << "Error while parsing NRTYServer::TMessage " << msgDoc.GetUrl() << ", kps=" << msgDoc.GetKeyPrefix() << ": " << CurrentExceptionMessage();
        return CurrentExceptionMessage();
    }
}

void TStandaloneIndexer::IndexUnsafe(TParsedDocument& document) {
    CHECK_WITH_LOG(IndexingEnabled());
    Y_ENSURE(Indexer->Index(document), "Cannot index document " << document.GetDocSearchInfo().GetUrl() << ", kps=" << document.GetDocSearchInfo().GetKeyPrefix());
}

void TStandaloneIndexer::IndexUnsafe(const NRTYServer::TMessage::TDocument &msgDoc) {
    TParsedDocument parsedDocument(*Config);
    NRTYServer::TDocParseContext c;
    TIndexComponentsStorage::Instance().GetDocumentParser().Parse(parsedDocument, msgDoc,
        NRTYServer::TMessage::ADD_DOCUMENT, c);
    IndexUnsafe(parsedDocument);
}

void TStandaloneIndexer::RemoveDoc(const TParsedDocument& document) {
    CHECK_WITH_LOG(IndexingEnabled());
    ui32 docIdToRemove = Max<ui32>();
    Indexer->RemapUrl2DocId(document.GetDocSearchInfo(), docIdToRemove);
    Indexer->RemoveDocIds(TVector<ui32>(1, docIdToRemove));
}

TParseResult TStandaloneIndexer::GetParsedDocument(const NRTYServer::TMessage::TDocument &msgDoc, const NRTYServer::TDocParseContext& c) const {
    try {
        THolder<TParsedDocument> result = MakeHolder<TParsedDocument>(*Config);
        TIndexComponentsStorage::Instance().GetDocumentParser().Parse(*result, msgDoc, NRTYServer::TMessage::ADD_DOCUMENT, c);
        return result;
    } catch (...) {
        ERROR_LOG << "Error while parsing NRTYServer::TMessage " << msgDoc.GetUrl() << ", kps=" << msgDoc.GetKeyPrefix()
                << ": " << CurrentExceptionMessage();
        return CurrentExceptionMessage();
    }
}


const TString& TStandaloneIndexer::GetIndexerDir() const {
    CHECK_WITH_LOG(IndexingEnabled());
    return Indexer->Dir.PathName();
}

void TStandaloneIndexer::EnableMerging() {
    CHECK_WITH_LOG(!IndexingEnabled());
    CHECK_WITH_LOG(!MergingEnabled());
    Merger.Reset(new TIndexMerger(*IndexStorage, *Config));
}

void TStandaloneIndexer::DisableMerging() {
    CHECK_WITH_LOG(MergingEnabled());
    Merger.Destroy();
}

bool TStandaloneIndexer::MergingEnabled() const {
    return !!Merger;
}

TVector<TString> TStandaloneIndexer::GetSegmentsForMerge() {
    CHECK_WITH_LOG(MergingEnabled());
    const ui32 maxDocsToMerge = Config->GetMergerConfig().MaxDocumentsToMerge;
    TDirsList dl;
    dl.Fill(Config->GetRealmListConfig().GetMainRealmConfig().RealmDirectory);
    TVector<TString> segments;
    ui32 docsToMerge = 0;
    while (TString segmentDir = dl.Next()) {
        TIndexControllerPtr index = IndexStorage->GetIndexController(segmentDir);
        //VERIFY_WITH_LOG(!!index, "Cannot get IndexController for %s", ~segmentDir);
        if (!index) {
            continue;
        }
        const ui32 docsCount = index->GetDocumentsCount(false);
        if (docsToMerge + docsCount > maxDocsToMerge) {
            continue;   // try next one, probably it's small enough to fit
        }
        docsToMerge += docsCount;
        segments.push_back(segmentDir);
    }
    return segments;
}

void TStandaloneIndexer::Merge() {
    while (TVector<TString> segments = GetSegmentsForMerge()) {
        if (segments.size() == 0) {
            break;
        }
        TString destDirName, tempDirName;
        IndexStorage->AllocateIndex(destDirName, tempDirName, 0, true);
        MergeTo(std::move(segments), Config->IndexDir, destDirName);
    }
}

void TStandaloneIndexer::MergeTo(TVector<TString> sourceSegments, TString destDirectory, TString destSegment) {
    CHECK_WITH_LOG(MergingEnabled());
    try {
        IMergerTask::TPtr taskPtr = new TMergeMoveTask(*Config, *IndexStorage, std::move(sourceSegments), destDirectory, destSegment);
        Merger->DoTask(taskPtr, nullptr);
    } catch (yexception& e) {
        e << " in TStandaloneIndexer::MergeTo for " << destDirectory << "/" << destSegment;
        throw;
    }
}
