#include "index_component_storage.h"
#include "index_hash_checker.h"

#include <saas/library/daemon_base/daemon/messages.h>

#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/common/message_collect_server_info.h>

#include <util/system/guard.h>

bool TIndexComponentsStorage::Process(IMessage* message) {
    TMessageCollectServerInfo* infoMessage = dynamic_cast<TMessageCollectServerInfo*>(message);
    if (infoMessage) {
        TGuard<TMutex> g(Mutex);
        for (auto&& i : Components) {
            infoMessage->AddComponentInfo(i.second->GetComponentInfo());
        }
        return true;
    }
    return false;
}

TString TIndexComponentsStorage::GetComponentsDescription() const {
    TStringStream ss;
    for (auto&& i : Components) {
        ss << i.first << ",";
    }
    return ss.Str();
}

void TIndexComponentsStorage::CreateParsedEntities(TParsedDocument& parsedDocument) const {
    for (auto&& i : ComponentsForParse)
        parsedDocument.CreateParsedEntity(i.Get());
}

const TDocumentParser& TIndexComponentsStorage::GetDocumentParser() const {
    VERIFY_WITH_LOG(!!DocumentParser, "Document parser is not created");
    return *DocumentParser;
}

void TIndexComponentsStorage::Fill(NRTYServer::IIndexBuildersStorage* builders, const NRTYServer::TBuilderConstructionContext& context) const {
    TVector<NRTYServer::IIndexComponentBuilder*> buildersLoc;
    for (auto&& i : ComponentsForIndex) {
        RTY_MEM_LOG("Builder " + i->GetName() + " constructing...");
        auto builder = i->CreateBuilder(context);
        if (builder) {
            NOTICE_LOG << "Fill Index Builder " << i->GetName() << " for " << context.TempDir.BaseName() << Endl;
            buildersLoc.push_back(builder.Get());
            builders->RegisterBuilder(i->GetName(), std::move(builder));
        }
        RTY_MEM_LOG("Builder " + i->GetName() + " constructing... OK");
    }
    for (int i = 0; i < buildersLoc.ysize(); ++i) {
        NOTICE_LOG << "Init Index Builder Interactions " << ComponentsForIndex[i]->GetName() << " for " << context.TempDir.BaseName() << Endl;
        buildersLoc[i]->InitInteractions(*builders);
    }
}

bool TIndexComponentsStorage::CheckConfig() const {
    for (auto&& i : ComponentsForIndex) {
        NOTICE_LOG << "Check config by " << i->GetName() << " component" << Endl;
        if (!i->CheckConfig()) {
            FATAL_LOG << "Check config by " << i->GetName() << " component ... FAILED" << Endl;
            return false;
        } else
            INFO_LOG << "Check config by " << i->GetName() << " component ... OK" << Endl;
    }
    return true;
}

void TIndexComponentsStorage::Fill(NRTYServer::IIndexManagersStorage* managers, const NRTYServer::TManagerConstructionContext& context) const {
    TVector<NRTYServer::IIndexComponentManager*> managersLoc;
    for (auto&& i : ComponentsForOpen) {
        auto manager = i->CreateManager(context);
        if (manager) {
            NOTICE_LOG << "Fill Index Manager " << i->GetName() << " for " << context.Dir.BaseName() << Endl;
            managersLoc.push_back(manager.Get());
            managers->RegisterManager(i->GetName(), std::move(manager));
        } else {
            NOTICE_LOG << "Manager wasn't constructed as " << i->GetName() << " for " << context.Dir.BaseName() << Endl;
        }
    }
    for (int i = 0; i < managersLoc.ysize(); ++i) {
        NOTICE_LOG << "Init Index Manager Interactions " << managersLoc[i]->GetComponentName() << " for " << context.Dir.BaseName() << Endl;
        managersLoc[i]->InitInteractions(*managers);
    }
}

const TIndexComponentsStorage::TComponentsOrdered& TIndexComponentsStorage::GetComponentsForOpen() const {
    return ComponentsForOpen;
}

