#include "builder.h"
#include "component.h"
#include "config.h"
#include "globals.h"
#include "manager.h"
#include "parsed_entity.h"

#include <dict/dictutil/str.h>
#include <saas/rtyserver/common/should_stop.h>
#include <saas/rtyserver/components/search/archive/data.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/merger/library/archive_op.h>
#include <ysite/yandex/srchmngr/arcmgr.h>

#include <library/cpp/digest/md5/md5.h>

#include <util/generic/algorithm.h>
#include <util/system/fs.h>

namespace {
    const TString IndexFile = "indexfastarc";

    class TFastArchiveRestorer: public IDocInfosBuilderCallback {
    public:
        TFastArchiveRestorer(TFastArchiveDocWriter& writer, const TRTYFastArchiveConfig& config)
            : Writer(writer)
            , Config(config)
        {}

        void OnBuildProperty(const char* name, const char* value) override {
            if (!Config.GetFastProperties().contains(name))
                return;

            const auto& splitted = SplitString(value, "\t");
            for (const TString& val : splitted) {
                if (FindAnyOf(val, ":;") != TString::npos)
                    continue;

                Writer.Add(name, val);
            }
        }
    private:
        TFastArchiveDocWriter& Writer;
        const TRTYFastArchiveConfig& Config;
    };

    bool IsUsed(const TRTYServerConfig& config) {
        return config.ComponentsSet.contains(FASTARCHIVE_COMPONENT_NAME);
    }
}

TRTYFastArchive::TRTYFastArchive(const TRTYServerConfig& config)
    : IIndexComponent(IsUsed(config))
    , Config(config)
    , ComponentConfig(config.ComponentsConfig.Get<TRTYFastArchiveConfig>(FASTARCHIVE_COMPONENT_NAME))
{
    CHECK_WITH_LOG(ComponentConfig);
    IndexFiles.insert(TIndexFile(IndexFile, false, TIndexFile::ppDisable));
}

TString TRTYFastArchive::GetName() const {
    return FASTARCHIVE_COMPONENT_NAME;
}

THolder<NRTYServer::IIndexComponentBuilder> TRTYFastArchive::CreateBuilder(const NRTYServer::TBuilderConstructionContext& context) const {
    if (context.Config.GetType() == "memory")
        return MakeHolder<TRTYFastArchiveMemoryBuilder>(context.Config, *ComponentConfig, GetName());
    else
        return MakeHolder<TRTYFastArchiveDiskBuilder>(context.Config, *ComponentConfig, context.TempDir.PathName(), GetName());
}

THolder<NRTYServer::IIndexComponentManager> TRTYFastArchive::CreateManager(const NRTYServer::TManagerConstructionContext& context) const {
    return MakeHolder<TRTYFastArchiveDiskManager>(context.Dir.PathName() + '/' + IndexFile);
}

NRTYServer::IComponentParser::TPtr TRTYFastArchive::BuildParser() const {
    return new TRTYFastArchiveParser{};
}

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

bool TRTYFastArchive::DoMerge(const NRTYServer::TMergeContext& context) const {
    TRTYMerger::TContext mc(context.Context);
    TMergeDocInfos infos(mc.Sources.size(), mc.Decoder, mc.Info);
    if (infos.IsEmpty()) {
        return true;
    }

    TVector<TAutoPtr<TRTYFastArchiveManager>> managers;
    for (ui32 i = 0; i < mc.Sources.size(); ++i) {
        const TString& file = mc.Sources[i] + '/' + IndexFile;
        if (!NFs::Exists(file)) {
            ERROR_LOG << "FASTARC: file " << file << " does not exist, skipping merge" << Endl;
            return true;
        }

        auto manager = new TRTYFastArchiveDiskManager(file);
        VERIFY_WITH_LOG(manager->Open(), "cannot open FASTARC disk manager for merger");
        managers.push_back(manager);
    }

    TVector<TAutoPtr<TRTYFastArchiveBuilder>> builders;
    NRTYServer::TIndexerConfigDisk cfg(Config.GetRealmListConfig().GetRealmConfigByConfigName(context.RealmName).GetIndexerConfigDisk());
    cfg.Threads = 1;
    for (ui32 i = 0; i < mc.Dests.size(); ++i) {
        builders.push_back(new TRTYFastArchiveDiskBuilder(cfg, *ComponentConfig, mc.Dests[i], GetName()));
    }

    TMergeDocInfos::TDocInfoSource p = infos.GetIterator();
    for (; p.IsValid() && !ShouldStop(context.RigidStopSignal); p.Next()) {
        auto doc = (TFastArchiveDoc*)managers[p.GetFromCl()]->GetDoc(p.GetFromDocId());
        const ui32 newDocId = p.GetToDocId();
        builders[p.GetToCl()]->Index(newDocId, *doc);
    }

    for (ui32 i = 0; i < mc.Sources.size(); ++i) {
        VERIFY_WITH_LOG(managers[i]->Close(), "cannot close FASTARC disk manager for merger");
    }

    for (ui32 i = 0; i < mc.Dests.size(); ++i) {
        TPathName srcDir(mc.Dests[i]);
        TPathName dstDir(mc.Dests[i]);
        NRTYServer::TBuilderCloseContext ctx(srcDir, dstDir, nullptr, context.RigidStopSignal);
        builders[i]->Close(ctx);
    }

    return true;
}

