#include "qs_const.h"
#include "qs_manager.h"
#include "qs_pos_builder.h"

#include <saas/rtyserver/components/erf/erf_writer.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/searcher_config.h>
#include <saas/rtyserver/search/cgi/rty_external_cgi.h>

class TSpecificQSCgi : public IRTYCgiReader {
private:
    TString Key;
    mutable TRequestParams RP;
public:
    TSpecificQSCgi(const TString& key)
        : Key(key)
    {}

    const TString& GetQSReq() const override {
        return Key;
    }
    const NRTYFactors::TRankModelHolder* GetFilterModel() const override { return nullptr; }
    const NRTYFactors::TRankModelHolder* GetFastFilterModel() const override { return nullptr; }
    const NRTYFactors::TRankModelHolder* GetRankModel() const override { return nullptr; }
    const NRTYFactors::TRankModelHolder* GetFastRankModel() const override { return nullptr; }
    const NRTYFactors::TUserFactorsCalcer* GetUserFactorsCalcer() const override { return nullptr; }
    const TExtraFactors* GetExtraFactors(bool /*fastRank*/) const override { return nullptr; }
    const TString* GetNamedFactorSet(bool /*fastRank*/) const override { return nullptr; }
    TComponentSearcher* GetPreSearcher() const override { return nullptr; }
    IRTYDocProcessor* GetRtyDocProcessor() const override { return nullptr; }
    void FillDynamicContext(TRTYDynamicFeatureContext&) const override { }
    ECalcFactors CalcAllFactors(bool /*fastrank*/) const override { return ECalcFactors::Formula; };
    bool ShouldCalcAllStaticFactors() const override { return false; }
    bool ShouldUseExternalAnnCalcer() const override { return false; }
    bool IsQuorumAnnDisabled(bool /*rtyAnnSaas*/) const override { return false; }
    TRequestParams& GetRP() const override { return RP; }
    float GetFilterBorder() const override { return 0; }
    float GetFastFilterBorder() const override { return 0; }
    EFilterBorderMode GetFbMode() const override { return EFilterBorderMode::Default; }
    EFilterBorderMode GetFastFbMode() const override { return EFilterBorderMode::Default; }
    bool GetBorderKeepRefineDoc() const override { return false; }
    TVector<ui64> GetKps() const override { return TVector<ui64>(); }
    TMaybe<TString> GetTmName() const override { return Nothing(); }
    void TreatCgiParams(TRequestParams& /*RP*/, const TCgiParameters& /*cgiParams*/) override {}
    TMaybe<bool> ShouldImitateQrQuorum() const override { return Nothing(); }
    bool IsSoftnessEnabledInTextQuorum() const override { return false; }
    bool AreRtyFactorsRequested() const override { return false; }
    ui32 GetFactorAnnHitsLimit() const override { return 0; }
};

TQSManager::TQSManager(const NRTYServer::TManagerConstructionContext& context)
    : NRTYServer::IIndexComponentManager(QS_COMPONENT_NAME)
    , Config(context.Config.Common.Owner)
    , FactorsList(Config.GetSearcherConfig().Factors->GetQuerySpecFactors())
    , Dir(context.Dir)
{
    for (NRTYFactors::TQSFactorsHashList::const_iterator i = FactorsList.begin(), e = FactorsList.end(); i != e; ++i) {
        FactorsInfo.push_back(i->second);
        Readers.push_back(new TRTYKIReader(Dir.PathName(), i->first));
        TRTYErfDiskManager::TCreationContext cc(Dir, TNamedBlocksErf::GetErfFileName(i->first), i->second->GetFactorsInfo(), true, context.UseGlobalFileMapping);

        Erfs.push_back(new TRTYErfDiskManager(cc, QS_COMPONENT_NAME));
        TQSIterator::TFunctionPtr func = IQSFunction::TFactory::Construct(i->second->GetFunctionName());
        VERIFY_WITH_LOG(!!func, "unknown qs function name: %s", i->second->GetFunctionName().data());
        FunctionsByName[i->second->GetFunctionName()] = func;
    }
}

bool TQSManager::DoOpen() {
    try {
        for (ui32 i = 0; i < Readers.size(); ++i) {
            Readers[i]->Open();
            if (!Erfs[i]->Open())
                ythrow yexception() << "cannot open erf";
        }
    } catch (...) {
        ERROR_LOG << "cannot open QSManager in " << Dir.PathName() << ": " << CurrentExceptionMessage();
        DoClose();
        return false;
    }
    return true;
}

bool TQSManager::DoClose() {
    for (ui32 i = 0; i < Readers.size(); ++i) {
        if (Readers[i]->IsOpen()) {
            Readers[i]->Close();
        }
        if(Erfs[i]->IsOpen()) {
            Erfs[i]->Close();
        }
    }
    return true;
}

ui32 TQSManager::GetDocumentsCount() const {
    return Max<ui32>();
}

void TQSManager::InitInteractions(const NRTYServer::IIndexManagersStorage& /* storage */) {

}