namespace {
    bool SortByPriorityComponent(const TIndexComponentsStorage::TComponentPtr& left, const TIndexComponentsStorage::TComponentPtr& right) {
        return left->GetPriority().ComponentPriority > right->GetPriority().ComponentPriority;
    }
    bool SortByPriorityBuilder(const TIndexComponentsStorage::TComponentPtr& left, const TIndexComponentsStorage::TComponentPtr& right) {
        return left->GetPriority().BuilderPriority > right->GetPriority().BuilderPriority;
    }
    bool SortByPriorityManager(const TIndexComponentsStorage::TComponentPtr& left, const TIndexComponentsStorage::TComponentPtr& right) {
        return left->GetPriority().ManagerPriority > right->GetPriority().ManagerPriority;
    }
}

void TIndexComponentsStorage::RegisterComponent(const TString& componentName) {
    TGuard<TMutex> g(Mutex);
    VERIFY_WITH_LOG(!Components.contains(componentName), "Incorrect register component method usage");
    CHECK_WITH_LOG(Config);
    TAtomicSharedPtr<NRTYServer::IIndexComponent> component;
    try {
        component = NRTYServer::IIndexComponent::TFactory::Construct(componentName, *Config);
    } catch (const NRTYServer::TComponentIsUnusedException&) {
        NOTICE_LOG << "Index Component " << componentName << " is not used, skipping\n";
        return;
    }
    VERIFY_WITH_LOG(component, "Component %s must be!" , componentName.data());
    VERIFY_WITH_LOG(component->GetName() == componentName, "Component %s name is %s" , componentName.data(), component->GetName().data());
    DocumentParser->CreateAndRegisterParser(*component);
    NOTICE_LOG << "Register Index Component " << componentName << Endl;
    ComponentsForParse.push_back(component);
    Components[component->GetName()] = component;
    ComponentsForIndex.push_back(component);
    ComponentsForNormalization.push_back(component);
    ComponentsForOpen.push_back(component);

    StableSort(ComponentsForOpen.begin(), ComponentsForOpen.end(), SortByPriorityManager);
    StableSort(ComponentsForNormalization.begin(), ComponentsForNormalization.end(), SortByPriorityComponent);
    StableSort(ComponentsForIndex.begin(), ComponentsForIndex.end(), SortByPriorityBuilder);
    StableSort(ComponentsForParse.begin(), ComponentsForParse.end(), SortByPriorityBuilder);
}

void TIndexComponentsStorage::ReleaseComponents() {
    TGuard<TMutex> g(Mutex);
    Components.clear();
    ComponentsForNormalization.clear();
    ComponentsForOpen.clear();
    ComponentsForIndex.clear();
    ComponentsForParse.clear();
}

void TIndexComponentsStorage::ResetConfig(const TRTYServerConfig& config) {
    Config = &config;
    ReleaseComponents();
    DocumentParser.Reset(new TDocumentParser);
    TSet<TString> compNames;
    Singleton<NRTYServer::IIndexComponent::TFactory>()->GetKeys(compNames);
    for (TSet<TString>::const_iterator i = compNames.begin(); i != compNames.end(); ++i) {
        RegisterComponent(*i);
    }
    VERIFY_WITH_LOG(TIndexComponentsStorage::Instance().CheckConfig(), "Incorrect config for components");
}

