#include "makeup_component.h"
#include "makeup_builder.h"
#include "makeup_saver.h"
#include "makeup_parsed_entity.h"
#include "read_only_makeup_manager.h"
#include "sequential_write_makeup_manager.h"

#include <kernel/keyinv/indexfile/oldindexfile.h>
#include <library/cpp/logger/global/global.h>
#include <library/cpp/balloc/optional/operators.h>

#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/merger/library/file_block_merger.h>

namespace {
    bool IsUsed(const TRTYServerConfig& config) {
        return (config.IndexGenerator == INDEX_COMPONENT_NAME) && (
               (!!config.GetSearcherConfig().Factors && config.GetSearcherConfig().Factors->GetZoneFactors().size())
            || (config.ComponentsSet.contains(MAKEUP_COMPONENT_NAME))
            || (config.GetSearcherConfig().SnippetsDeniedZonesVector.ysize())
            );
    }
}

NRTYMerger::IHeader::TPtr TRTYMakeupIndexComponent::BuildDefaultHeader() const {
    return new TZonesDescription(Config);
}

NRTYMerger::IObject::TPtr TRTYMakeupIndexComponent::BuildDefaultObject() const {
    return new NZonesMakeup::TDocumentMergerWriter(nullptr, false);
}

NRTYServer::IParsedEntity::TPtr TRTYMakeupIndexComponent::BuildParsedEntity(NRTYServer::IParsedEntity::TConstructParams& params) const {
    return new TRTYMakeupParsedEntity(params);
}

NRTYServer::IComponentParser::TPtr TRTYMakeupIndexComponent::BuildParser() const {
    return nullptr;
}

TRTYMakeupIndexComponent::TRTYMakeupIndexComponent(const TRTYServerConfig& config)
    : IIndexComponent(IsUsed(config))
    , Config(config)
{
    const bool defaultChecked = true;
    const TIndexFile::EPrefetchPolicy defaultPrefetchPolicy = TIndexFile::ppDisable;

    IndexFiles.insert(TIndexFile(TFsPath(TRTYMakeupManager::HdrFileName).Basename(), defaultChecked, defaultPrefetchPolicy));
    IndexFiles.insert(TIndexFile(TFsPath(TRTYMakeupManager::DocsFileName).Basename(), defaultChecked, defaultPrefetchPolicy));
}

THolder<NRTYServer::IIndexComponentManager> TRTYMakeupIndexComponent::CreateManager(const NRTYServer::TManagerConstructionContext& context) const {
    if (context.IndexType == IIndexController::FINAL) {
        TZonesDescription zd(context.Config.Common.Owner);
        ui32 zoneFactorsCount = zd.GetFactorsZonesCount();
        if (zoneFactorsCount <= 8) {
            NOTICE_LOG << "Storage format adopt for ui8" << Endl;
            return MakeHolder<TReadOnlyRTYMakeupManager<ui8>>(context.Dir, context.Config.Common.Owner);
        }
        else if (zoneFactorsCount <= 16){
            NOTICE_LOG << "Storage format adopt for ui16" << Endl;
            return MakeHolder<TReadOnlyRTYMakeupManager<ui16>>(context.Dir, context.Config.Common.Owner);
        }
        else if (zoneFactorsCount <= 32){
            NOTICE_LOG << "Storage format adopt for ui32" << Endl;
            return MakeHolder<TReadOnlyRTYMakeupManager<ui32>>(context.Dir, context.Config.Common.Owner);
        }
        else {
            FAIL_LOG("a lot of factors: %d", zoneFactorsCount);
        }
    }
    return nullptr;
}

THolder<NRTYServer::IIndexComponentBuilder> TRTYMakeupIndexComponent::CreateBuilder(const NRTYServer::TBuilderConstructionContext& context) const {
    if (context.Config.GetType() == "memory") {
        return MakeHolder<TRTYMemoryMakeupBuilder>(context.Config.MaxDocuments, context.Config.Common.Owner, GetName());
    }
    else
        return MakeHolder<TRTYDiskMakeupBuilder>(context.Config.MaxDocuments, context.Config, false, GetName());
}

const TRTYMakeupIndexComponent::TIndexFiles& TRTYMakeupIndexComponent::GetIndexFiles() const {
    return IndexFiles;
}

