#include "factors_calcer.h"

#include <saas/rtyserver/components/suggest/config/config.h>

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

const TSuggestFactorsCalcer::TAvailableFactors& TSuggestFactorsCalcer::TAvailableFactors::Instance() {
    return *Singleton<TAvailableFactors>();
}

TSuggestFactorsCalcer::TAvailableFactors::TAvailableFactors() {
    insert(value_type("Count", &TSuggestFactorsCalcer::CalcCountFactor));
    insert(value_type("AverageWeight", &TSuggestFactorsCalcer::CalcAverageWeightFactor));
    insert(value_type("WordsCount", &TSuggestFactorsCalcer::CalcWordsCountFactor));
    insert(value_type("FirstWordPositionInv", &TSuggestFactorsCalcer::CalcFirstWordPositionInvFactor));
    insert(value_type("BM25", &TSuggestFactorsCalcer::CalcBM25Factor));
    insert(value_type("Order", &TSuggestFactorsCalcer::CalcOrderFactor));
    insert(value_type("Density", &TSuggestFactorsCalcer::CalcDensityFactor));
}

void TSuggestFactorsCalcer::Init(const TRTYServerConfig& config, const NRTYFactors::TRankModelHolder& rankModel, ui32 queryWordsCount) {
    WordWeight.resize(queryWordsCount, 1);
    for (ui32 factorIndex : rankModel.GetUsedFactors()) {
        const NRTYFactors::TFactor& factor = *config.GetSearcherConfig().Factors->GetFactorByFormulaIndex(factorIndex);
        if (factor.FactorType != NRTYFactors::ftSuggest)
            throw yexception() << "in suggest only suggest factors available, but formula use factor "
                << factor.Name << ", index " << factorIndex << " of type " << factor.FactorType;
        auto i = TAvailableFactors::Instance().find(factor.Name);
        CHECK_WITH_LOG(!i.IsEnd());
        Factors.push_back(TFactorData(factor.IndexGlobal, i->second));
    }
    Bm25Tracker.Init(queryWordsCount);
    MaxSuggestLength = config.ComponentsConfig.Get<TSuggestComponentConfig>(SUGGEST_COMPONENT_NAME)->GetMaxWordLength();
}

float TSuggestFactorsCalcer::CalcCountFactor(const TSuggestRecord& sr, const THits& /*hits*/) {
    return sr.GetCount();
}

float TSuggestFactorsCalcer::CalcAverageWeightFactor(const TSuggestRecord& sr, const THits& /*hits*/) {
    if (!sr.GetCount())
        return 0;
    return float(sr.GetWeight()) / sr.GetCount();
}

float TSuggestFactorsCalcer::CalcWordsCountFactor(const TSuggestRecord& sr, const THits& /*hits*/) {
    if (!sr.GetCount())
        return 0;
    return float(sr.GetWordsCount()) / sr.GetCount();
}

float TSuggestFactorsCalcer::CalcFirstWordPositionInvFactor(const TSuggestRecord& /*sr*/, const THits& hits) {
    return 1.f / (1 + TWordPosition::Word(hits.Begin->Pos.Beg));
}

float TSuggestFactorsCalcer::CalcBM25Factor(const TSuggestRecord& sr, const THits& hits) {
    if (!sr.GetCount())
        return 0;
    Bm25Tracker.NewDoc();
    for (const TFullPositionEx* hit = hits.Begin; hit != hits.End; ++hit)
        Bm25Tracker.Add(hit->Pos, hit->WordIdx);
    return Bm25Tracker.CalcScore(WordWeight, EQUAL_BY_STRING, CalcWordsCountFactor(sr, hits), 10);
}

float TSuggestFactorsCalcer::CalcOrderFactor(const TSuggestRecord& /*sr*/, const THits& hits) {
    /// We want to calculate max length of ordered by request word number (WordIdx) subsequence of hits.
    /// dynamic is array where dynamic[i] equals WordIdx of last element of subsequence length i.
    /// Max index of finite element of dynamic is the desired length.
    TVector<int> dynamic(hits.End - hits.Begin + 1, Max<int>());
    dynamic[0] = -1;
    ui32 maxOrderdSequenceLength = 0;
    for (const TFullPositionEx* hit = hits.Begin; hit != hits.End; ++hit) {
        int wordId = hit->WordIdx;
        TVector<int>::iterator d = UpperBound(dynamic.begin(), dynamic.end(), wordId);
        if (*(d - 1) <= wordId && wordId <= *d) {
            *d = wordId;
            maxOrderdSequenceLength = Max<ui32>(maxOrderdSequenceLength, d - dynamic.begin());
        }
    }
    return float(maxOrderdSequenceLength) / WordWeight.size();
}

float TSuggestFactorsCalcer::CalcDensityFactor(const TSuggestRecord& /*sr*/, const THits& hits) {
    ui32 maxWord = 0;
    ui32 minWord = Max<ui32>();
    for (const TFullPositionEx* hit = hits.Begin; hit != hits.End; ++hit) {
        ui32 word = TWordPosition::Word(hit->Pos.Beg);
        minWord = Min(word, minWord);
        maxWord = Max(word, maxWord);
    }
    if (maxWord == minWord)
        return 1;
    return float(hits.End - hits.Begin) / (maxWord - minWord + 1);
}
