#pragma once

#include "factor_calculate.h"
#include "user_factors_calcer.h"
#include "user_values_storage.h"

#include <util/generic/array_ref.h>
#include <util/generic/hash_set.h>
#include <util/generic/yexception.h>
#include <util/string/cast.h>

struct TRTYFunctionCtx {
    TRTYSearchStatistics* SearchStatistics = nullptr;
    //NRTYFactors::TCachedValuesStorage* CalcerCache = nullptr;
};

namespace NRTYFeatures {
    //
    // TFactorCalcerUserFuncEx: метод компонеты, доступный для использования в relev=calc (в TUserFactorsCalcer) полнотекстового поиска
    //
    struct TFactorCalcerUserFuncEx {
        using TFunc = std::function<float(ui32, TArrayRef<const float>, const TRTYFunctionCtx&)>;
    public:
        TFunc Callable_;
        ui32 MinArgsCount;
        ui32 MaxArgsCount;
    public:
        float operator()(ui32 docId, TArrayRef<const float> args, const TRTYFunctionCtx& uctx) const {
            Y_ASSERT(Callable_);
            return Callable_(docId, args, uctx);
        }

        operator bool() const {
            return !!Callable_;
        }
    };

    //
    // TFactorCalcerUserFunc: метод компонеты, доступный для использования в relev=calc (в TUserFactorsCalcer) полнотекстового поиска
    //
    // Упрощённый вариант, без использования TRTYFunctionCtx
    //
    struct TFactorCalcerUserFunc : public TFactorCalcerUserFuncEx {
    };

    //
    // TFactorCalcerGenericUserFunc: вычислитель выражения, создаваемый из компоненты
    //
    struct TFactorCalcerGenericUserFunc {
        using IFactorCalculate = NRTYFactors::IFactorCalculate;
        using TFunc = std::function<THolder<IFactorCalculate>(TCalcFactorsContext&, const TRTYFunctionCtx&, TArrayRef<const NRTYFactors::IFactorCalculate*>)>;
        public:
            TFunc Callable_;
            ui32 MinArgsCount;
            ui32 MaxArgsCount;

        THolder<IFactorCalculate> operator()(TCalcFactorsContext& ctx, const TRTYFunctionCtx& uctx, TArrayRef<const NRTYFactors::IFactorCalculate*> args) const {
            Y_ASSERT(Callable_);
            return Callable_(ctx, uctx, args);
        }

        operator bool() const {
            return !!Callable_;
        }
    };

    //
    // TComponentGtaFunc: метод компоненты, доступный для получения GTA в репорте полнотекстового поиска через gta=_<methodName>
    //
    using TComponentGtaFunc = std::function<TString(ui32)>;

    //
    // TFactorCalcerLayeredFunc: функция, позволяющая указать стрим (подраздел) индекса при применении
    //
    struct TFactorCalcerLayeredFunc {
        using TFunc = std::function<float(ui32, i64, TArrayRef<const float>, const TRTYFunctionCtx&)>;
    public:
        TFunc Callable_;
        ui32 MinArgsCount;
        ui32 MaxArgsCount;
    public:
        float operator()(ui32 docId, i64 subId, TArrayRef<const float> args, const TRTYFunctionCtx& uctx) const {
            Y_ASSERT(Callable_);
            return Callable_(docId, subId, args, uctx);
        }

        operator bool() const {
            return !!Callable_;
        }
    };

    //
    // TImportedFunctions: Класс-коллеция std::function, "поднятых" из компонент по запросу
    //
    class TImportedFunctions {
    public:
        struct TData {
            THashMap<TString, TFactorCalcerUserFuncEx> UserFuncs;
            THashMap<TString, TFactorCalcerGenericUserFunc> GenericUserFuncs;
            THashMap<TString, TFactorCalcerLayeredFunc> LayeredFuncs;
            THashMap<TString, TComponentGtaFunc> ComponentGtas;
        };

    public:
        Y_FORCE_INLINE const TData& GetFunctions() const {
            return Functions;
        }

    protected:
        friend class TImportedFunctionsBuilder;

        TData& MutableFunctions() {
            return Functions;
        }

    private:
        TData Functions;
    };

    //
    // TImportedFunctionsBuilder: билдер для TImportedFunctions (синтаксический сахар на темплейтах)
    //
    class TImportedFunctionsBuilder final {
    private:
        TImportedFunctions::TData& Functions;
        THashSet<TString> RequestedItems;

    public:
        TImportedFunctionsBuilder(TImportedFunctions& f)
            : Functions(f.MutableFunctions())
        {
        }

        void Request(TStringBuf name) {
            RequestedItems.emplace(name);
        }

        /// Опубликовать метод класса
        template<typename TCallable, typename T, typename TMember>
        void Add(TMember method, T This, const char* name, ui32 nArgs);

        template<typename TCallable, typename T, typename TMember>
        void Add(TMember method, T This, const char* name, ui32 minArgs, ui32 maxArgs);

