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

#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/util/system/stream_digest.h>

TKeysComponent::TKeysComponent(const TRTYServerConfig& config)
    : IIndexComponent(IsUsedStatic(config))
    , Config(config)
{
    IndexFiles.insert(TIndexFile("index.keys.inv", true, TIndexFile::ppLock));
    IndexFiles.insert(TIndexFile("index.keys.key", true, TIndexFile::ppLock));
    IndexFiles.insert(TIndexFile(NRTYServer::KeysBloomFileName, true, TIndexFile::ppDisable));
    IndexFiles.insert(TIndexFile(NRTYServer::KeysBloomMD5FileName, true, TIndexFile::ppDisable));
}

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

TString TKeysComponent::GetName() const {
    return NRTYServer::KeysComponentName;
}

bool TKeysComponent::IsUsedStatic(const TRTYServerConfig& config) {
    return config.ComponentsSet.contains(NRTYServer::KeysComponentName)
            || config.IndexGenerator == FULL_ARCHIVE_COMPONENT_NAME
            || config.IndexGenerator == INDEX_COMPONENT_NAME;
}

bool TKeysComponent::GetInfoChecker(NRTYServer::TInfoChecker& info) const {
    info.Version = 2;
    return true;
}

THolder<NRTYServer::IIndexComponentBuilder> TKeysComponent::CreateBuilder(const NRTYServer::TBuilderConstructionContext& context) const {
    if (context.Config.GetType() == "memory") {
        return MakeHolder<TKeysIndexBuilderMemory>(context.Config.Common.Owner, GetName());
    }

    return MakeHolder<TKeysIndexBuilderDisk>(context.TempDir, context.Config, GetName());
}

THolder<NRTYServer::IIndexComponentManager> TKeysComponent::CreateManager(const NRTYServer::TManagerConstructionContext& context) const {
    switch (context.IndexType) {
        case IIndexController::PREPARED:
        case IIndexController::FINAL:
            return MakeHolder<TKeysIndexManagerDisk>(context.Dir.PathName(), context.Config.Common.Owner);
        case IIndexController::DISK:
        case IIndexController::MEMORY:
            return nullptr;
        default:
            FAIL_LOG("Incorrect index type");
    }
}

bool TKeysComponent::DoMerge(const NRTYServer::TMergeContext& context) const {
    try {
        TRTYMerger::TContext contextCopy = context.Context;
        contextCopy.AdditionalSuffixIndexName = ".keys.";
        contextCopy.WriteSentLens = false;
        TRTYMerger merger(nullptr, TRTYMerger::otKI);
        merger.MergeIndicies(contextCopy);
        return true;
    } catch (...) {
        ERROR_LOG << "Can't merge Keys Component for " << context.Context.TempDir << ": " << CurrentExceptionMessage() << Endl;
        return false;
    }
}

NRTYServer::IComponentParser::TPtr TKeysComponent::BuildParser() const {
    return new TKeysComponentParser(Config);
}

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

bool TKeysComponent::CheckKeyInv(const NRTYServer::TNormalizerContext& context) const {
    try {
        TRTYKIReader ki(context.Dir.PathName(), "keys");
        ki.Open();
        bool result = ki.IsOpen();
        if (result)
            ki.Close();
        else
            ERROR_LOG << "Cannot open keyinv in " << context.Dir.PathName() << "/index.keys" << Endl;
        return result;
    } catch (...) {
        ERROR_LOG << "Cannot open keyinv in " << context.Dir.PathName() << "/index.keys:" << CurrentExceptionMessage() << Endl;
        return false;
    }
}

bool TKeysComponent::CheckBloom(const NRTYServer::TNormalizerContext& context) const {
    const TString bloomFilterFilePath(context.Dir.PathName() + "/" + NRTYServer::KeysBloomFileName);
    const TString bloomFilterMD5FilePath(context.Dir.PathName() + "/" + NRTYServer::KeysBloomMD5FileName);

    if (!TFsPath(bloomFilterFilePath).Exists() || !TFsPath(bloomFilterMD5FilePath).Exists()) {
        return false;
    }
    const TString storedMD5(TUnbufferedFileInput(bloomFilterMD5FilePath).ReadAll());
    const TString calcMD5(MD5::File(bloomFilterFilePath));
    return storedMD5 == calcMD5;
}

bool TKeysComponent::DoAllRight(const NRTYServer::TNormalizerContext& context) const {
    TKeysIndexManagerDisk manager(context.Dir.PathName(), context.Config);
    NRTYServer::TManagerTryGuard guard(manager);
    return manager.IsOpened();
}

bool TKeysComponent::CheckConfig() const {
    return true;
}


void TKeysComponent::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    if (!CheckKeyInv(context))
        FixKeyInv(context);
    if (!CheckBloom(context))
        FixBloom(context);
}

void TKeysComponent::FixKeyInv(const NRTYServer::TNormalizerContext& context) const {
    INFO_LOG << "Try repair keyinv in " << context.Dir.PathName() << "/index.keys..." << Endl;
    TFsPath(context.Dir.PathName() + "/" + NRTYServer::KeysBloomFileName).ForceDelete();
    TFsPath(context.Dir.PathName() + "/" + NRTYServer::KeysBloomMD5FileName).ForceDelete();
    const NRTYServer::TRealmConfig& realmConfig = Config.GetRealmListConfig().GetRealmConfigByConfigName(context.GetRealmName());
    TKeysIndexBuilderDisk builder(context.Dir, realmConfig.GetIndexerConfigDisk(), GetName());
    builder.Start();
    builder.Stop();
    builder.Close(NRTYServer::TBuilderCloseContext(context.Dir, context.Dir, nullptr, nullptr));
    INFO_LOG << "Try repair keyinv in " << context.Dir.PathName() << "/index.keys...OK" << Endl;
}

namespace {
    class TAllKeysScaner : public IKeyPosScanner {
    public:
        TAllKeysScaner(TBloomFilterFaster& keys)
            : Keys(keys)
        {}

        const char* GetStartPos() const override {
            return "";
        }

        EContinueLogic TestKey(const char* key) override {
            Keys.Add(TString(key));
            return IKeyPosScanner::clSkip;
        }

        void ProcessPos(const char* /*key*/, const TPosIterator<>& /*it*/) override {
        }
    private:
        TBloomFilterFaster& Keys;
    };
}

void TKeysComponent::FixBloom(const NRTYServer::TNormalizerContext& context) const {
    INFO_LOG << "Try repair bloom in " << context.Dir.PathName() << "/index.keys..." << Endl;
    TYndex4Searching kp;
    kp.InitSearch(context.Dir.PathName() + "/index.keys.");
    TBloomFilterFaster keys(kp.KeyCount(), 0.0001);
    TAllKeysScaner scaner(keys);
    kp.Scan(scaner);
    const TString tmpFilePath = context.Dir.PathName() + "/~" + NRTYServer::KeysBloomFileName;
    TFileOutput fo(tmpFilePath);
    fo.SetFinishPropagateMode(true);
    TMD5ProxyStream md5Stream(fo);
    keys.Save(&md5Stream);
    md5Stream.Finish();
    const TString finalFilePath = context.Dir.PathName() + "/" + NRTYServer::KeysBloomFileName;
    TFsPath(tmpFilePath).RenameTo(TFsPath(finalFilePath));
    TUnbufferedFileOutput sumFo(context.Dir.PathName() + "/" + NRTYServer::KeysBloomMD5FileName);
    sumFo.Write(md5Stream.GetMD5());
    sumFo.Finish();

    INFO_LOG << "Try repair bloom in " << context.Dir.PathName() << "/index.keys...OK" << Endl;
}