bool TQSManager::GetDocInfo(const ui32 docId, NJson::TJsonValue& result) const {
    TBasicFactorStorage data(N_FACTOR_COUNT);
    TFactorView factorValues(data);
    for (ui32 i = 0; i < FactorsInfo.size(); ++i) {
        TRTYErfDiskManagerPtr erf = Erfs[i];
        const NRTYFactors::TQSFactorsList::TPtr qsFactorsInfo = FactorsInfo[i];
        const TString& name = qsFactorsInfo->GetFunctionName();
        const auto& staticFactors = qsFactorsInfo->GetFactorsInfo();
        const ui32 staticFactorsCount = staticFactors->GetStaticFactorsCount();

        factorValues.Clear();
        NJson::TJsonValue nameBlock;
        NIndexerCore::TIndexReader reader(Readers[i]->GetFilePath().data(), IYndexStorage::FINAL_FORMAT);
        while (reader.ReadKey()) {
            const char* key = reader.GetKeyText();
            VERIFY_WITH_LOG(strlen(key) > (QSFactorKeyPrefix.size() + 1), "incorrect QS key");
            const TString originalKey(key + QSFactorKeyPrefix.size(), key + strlen(key));
            TQSIteratorsPull qsFactorsPool;
            TSpecificQSCgi cgi(originalKey);
            qsFactorsPool.Add(new TQSIterator(Readers[i], erf, &cgi, qsFactorsInfo, FunctionsByName));
            qsFactorsPool.ReadFactors(factorValues, docId);

            NJson::TJsonValue keyBlock;
            for (ui32 i = 0; i < staticFactorsCount; ++i) {
                const NRTYFactors::TSimpleFactorDescription& factor = staticFactors->GetFactor(i);
                keyBlock.InsertValue(factor.Name, factorValues[factor.IndexGlobal]);
            }
            nameBlock.InsertValue(originalKey, keyBlock);
        }
        result.InsertValue(name, nameBlock);
    }
    return true;
}

TQSIterator::TQSIterator(TRTYKIReader::TPtr kiReader, TRTYErfDiskManagerPtr erfReader, const IQSRequest* cgi, NRTYFactors::TQSFactorsList::TPtr qsFunction, const TFunctionsByName& functionsByName)
    : KIReader(kiReader)
    , ErfReader(erfReader)
{
    TYndexRequester& yr = *KIReader->GetYR();
    QSFunction = qsFunction;
    TLanguageContext langContext(LI_BASIC_LANGUAGES, nullptr);
    TLangMask langMask = THitsLoader::GetDefaultLemmatizedLangMask();
    TLangMask flatBastardsLangMask = THitsLoader::GetDefaultFlatBastardsLangMask();

    TRichTreePtr Request = functionsByName.find(QSFunction->GetFunctionName())->second->GetFResult(cgi, langContext);

    HitsLoader.Reset(new THitsLoader(yr.YMain(), langMask, flatBastardsLangMask,
        yr.GetAnchorWordWeight(), yr.GetHitsForReadLoader(), THitsLoader::IDX_TEXT, true));

    TopAndArgs.Reset(new TTopAndArgs());
    ELoadResult res = GenerateTopAndArgs(HitsLoader.Get(), HitsLoader.Get(), nullptr, *Request->Root, nullptr, TopAndArgs.Get(), nullptr, TGenerateTopAndArgOptions(), GetWebFactorsInfo());
    if (res != LR_OK)
        TopAndArgs.Destroy();
}

void TQSIteratorsPull::ReadFactors(TFactorView& factors, const ui32 docId) {
    if ((i32)docId < DocId) {
        ERROR_LOG << "Incorrect document sequence " << docId << " > " << DocId << Endl;
        Y_ASSERT(0);
    }
    DocId = docId;
    for (TVector<TQSIterator::TPtr>::iterator it = Iterators.begin(), ite = Iterators.end(); it != ite; ++it) {
        TFullPosition res[2];
        SUPERLONG docIdSL = 0;
        TWordPosition::SetDoc(docIdSL, DocId);
        for (ui32 itId = 0; itId < (*it)->GetTopAndArgs()->Words.size(); ++itId) {
            (*it)->GetTopAndArgs()->Words[itId]->SkipToDoc(docIdSL);
            ui32 size = (*it)->GetTopAndArgs()->Words[itId]->GetPositions(&res[0], 2 * sizeof(TFullPosition));
            if (size) {
                if (size != 2) {
                    ERROR_LOG << "Incorrect positions for QS iterator " << size << Endl;
                    Y_ASSERT(0);
                    continue;
                }
                ui32 posErf = 0;
                if (!TQSPosBuilder::BuildPosition(TWordPosition(res[0].Beg), TWordPosition(res[1].Beg), posErf))
                    continue;
                auto& erfManager = *(*it)->GetErfManager();
                if (erfManager.Read(factors, posErf)) {
                    break;
                } else {
                    ERROR_LOG << "Incorrect erf reading for position " << posErf << Endl;
                    Y_ASSERT(0);
                }
            }
        }
    }
}

TQSIteratorsPull::TPtr TQSManager::BuildIteratorsPull(const IQSRequest* cgi, const NRTYFactors::TRTYFactorMask* rtyFactorMask) const {
    Y_ASSERT(rtyFactorMask->QSFactorsInfo.IsUsed());
    TQSIteratorsPull::TPtr result = new TQSIteratorsPull();
    for (ui32 i = 0; i < FactorsInfo.size(); ++i) {
        if (rtyFactorMask->QSFactorsInfo.IsUsed(i) && Readers[i]->IsOpen())
            result->Add(new TQSIterator(Readers[i], Erfs[i], cgi, FactorsInfo[i], FunctionsByName));
    }
    return result;
}