        /// Опубликовать анонимную функцию для Gta
        template<typename TFoo>
        void AddGta(const char* name, TFoo func);

        /// Опубликовать метод класса, с преобразованием ToString, для Gta
        template<typename T, typename TMember>
        void AddGta(TMember method, T This, const char* name);

    private:
        bool ClaimRequested(const char* name) {
            auto iRequested = RequestedItems.find(name);
            if (iRequested == RequestedItems.end())
                return false;
            RequestedItems.erase(iRequested);
            return true;
        }

    public:
        const THashSet<TString>& GetMissing() const {
            return RequestedItems;
        }
    };

    //
    // Template helpers for TImportedFunctionsBuilder::Add
    //
    namespace NPrivate {
        namespace {
            using namespace std::placeholders;
            // some template typetraits that call an appropriate version of std::bind
            template<typename TItem>
            struct TFuncSetter;

            template<>
            struct TFuncSetter<TFactorCalcerUserFuncEx> {
                using TItem = TFactorCalcerUserFuncEx;

                template<typename T, typename TMethod>
                static void Set(TItem& slot, T This, TMethod method, ui32 minArgs, ui32 maxArgs) {
                    slot = { std::bind(method, This, _1, _2, _3), minArgs, maxArgs };
                }

                static THashMap<TString, TItem>& AsItems(TImportedFunctions::TData& data) {
                    return data.UserFuncs;
                }
            };

            template<>
            struct TFuncSetter<TFactorCalcerUserFunc> {
                using TItem = TFactorCalcerUserFuncEx;

                template<typename T, typename TMethod>
                static void Set(TItem& slot, T This, TMethod method, ui32 minArgs, ui32 maxArgs) {
                    auto foo = [This, method](ui32 docId, TArrayRef<const float> args, const TRTYFunctionCtx&) {
                        return (*This.*method)(docId, args);
                    };
                    slot = { foo, minArgs, maxArgs };
                }

                static THashMap<TString, TItem>& AsItems(TImportedFunctions::TData& data) {
                    return data.UserFuncs;
                }
            };

            template<>
            struct TFuncSetter<TFactorCalcerGenericUserFunc> {
                using TItem = TFactorCalcerGenericUserFunc;

                template<typename T, typename TMethod>
                static void Set(TItem& slot, T This, TMethod method, ui32 minArgs, ui32 maxArgs) {
                    slot = { std::bind(method, This, _1, _2, _3), minArgs, maxArgs };
                }

                static THashMap<TString, TItem>& AsItems(TImportedFunctions::TData& data) {
                    return data.GenericUserFuncs;
                }
            };

            template<>
            struct TFuncSetter<TFactorCalcerLayeredFunc> {
                using TItem = TFactorCalcerLayeredFunc;

                template<typename T, typename TMethod>
                static void Set(TItem& slot, T This, TMethod method, ui32 minArgs, ui32 maxArgs) {
                    TFactorCalcerLayeredFunc::TFunc foo =
                    slot = { std::bind(method, This, _1, _2, _3, _4), minArgs, maxArgs };
                }

                static THashMap<TString, TItem>& AsItems(TImportedFunctions::TData& data) {
                    return data.LayeredFuncs;
                }
            };

            template<>
            struct TFuncSetter<TComponentGtaFunc> {
                using TItem = TComponentGtaFunc;

                template<typename T, typename TMethod>
                static void Set(TItem& slot, T This, TMethod method, ui32) {
                    slot = std::bind(method, This, _1);
                }

                static THashMap<TString, TItem>& AsItems(TImportedFunctions::TData& data) {
                    return data.ComponentGtas;
                }
            };
        }
    }

    template<typename TCallable, typename T, typename TMember>
    void TImportedFunctionsBuilder::Add(TMember method, T This, const char* name, ui32 nArgs) {
        Add<TCallable>(method, This, name, nArgs, nArgs);
    }

    template<typename TCallable, typename T, typename TMember>
    void TImportedFunctionsBuilder::Add(TMember method, T This, const char* name, ui32 minArgs, ui32 maxArgs) {
        if (!ClaimRequested(name))
            return;

        using THelper = NRTYFeatures::NPrivate::TFuncSetter<TCallable>;
        THashMap<TString, typename THelper::TItem>& items = THelper::AsItems(Functions);
        THelper::Set(items[name], This, method, minArgs, maxArgs);
    }

    template<typename TFoo>
    void TImportedFunctionsBuilder::AddGta(const char* name, TFoo func) {
        if (!ClaimRequested(name))
            return;

        using THelper = NRTYFeatures::NPrivate::TFuncSetter<TComponentGtaFunc>;
        THelper::AsItems(Functions)[name] = func;
    }

    template<typename T, typename TMember>
    void TImportedFunctionsBuilder::AddGta(TMember method, T This, const char* name) {
        AddGta(name, [This, method](ui32 docId) -> TString {
            return ToString((*This.*method)(docId));
        });
    }
}