bool TIndexComponentsStorage::Merge(const NRTYServer::TMergeContext& context) const {
    bool error = false;
    auto* const task = context.Task; // only for logging, may be nullptr
    for (auto&& component : ComponentsForIndex) {
        if (error || !!context.RigidStopSignal && *context.RigidStopSignal) {
            break;
        }

        TInstant startTime = Now();
        RTY_MEM_LOG("Merge " + component->GetName() + " for " + context.Identifier + " start");
        if (task)
            task->AddProgressInfo("Merger for " + component->GetName() + " started");
        try {
            if (!component->Merge(context)) {
                ERROR_LOG << "Incorrect merge component " << component->GetName() << " result" << Endl;
                error = true;
            }
            if (!error && task) {
                task->AddProgressInfo("Merger for " + component->GetName() + " finished successfully");
            }
        } catch (const TMergeCancelException&) {
            task->AddProgressInfo("Merger for " + component->GetName() + " cancelled");
            error = true;
        } catch (const yexception& e) {
            if (task)
                task->AddProgressInfo("Merger for " + component->GetName() + " FAILED: " + TString(e.what()));
            AbortFromCorruptedIndex("Error while merge %s, component %s : %s", context.Identifier.data(), component->GetName().data(), e.what());
        }
        RTY_MEM_LOG("Merge " + component->GetName() + " for " + context.Identifier + " finished(" + ToString(Now() - startTime) + ")");
    }

    if (!!context.RigidStopSignal && *context.RigidStopSignal) {
        if (task)
            task->AddProgressInfo("Merger stopped by rigid stop");
        INFO_LOG << "Merger stopped by rigid stop" << Endl;
        return false;
    }
    return !error;
}

bool TIndexComponentsStorage::AllRight(const NRTYServer::TNormalizerContext& context) const {
    CHECK_WITH_LOG(Config);
    TIndexHashChecker checkSaver(*Config, context.Dir.PathName(), false);
    for (auto&& i : ComponentsForNormalization) {
        TStringStream logText;
        logText << "Check " << i->GetName() << " for " << context.Dir.BaseName();
        RTY_MEM_LOG(logText.Str() + " start");
        if (checkSaver.NeedInCheck(*i)) {
            if (i->AllRight(context)) {
                INFO_LOG << "component " << i->GetName() << " allright check OK" << Endl;
                RTY_MEM_LOG(logText.Str() + " FINISHED");
            } else {
                WARNING_LOG << "component " << i->GetName() << " allright check FAILED" << Endl;
                RTY_MEM_LOG(logText.Str() + " FAILED");
                return false;
            }
        } else {
            RTY_MEM_LOG(logText.Str() + " SKIPPED");
        }
    }
    return true;
}

void TIndexComponentsStorage::CheckAndFix(const NRTYServer::TNormalizerContext& context) const {
    CHECK_WITH_LOG(Config);
    TIndexHashChecker checkSaver(*Config, context.Dir.PathName());
    for (auto&& i : ComponentsForNormalization) {
        TStringStream logText;
        logText << "CheckAndFix " << i->GetName() << " for " << context.Dir.BaseName();
        RTY_MEM_LOG(logText.Str() + " start");
        if (checkSaver.NeedInCheck(*i)) {
            i->CheckAndFix(context);
            checkSaver.SaveCheckData(*i, true);
            RTY_MEM_LOG(logText.Str() + " FINISHED");
        } else {
            RTY_MEM_LOG(logText.Str() + " SKIPPED");
        }
    }
}

void TIndexComponentsStorage::CorrectSearchConfig(TSearchConfig& config, NRTYServer::TSearcherConfig::TSearchConfigType searchConfigType) {
    for (auto&& i : ComponentsForIndex) {
        INFO_LOG << "CorrectSearchConfig " << i->GetName() << "..." << Endl;
        i->CorrectSearchConfig(config, searchConfigType);
        INFO_LOG << "CorrectSearchConfig " << i->GetName() << "...OK" << Endl;
    }
}

THolder<TPruningConfig::ICalcer> TIndexComponentsStorage::CreatePruningCalcer(const NRTYServer::IIndexManagersStorage* managers) const {
    THolder<TPruningConfig::ICalcer> result;
    for (const auto& cmp : Components) {
        if (auto calcer = cmp.second->CreatePruningCalcer(managers)) {
            VERIFY_WITH_LOG(!result, "pruning calcer created by two components");
            result = std::move(calcer);
        }
    }
    if (!result)
        result.Reset(new TPruningConfig::ICalcer);
    return result;
}