bool TRTYFastArchive::GetInfoChecker(NRTYServer::TInfoChecker& info) const {
    const auto& properties = ComponentConfig->GetFastProperties();
    info.Version = FastArchiveVersion;
    info.AdditionalHash = MD5::Calc(JoinStrings(properties.begin(), properties.end(), ","));
    return true;
}

bool TRTYFastArchive::DoAllRight(const NRTYServer::TNormalizerContext& context) const {
    const TSet<TString>& configProperties = ComponentConfig->GetFastProperties();
    const TString& filename = context.Dir.PathName() + '/' + IndexFile;

    TSet<TString> indexProperties;
    ui32 version;
    if (!ReadArchiveHeader(filename, version, indexProperties)) {
        INFO_LOG << "FASTARC: cannot read header" << Endl;
        return false;
    }
    if (version != FastArchiveVersion) {
        INFO_LOG << "FASTARC: version mismatch " << version << " != " << FastArchiveVersion << Endl;
        return false;
    }

    TVector<TString> difference(configProperties.size() + indexProperties.size());
    auto i = SetSymmetricDifference(indexProperties.begin(), indexProperties.end(), configProperties.begin(), configProperties.end(), difference.begin());
    if (i - difference.begin()) {
        INFO_LOG << "FASTARC: properties changed" << Endl;
        return false;
    }

    return true;
}

void TRTYFastArchive::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    if (AllRight(context))
        return;

    const TFsPath directory = context.Dir.PathName();

    TFileMap indexFrq(directory / "indexfrq", TFileMap::oRdWr);
    indexFrq.Map(0, indexFrq.GetFile().GetLength());
    const ui32 docCount = indexFrq.GetFile().GetLength() / sizeof(i16);

    const auto archive = NRTYServer::CreateArchiveData(directory / "index");
    AssertCorrectConfig(!!archive, "Cannot restore FastArchive: no SearchArchive");
    TRTYFastArchiveDiskBuilder builder(Config.GetRealmListConfig().GetRealmConfigByConfigName(context.GetRealmName()).GetIndexerConfigDisk(), *ComponentConfig, directory, GetName());
    TFastArchiveStringPool StringPool;

    for (ui32 docId = 0; docId < docCount; ++docId) {
        TFastArchiveDocWriter writer;
        TFastArchiveRestorer restorer(writer, *ComponentConfig);

        TDocDescr description;
        TBlob extInfo = archive->GetDocExtInfo(docId)->UncompressBlob();
        description.UseBlob(extInfo.Data(), (unsigned int)extInfo.Size());
        description.ConfigureDocInfos(&restorer);

        TFastArchiveDoc doc(&StringPool);
        doc.FillFrom(writer);
        builder.Index(docId, doc);
    }

    for (const auto& file : IndexFiles) {
        NFs::Remove(directory / file.Name);
    }

    const TPathName indexDir(directory.GetPath());
    NRTYServer::TBuilderCloseContext ctx(indexDir, indexDir, nullptr, nullptr);
    builder.Close(ctx);
}
