#include "factors_mask.h"

#include <saas/rtyserver/factors/factors_config.h>
#include <saas/rtyserver/factors/factors_domain.h>
#include <saas/rtyserver/factors/rank_model.h>

#include <kernel/web_factors_info/factor_names.h>

#include <util/digest/fnv.h>

namespace {
    template<class T>
    ui32 GetBlockNum(const TString& name, const T& list) {
        ui32 index = 0;
        for (typename T::const_iterator i = list.begin(), e = list.end(); i != e; ++i, ++index) {
            for (ui32 f = 0; f < i->second->GetFactorsList().size(); ++f) {
                if (i->second->GetFactorsList()[f].Name == name)
                    return index;
            }
        }
        FAIL_LOG("Incorrect GetBlockNum result");
        return 0;
    }
}

TFactorsMaskCache::TFactorsMaskCache(const NRTYFactors::TConfig& config)
    : Config(config)
{
    NRTYFactors::TUsedFactors allFactors;
    Config.GetAllFactorsIndexes(allFactors);
    AllFactorsMasks = CalculateMasks(allFactors);
}

const NRTYServer::TFactorMasks& TFactorsMaskCache::Get(
    const NRTYFactors::TRankModelHolder* rankModel,
    const NRTYFactors::TRankModelHolder* filterModel,
    const NRTYFactors::TFactorSet* factorSet,
    const NRTYFactors::TUsedFactors* extraFactors
) const {
    TFactorsMaskHash hash = 0;
    if (rankModel) {
        hash ^= FactorHash(rankModel->GetName());
    }
    if (filterModel) {
        hash ^= (FactorHash(filterModel->GetName()) >> 1);
    }
    if (factorSet) {
        hash ^= IntHash(FactorHash(factorSet->GetName()));
    }
    if (extraFactors) {
        hash ^= FactorHash(*extraFactors);
    }

    {
        TReadGuard guard(Lock);
        auto p = Storage.find(hash);
        if (p != Storage.end()) {
            return p->second;
        }
    }
    {
        TWriteGuard guard(Lock);
        auto p = Storage.find(hash);
        if (p != Storage.end()) {
            return p->second;
        }

        NRTYFactors::TUsedFactors factors;
        if (rankModel) {
            factors.insert(rankModel->GetUsedFactors().begin(), rankModel->GetUsedFactors().end());
        }
        if (filterModel) {
            factors.insert(filterModel->GetUsedFactors().begin(), filterModel->GetUsedFactors().end());
        }
        if (factorSet) {
            factors.insert(factorSet->GetUsedFactors().begin(), factorSet->GetUsedFactors().end());
        }
        if (extraFactors) {
            factors.insert(extraFactors->begin(), extraFactors->end());
        }
        p = Storage.insert(std::make_pair(hash, CalculateMasks(factors))).first;
        return p->second;
    }
}

TFactorsMaskCache::TFactorsMaskHash TFactorsMaskCache::FactorHash(const TString& modelName) const {
    return FnvHash<ui64>(modelName);
}

TFactorsMaskCache::TFactorsMaskHash TFactorsMaskCache::FactorHash(const NRTYFactors::TUsedFactors& factors) const {
    TFactorsMaskHash hash = 0;
    for (auto&& f : factors) {
        hash ^= FnvHash<ui64>(&f, sizeof(f));
    }
    return hash;
}

NRTYServer::TFactorMasks TFactorsMaskCache::CalculateMasks(const NRTYFactors::TUsedFactors& factors) const {
    NRTYServer::TFactorMasks result;
    result.DynamicMask = CalculateDynamicFactorsMask(factors);
    result.DynamicChunks = CalculateDynamicFactorsChunks(factors);
    result.RTYMask = CalculateRTYFactorMask(factors);
    return result;
}

TCombinedFactorMask TFactorsMaskCache::CalculateDynamicFactorsMask(const NRTYFactors::TUsedFactors& factors) const {
    TCombinedFactorMask result(/*setAll=*/false);

    // We assume here that only the 'web_production' slice depends on FactorMask.
    // RTYServer is the only application where TCombinedFactorMask is created with setAll=false, and then is used to fine-tune the basesearch.
    const IFactorsInfo* webProductionInfo = ::GetWebFactorsInfo();
    CHECK_WITH_LOG(webProductionInfo != nullptr);

    TVector<TString> names;
    for (auto&& f : factors) {
        if (Config.IsFictiveFactor(f)) {
            continue;
        }

        const NRTYFactors::TFactor& factor = *Config.GetFactorByFormulaIndex(f);
        switch (factor.FactorType) {
        case NRTYFactors::ftZone:
            result.Group.BM25F() = true;
            break;
        case NRTYFactors::ftDynamic:
            if (webProductionInfo->GetFactorIndex(factor.Name.c_str()).Defined()) {
                names.push_back(factor.Name);
            }
            break;
        default:
            break;
        }
    }

    FillFactorGroupMask(result, names, *webProductionInfo);
    return result;
}

