#pragma once

#include <saas/rtyserver/factors/factor.h>
#include <saas/rtyserver/factors/factors_description.h>
#include <saas/rtyserver/indexer_core/document_parser.h>
#include <saas/rtyserver/indexer_core/parsed_document.h>

#include "erf_global.h"

class TRTYErfIndexComponent;

namespace NRTYFactors {
    class TConfig;
}

class TRTYErfParsedStorage {
public:
    typedef NRTYServer::TMessage::TFactorValues::TAction TRTYErfAction;
    typedef NRTYServer::TMessage::TFactorValues::TValue TFactorValue;
    typedef ::google::protobuf::RepeatedPtrField< TProtoStringType> TRTYFactorsNamesProto;

public:
    TRTYErfParsedStorage()
        : Values(0)
        , FactorsInfo(nullptr)
    {
    }

    virtual ~TRTYErfParsedStorage() {}

    virtual void SetFactorsInfo(const IRTYStaticFactors* factorsInfo)
    {
        FactorsInfo = factorsInfo;
        Checks.resize(FactorsInfo->GetStaticFactorsCount(), false);
        TBasicFactorStorage storage(FactorsInfo->GetStaticFactorsCount());
        Values.Swap(storage);
        Actions.resize(FactorsInfo->GetStaticFactorsCount(), NRTYServer::TMessage::TFactorValues::SET);
    }

    void SetValue(ui32 index, float value, TRTYErfAction action = NRTYServer::TMessage::TFactorValues::SET, bool noCheck = false) {
        CHECK_WITH_LOG(noCheck || !Checks[index]);
        CHECK_WITH_LOG(index < Values.Size());
        switch (action) {
        case NRTYServer::TMessage::TFactorValues::SET:
            Values[index] = value;
            break;
        case NRTYServer::TMessage::TFactorValues::ADD:
            Values[index] += value;
            break;
        default:
            FAIL_LOG("invalid action %i", (int)action);
        }
        Checks[index] = true;
    }

    const TBasicFactorStorage& GetValues() const {
        return Values;
    }

    const TVector<TRTYErfAction>& GetActions() const {
        return Actions;
    }

    const TVector<bool>& GetChecks() const {
        return Checks;
    }

    void DoMerge(const TRTYErfParsedStorage& entity, bool forceValueSubstitution);
    void Merge(const TRTYErfParsedStorage& entity) {
        DoMerge(entity, false);
    }
    void Update(const TRTYErfParsedStorage& entity) {
        DoMerge(entity, true);
    }

    bool Apply(TBasicFactorStorage& data) const;

    bool IsFull() const {
        for (ui32 i = 0; i < Checks.size(); ++i) {
            if (!Checks[i])
                return false;
        }
        return true;
    }

    void AddFactors(const TRTYFactorsNamesProto& namesInfo, const NRTYServer::TMessage::TFactorValues& valuesInfo, bool strictFactorCount);
    void SetIgnoredFactorsInfo(const IRTYStaticFactors* factorsInfo);
private:
    bool GetFactorValue(const TString& name, ui32 index, bool strict, TFactorValue& value) const;

private:
    TVector<bool> Checks;

protected:
    TBasicFactorStorage Values;
    TVector<TRTYErfAction> Actions;
    const IRTYStaticFactors* FactorsInfo;
    const IRTYStaticFactors* IgnoredFactorsInfo = nullptr;

    THashMap<TString, TFactorValue> ValueByFactorName;
};

class TRTYErfParsedEntity : public TParsedDocument::TParsedEntity, public TRTYErfParsedStorage {
protected:
    void DoApplyPatch(const TParsedDocument& doc) override;
public:
    TRTYErfParsedEntity(TConstructParams& params);

    bool FillFactorStorage(TFactorView& factorStorage) const override;

    void MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const override;
};

class TRTYErfComponentParser : public TComponentParser {
private:
    using TPruningFactors = TSet<TString>;
private:
    TPruningFactors PruningFactors;

public:
    explicit TRTYErfComponentParser(const TRTYErfIndexComponent& component);

    virtual void Parse(TParsingContext& context) const;
};

class TErfStaticFactors: public TSimpleStaticFactorsDescription {
public:
    explicit TErfStaticFactors(const NRTYFactors::TFactorsList& factorsList)
        : TSimpleStaticFactorsDescription(factorsList)
    {
    };

public:
    // IRTYStaticFactors
    virtual bool LoadErfBlock(TBasicFactorStorage& erfBlock, const TParsedDocument& document) const override {
        const TRTYErfParsedEntity* docData = document.GetComponentEntity<TRTYErfParsedEntity>(ERF_COMPONENT_NAME);
        VERIFY_WITH_LOG(docData, "Incorrect situation");
        VERIFY_WITH_LOG(docData->GetValues().Size() == GetStaticFactorsCount(), "Incorrect interaction Write method and doc description");
        CHECK_WITH_LOG(docData->IsFull());
        for (ui32 i = 0; i < GetStaticFactorsCount(); ++i) {
            erfBlock[i] = docData->GetValues()[i];
        }
        return true;
    }
};

class TRTYStaticFactorsConfig: public IRTYStaticFactors {
private:
    THolder<TErfStaticFactors> FactorsData;
public:
    explicit TRTYStaticFactorsConfig(const NRTYFactors::TConfig* config);

    ui32 GetStaticFactorsCount() const override {
        if (!!FactorsData)
            return FactorsData->GetStaticFactorsCount();
        else
            FAIL_LOG("Incorrect factors usage");
        return 0;
    }

    const NRTYFactors::TSimpleFactorDescription& GetFactor(ui32 index) const override {
        VERIFY_WITH_LOG(!!FactorsData, "Incorrect factors usage");
        return FactorsData->GetFactor(index);
    }

    bool LoadErfBlock(TBasicFactorStorage& erfBlock, const TParsedDocument& parsedDoc) const override {
        VERIFY_WITH_LOG(!!FactorsData, "Incorrect factors usage");
        return FactorsData->LoadErfBlock(erfBlock, parsedDoc);
    }
};