bool TRTYMakeupIndexComponent::DoMerge(const NRTYServer::TMergeContext& rtyContext) const {

    VERIFY_WITH_LOG(rtyContext.Merger, "Incorrect usage TRTYMakeupIndexComponent::DoMerge method");

    NRTYMerger::TBlockMerger::TSourceReaders sourceReaders;
    NRTYMerger::TBlockMerger::TDestWriters destWriters;

    for (ui32 src = 0; src < rtyContext.Context.Sources.size(); ++src) {
        const TString& source = rtyContext.Context.Sources[src];
        const TString hdrFilePath = source + TRTYMakeupManager::HdrFileName;
        const TString docsFilePath = source + TRTYMakeupManager::DocsFileName;
        if (NFs::Exists(hdrFilePath) && NFs::Exists(docsFilePath))
            sourceReaders.push_back(new NRTYMerger::ISourceFileReader(hdrFilePath, docsFilePath, *this));
        else {
            WARNING_LOG << "Case usage Merger before fix " << hdrFilePath << " " << docsFilePath << " index withno makeup data" << Endl;
            return true;
        }
    }

    for (ui32 dst = 0; dst < rtyContext.Context.Dests.size(); ++dst) {
        destWriters.push_back(new NRTYMerger::IDestFileWriter(
            rtyContext.Context.Dests[dst] + TRTYMakeupManager::HdrFileName,
            rtyContext.Context.Dests[dst] + TRTYMakeupManager::DocsFileName,
            *this, rtyContext.Context.WriteOption));
    }


    NRTYMerger::TContext context;
    context.StopFlag = rtyContext.RigidStopSignal;

    NRTYMerger::TBlockMerger blockMerger(sourceReaders, destWriters, *rtyContext.Context.Decoder);
    return blockMerger.Merge(context);
}

bool TRTYMakeupIndexComponent::DoAllRight(const NRTYServer::TNormalizerContext& context) const {
    const TString hdrFile = context.Dir.PathName() + TRTYMakeupManager::HdrFileName;
    const TString docsFile = context.Dir.PathName() + TRTYMakeupManager::DocsFileName;
    if(!NFs::Exists(hdrFile) && !NFs::Exists(docsFile))
        return false;
    int version = 1;
    TUnbufferedFileInput in(hdrFile);
    in.Load(&version, sizeof(version));
    if (version < TReadWriteRTYMakeupManager::SerializeVersion) {
        WARNING_LOG << "Makeup version mismatch" << Endl;
        return false;
    }
    TZonesDescription zd(in, context.Config);
    return zd.CheckFactorsSequence();
}

void TRTYMakeupIndexComponent::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    ThreadDisableBalloc();
    if (AllRight(context))
        return;
    const ui32 docCount = context.Managers.GetDocumentsCount();
    TSequentialWriteMakeupManager manager(Config, docCount);
    RTY_MEM_LOG("MAKEUP CheckAndFix : Training scan index...");
    {
        THashMap<TString, ui32> key2docCount;
        NIndexerCore::TIndexReader preReader((context.Dir.PathName() + "/index").data(), IYndexStorage::FINAL_FORMAT);
        while (preReader.ReadKey()) {
            const char* key = preReader.GetKeyText();
            if (key[0] == '#' && key[1] == '#')
                continue;
            if (manager.IsZoneForStore(key)) {
                const char* name = key + (Config.IsPrefixedIndex ? 18 : 1);
                ++key2docCount[name];
            }
        }
        TVector<std::pair<ui32, TString>> counts2keys;
        counts2keys.reserve(key2docCount.size());
        for (const auto& key2count: key2docCount) {
            counts2keys.emplace_back(key2count.second, key2count.first);
        }
        RTY_MEM_LOG("MAKEUP CheckAndFix : Sorting positions...");
        key2docCount.clear();
        // the most frequent keys should go first to minimize the resulting index size
        Sort(counts2keys, [](const std::pair<ui32, TString>& a, const std::pair<ui32, TString>& b) -> bool {
            return a > b;
        });
        RTY_MEM_LOG("MAKEUP CheckAndFix : Precharging decoder...");
        for (const auto& count2key:  counts2keys) {
            manager.GetMakeupStorage()->AddZoneUnsafe(count2key.second);
        }
    }
    RTY_MEM_LOG("MAKEUP CheckAndFix : Scan index...");
    NIndexerCore::TIndexReader reader((context.Dir.PathName() + "/index").data(), IYndexStorage::FINAL_FORMAT);
    while (reader.ReadKey()) {
        const char* key = reader.GetKeyText();
        if (key[0] == '#' && key[1] == '#')
            continue;
        TPosIterator<> pos;
        for (reader.InitPosIterator(pos); pos.Valid(); pos.Next()) {
            manager.StorePosition(pos.Doc(), key, pos.Current());
        }
    }
    RTY_MEM_LOG("MAKEUP CheckAndFix : Scan index... OK");
    NOTICE_LOG << "MAKEUP CheckAndFix : Serialization..." << Endl;
    TFixedBufferFileOutput hdrOut(context.Dir.PathName() + TRTYMakeupManager::HdrFileName);
    TFixedBufferFileOutput docsOut(context.Dir.PathName() + TRTYMakeupManager::DocsFileName);
    manager.Serialize(hdrOut, docsOut);
    RTY_MEM_LOG("MAKEUP CheckAndFix : Serialization...OK");
}

bool TRTYMakeupIndexComponent::GetInfoChecker(NRTYServer::TInfoChecker& info) const {
    info.Version = TReadWriteRTYMakeupManager::SerializeVersion;
    return true;
}