NRTYFactors::TFactorChunks TFactorsMaskCache::CalculateDynamicFactorsChunks(const NRTYFactors::TUsedFactors& factors) const {
    TMap<ui32, ui32> remap;
    for (auto&& f : factors) {
        if (Config.IsFictiveFactor(f)) {
            continue;
        }

        const NRTYFactors::TFactor& factor = *Config.GetFactorByFormulaIndex(f);
        switch (factor.FactorType) {
        case NRTYFactors::ftDynamic:
            remap[factor.IndexBase.Offset] = factor.IndexGlobal;
            break;
        default:
            break;
        }
    }

    NRTYFactors::TFactorChunks result;
    Config.BuildChunks(result, remap);
    return result;
}

NRTYFactors::TRTYFactorMask TFactorsMaskCache::CalculateRTYFactorMask(const NRTYFactors::TUsedFactors& factors) const {
    NRTYFactors::TRTYFactorMask result;

    result.QSFactorsInfo.Init(Config.GetQuerySpecFactors().size());
    result.CSFactorsInfo.Init(Config.GetCommonStatFactors().size());

    const IFactorsInfo* webProductionInfo = ::GetWebFactorsInfo();
    const NRTYFactors::TBaseDomain* slicedDomain = webProductionInfo ? Config.GetBaseDomain() : nullptr;

    TVector<NRTYFactors::TFactor> used;
    for (auto&& f : factors) {
        if (Config.IsFictiveFactor(f)) {
            continue;
        }

        const NRTYFactors::TFactor& factor = *Config.GetFactorByFormulaIndex(f);
        switch (factor.FactorType) {
        case NRTYFactors::ftRtyTimeDynamic: {
            const NRTYFactors::TFactor* base = Config.GetFactorByName(factor.BaseFactorName);
            Y_ASSERT(base);
            result.StaticFactors = true;
            result.DynamicFactors = true;
            result.TimeFactors.push_back(NRTYFactors::TTimeFactorData(base->IndexGlobal, factor.IsInv, factor.IndexGlobal));
            break;
        }
        case NRTYFactors::ftRtyDynamic:
            result.DynamicFactors = true;
            used.push_back(factor);
            break;
        case NRTYFactors::ftZone:
            result.ZoneFactors = true;
            break;
        case NRTYFactors::ftCommonStatic:
            result.CSFactorsInfo.Set(GetBlockNum<NRTYFactors::TCSFactorsHashList>(factor.Name, Config.GetCommonStatFactors()));
            break;
        case NRTYFactors::ftQS:
            result.QSFactorsInfo.Set(GetBlockNum<NRTYFactors::TQSFactorsHashList>(factor.Name, Config.GetQuerySpecFactors()));
            break;
        case NRTYFactors::ftStatic:
            result.StaticFactors = true;
            break;
        case NRTYFactors::ftDynamic:
            if (slicedDomain) {
                TMaybe<NFactorSlices::TFactorIndex> webProductionFactorId = slicedDomain->GetWebProductionFactorId(factor.IndexBase);
                if (webProductionFactorId) {
                    const bool isLb = webProductionInfo->HasTagId(*webProductionFactorId, TFactorInfo::TG_LINGBOOST)
                                      || webProductionInfo->HasTagId(*webProductionFactorId, TFactorInfo::TG_TEXT_MACHINE);
                    const bool isLegacyFactorAnn = !isLb && webProductionInfo->HasTagId(*webProductionFactorId, TFactorInfo::TG_ANNOTATION_NOFILTER);
                    if (isLegacyFactorAnn)
                        result.UsesFactorAnnWithoutTm = true;
                    if (isLb)
                        result.UsesWebTextMachine = true;
                }
            }
            break;
        case NRTYFactors::ftUser:
        case NRTYFactors::ftSuggest:
        case NRTYFactors::ftSuggestDict:
            break;
        default:
            FAIL_LOG("Unknown factor type %d", (ui32)factor.FactorType);
        }
    }

    for (auto&& factor : used) {
        if (factor.Name == "LiveTime")
            result.IndexLiveTime = factor.IndexGlobal;
        else if (factor.Name == "InvLiveTime")
            result.IndexInvLiveTime = factor.IndexGlobal;
        else if (factor.Name == "FreshnessDay")
            result.IndexFreshnessDay = factor.IndexGlobal;
        else if (factor.Name == "FreshnessWeek")
            result.IndexFreshnessWeek = factor.IndexGlobal;
        else if (factor.Name == "FreshnessMonth")
            result.IndexFreshnessMonth = factor.IndexGlobal;
        else if (factor.Name.StartsWith("Refine"))
            result.RefineFactors.push_back(NRTYFactors::TTimeFactorData(factor.IndexGlobal, false, factor.IndexGlobal));
        else {
            const bool isDpFeature = Config.IsDpFactor(factor.Name);
            result.PluginFactors.emplace_back(factor.Name, factor.IndexGlobal);
            if (!isDpFeature) {
                WARNING_LOG << "Unknown RTY dynamic factore '%s' will be zeroed" << factor.Name << Endl;
            }
        }
    }

    return result;
}
