#pragma once

#include "factor_calculate.h"
#include "factors_config.h"
#include "factors_reqparam.h"
#include "user_values_storage.h"

#include <saas/rtyserver/model/index.h>
#include <search/reqparam/reqparam.h>
#include <saas/rtyserver/search/cgi/rty_search_statistics.h>
#include <kernel/groupattrs/docsattrs.h>

#include <util/string/vector.h>

namespace NRTYFeatures {
    class TImportedFunctions;
}

namespace NRTYFactors {
    using TUserDefinedAttrsStorage = THashMap<std::pair<ui32, TString>, TString>;

    enum class EExprType {
        None,
        GroupingAttr,
        SecondaryKey
    };

    class TUserFactorsCalcer {
    public:
        typedef TSet<ui32> TIndexSet;

        class IFactorCalcer;
        typedef TSimpleSharedPtr<IFactorCalcer> TFactorCalcerPtr;

        class TFactorCalcerHolder {
        public:
            TFactorCalcerHolder(
                IFactorCalcer* calcer, ui32 index, const TOverridable<float>& defaultFactorValue, TQueryValuesStorage::TPtr valuesStorage, TAtomicSharedPtr<TUserDefinedAttrsStorage> attrsStorage, TRTYSearchStatistics& searchStatistics)
                : Calcer(calcer)
                , Index(index)
                , DefaultFactorValue(defaultFactorValue)
                , ValuesStorage(valuesStorage)
                , AttrsStorage(attrsStorage)
                , SearchStatistics(&searchStatistics)
            {}

            TFactorCalcerHolder() = default;

            inline void Calc(TCalcFactorsContext& ctx) const {
                try {
                    (*ctx.Factors)[Index] = Calcer->Calc(ctx);
                } catch (TFactorCalculateDefaultValueException& /*exp*/) {
                    if (!DefaultFactorValue.IsSet()) {
                        throw;
                    }
                    SearchStatistics->ReportUserFactorDefaulted();
                    (*ctx.Factors)[Index] = DefaultFactorValue.Get();
                }
            }

            inline void FillUsedFactors(TIndexSet& indexes) const {
                Calcer->FillUsedFactors(indexes);
            }

            inline void SetOrder(ui32 order) {
                Order = order;
            }

            inline ui32 GetOrder() const {
                return Order;
            }

            inline ui32 GetIndex() const {
                return Index;
            }

            inline bool operator < (const TUserFactorsCalcer::TFactorCalcerHolder& right) const {
                return Order < right.Order;
            }
            inline TFactorCalcerPtr& GetCalcer() {
                return Calcer;
            }
        private:
            TFactorCalcerPtr Calcer;
            ui32 Index = 0;
            TOverridable<float> DefaultFactorValue;
            ui32 Order = 0;
            // Cannot not remove it right now, it is used as TQueryValuesStorage* in other place, see SAASSUP-2853
            TQueryValuesStorage::TPtr ValuesStorage;
            TAtomicSharedPtr<TUserDefinedAttrsStorage> AttrsStorage;
            TRTYSearchStatistics* SearchStatistics{nullptr};
        };

        typedef THashMap<ui32, TFactorCalcerHolder> TCalcers;

        class IFactorCalcer : public ::NRTYFactors::IFactorCalculate {
            static void NotSupported(TStringBuf s) {
                ythrow yexception() << s << " is not supported";
            }
        public:
            virtual ~IFactorCalcer() {}

            virtual void FillUsedFactors(TIndexSet& indexes) const = 0;
            virtual void Init(TCalcers& /*calcers*/) {}

            // @brief A reflection-like technique, used by some functions to decapsulate arguments
            // (i.e. extract a name of the grouping attribute from the "#group_xyz" accessor)
            // @return The meaning of the returned value is specific to the EExprType used. An empty value means "not supported".
            virtual TString DescribeAs(EExprType) const {
                return Default<TString>();
            };
        };

        class TFormulaUserFactorsCalcer {
        public:
            TFormulaUserFactorsCalcer(const TUserFactorsCalcer& owner, const TSet<ui32>& usedFactors);

            inline void Calc(TCalcFactorsContext& ctx) const {
                TConfig::CopyFactors(CopyFactorsChunks, *ctx.Factors, *ctx.Factors);
                try {
                    for(TCalcers::const_iterator i = Calcers.begin(); i != Calcers.end(); ++i) {
                        i->Calc(ctx);
                    }
                } catch (...) {
                    SearchStatistics.ReportUserFactorCalcFailed();
                    throw;
                }
            }

            inline const TIndexSet& GetUsedFactors() const {
                return UsedFactors;
            }

            inline const TUserDefinedAttrsStorage* GetUserDefinedAttrs() const {
                return UserDefinedAttrs;
            }

            inline bool IsEmpty() const {
                return CopyFactorsChunks.empty() && Calcers.empty();
            }

        private:
            ui32 SetOrder(ui32 index);

        private:
            typedef TVector<TFactorCalcerHolder> TCalcers;
            TUserFactorsCalcer::TCalcers CommonCalcers;
            TCalcers Calcers;
            TFactorChunks CopyFactorsChunks;
            TIndexSet UsedFactors;
            TRTYSearchStatistics& SearchStatistics;
            const TUserDefinedAttrsStorage* UserDefinedAttrs;
        };

        //
        // TCachers
        //
        class TCachers {
        public:
            TQueryValuesStorage::TPtr ValuesStorage;
            TAtomicSharedPtr<TUserDefinedAttrsStorage> AttrsStorage;

        public:
            explicit TCachers(bool enableCacheInvalidation)
                : ValuesStorage(MakeIntrusive<TQueryValuesStorage>(enableCacheInvalidation))
                , AttrsStorage(MakeAtomicShared<TUserDefinedAttrsStorage>())
            {
            }
        };

    public:
        static TUserFactorsCalcer* Create(
            const TVector<TString>& calcDirectives, const TVector<TString>& storeDirectives,
            const TFactorPoliteness& reqParams, const TMap<ui32, ui32>& setSrcToDst,
            const TConfig& factorConfig, const NGroupingAttrs::TDocsAttrs* docsAttrs,
            const NRTYFeatures::TImportedFunctions* imports, const IDDKManager* ddkManager,
            const TRequestParams* rp, TRTYSearchStatistics& searchStatistics,
            bool enableCacheInvalidation
        );
        TFormulaUserFactorsCalcer* CreateFormulaCalcer(const TSet<ui32>& usedFactors) const;
        const TUserDefinedAttrsStorage* GetUserDefinedAttrs() const;

        THolder<TCachers> Cachers;

    private:
        typedef THashMap<ui32, ui32> TSetDstToSrc;
        TUserFactorsCalcer(
            const TCalcers& calcers, const TMap<ui32, ui32>& setSrcToDst, const TConfig& config, THolder<TCachers>&& cachers, TRTYSearchStatistics& searchStatistics);

        bool CheckCircles(TFactorCalcerHolder& calcer, TString& errors);
        TCalcers Calcers;
        TSetDstToSrc SetDstToSrc;
        const TConfig& Config;
        TRTYSearchStatistics& SearchStatistics;
    };

    namespace NCalcers {
        struct TInitCalcerParams {
            const TConfig& FactorConfig;
            const NGroupingAttrs::TDocsAttrs* DocsAttrs{nullptr};
            const NRTYFeatures::TImportedFunctions* Imports{nullptr};
            const IDDKManager* DDKManager{nullptr};
            const TRequestParams* RP{nullptr};
            TRTYSearchStatistics& searchStatistics;
            TQueryValuesStorage* ValuesStorage{nullptr};
            TUserDefinedAttrsStorage* UserDefinedAttrs{nullptr};
        };

        class TUserFunction {
        public:
            virtual void Init(const TStringBuf& src, const TStringBuf& name, size_t& pos, const TInitCalcerParams& params);
            virtual void Validate(const TStringBuf& /*name*/);

        protected:
            typedef TSimpleSharedPtr<TUserFactorsCalcer::IFactorCalcer> TArg;
            typedef TVector<TArg> TArgs;
            TArgs Args;
            const IDDKManager* DDKManager {nullptr};
            const TRequestParams* RP {nullptr};
            TRTYSearchStatistics* SearchStatistics;
            TQueryValuesStorage* ValuesStorage;
            TUserDefinedAttrsStorage* UserDefinedAttrs;
        };

        class TFactorCalcerFunc: public TUserFunction, public TUserFactorsCalcer::IFactorCalcer {
            void UpdateDependsOnDoc() const override;

        public:
            using TUserFactorsCalcer::IFactorCalcer::Init;
            using TUserFunction::Init;
            using TUserFunction::Validate;

            void FillUsedFactors(TUserFactorsCalcer::TIndexSet& indexes) const override;
        };
        typedef NObjectFactory::TObjectFactory<TFactorCalcerFunc, TStringBuf> TFuncFactory;

        class TStoreValueFunc: public TUserFunction, public IStoreValueCalcer {
            void UpdateDependsOnDoc() override;
        public:
            virtual void Init(const TStringBuf& src, const TStringBuf& name, size_t& pos, const TInitCalcerParams& params) override;

            using TUserFunction::Validate;
        };
        typedef NObjectFactory::TObjectFactory<TStoreValueFunc, TStringBuf> TStoreValueFuncFactory;
    }
}
