#pragma once

#include "iface.h"
#include "events.h"
#include "node.h"

#include <balancer/kernel/helpers/default_instance.h>
#include <library/cpp/proto_config/config.h>

#include <util/generic/singleton.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/string/builder.h>
#include <util/string/strip.h>

namespace NSrvKernel {
    struct TConstructingListItem: public TIntrusiveListItem<TConstructingListItem> {
        TConstructingListItem(const IModuleHandle* handle, TIntrusiveList<TConstructingListItem>& t) noexcept
            : Handle(handle)
        {
            t.PushBack(this);
        }

        const IModuleHandle* const Handle = nullptr;
    };

    struct TConstructingList: public TIntrusiveList<TConstructingListItem>
    {
        static TConstructingList& Instance();
    };

    NODEBASE(IModule)
        , public TModuleParams
    {
    public:
        using TModuleBase = TGenericNode;

        TGenericNode(const TModuleParams& mp) noexcept
            : TModuleParams(mp)
        {
            if (mp.Modules) {
                mp.Modules->PushBack(this);
            }
        }

        static IModule* Construct(const TModuleParams& mp) {
            // The list element pushes on construction and pops on destruction on scope exit.
            const TConstructingListItem constructing{ TGenericNode::Handle(), TConstructingList::Instance() };

            THolder<T> module = MakeHolder<T>(mp);
            module->InitTypeName();
            module->CheckConstraints();

            if (!module->CanWorkWithoutHTTP()) {
                module->CheckHasParent("http");
            }

            return module.Release();
        }

        TStringBuf Name() const {
            return DoHandle()->Name();
        }

        template <class TOp>
        bool CheckParents(TOp&& t) const {
            const auto& list = TConstructingList::Instance();
            auto penult = list.End();
            penult.Prev();
            for (auto iter = list.Begin(); iter != penult; ++iter) {
                if (t(iter->Handle->Name())) {
                    return true;
                }
            }
            return false;
        }

        void CheckHasParent(TStringBuf module) const {
            if (!CheckParents([&](TStringBuf name) {
                return module == name;
            })) {
                ythrow TConfigParseError() << "Module <" << module << "> required as a parent for <" << Name() << ">";
            }
        }

    protected:
        template <typename TArg>
        void RegisterEvent(TString regexp, TString name, void (TArg::*func)(TEventData& event) noexcept, TArg* arg) {
            EventHandler_.Register(std::move(regexp), std::move(name), func, arg);
        }

        void ParseEventConfig(NConfig::IConfig* config) {
            ParseMap(config, [this](const auto& k, auto* value) {
                DoConsumeEvent(k, value->AsString());
            });
        }

        static void ApplyCallback(
            NProtoConfig::TUnknownFieldCb cb,
            const NProtoConfig::TKeyStack&,
            const TString& key,
            NConfig::IConfig::IValue* value
        ) {
            cb(key, value);
        }

        static void ApplyCallback(
            NProtoConfig::TStackUnknownFieldCb cb,
            const NProtoConfig::TKeyStack& keys,
            const TString& key,
            NConfig::IConfig::IValue* value
        ) {
            cb(keys, key, value);
        }

        template <class TProtoConfig, class TCallback>
        TProtoConfig ParseProtoConfig(TCallback&& unknownFieldCb) {
            using namespace NProtoConfig;
            TProtoConfig config;
            ParseConfig(*Config, config,
                [&](const TKeyStack& keys, const TString& key, NConfig::IConfig::IValue* value) {
                    if (key == "events") {
                        ParseEventConfig(value->AsSubConfig());
                    } else {
                        ApplyCallback(std::forward<TCallback>(unknownFieldCb), keys, key, value);
                    }
                }
            );
            return config;
        }

        virtual void DoConsumeEvent(TString, TString) {
        }

    private:
        IModuleHandle* DoHandle() const noexcept override {
            return TGenericNode::Handle();
        }

        const TEventHandler* DoEventHandler() const noexcept override {
            return &EventHandler_;
        }

    private:
        TEventHandler EventHandler_;
    };

    class TTlsBase : TNonCopyable {
    };

    template <class I, class T, const char* N>
    class TModuleWithSubModule : public TGenericNode<I, T, N> {
    public:
        using TModuleBase = TModuleWithSubModule;
        using TGenericNode<I, T, N>::TGenericNode;
        TBackendCheckResult CheckBackends(IWorkerCtl& ctl, bool runtimeCheck) const noexcept override {
            if (Submodule_) {
                return Submodule_->CheckBackends(ctl, runtimeCheck);
            }
            return I::CheckBackends(ctl, runtimeCheck);
        }
    protected:
        THolder<I> Submodule_;
    };

    template <class T, class Tls, const char* N, template <typename, typename, const char*> class Base>
    class TModuleWithTLS : public Base<IModule, T, N> {
    public:
        using TModuleBase = TModuleWithTLS;

        TModuleWithTLS() = delete;
        explicit TModuleWithTLS(const TModuleParams& mp)
            : Base<IModule, T, N>(mp)
            , Tls_(mp.Control->GetCountOfChildren() + 1)
        {}

        void DoInit(IWorkerCtl* process) noexcept final {
            try {
                Y_VERIFY(process->WorkerId() < Tls_.size());
                Y_VERIFY(!Tls_[process->WorkerId()].Get());
                Tls_[process->WorkerId()] = std::move(DoInitTls(process));
            } catch (...) {
                Cerr << "Unexpected exception while init balancer module " << CurrentExceptionMessage() << Endl;
                throw;
            }
        }

        TError DoRun(const TConnDescr& descr) const final {
            return DoRun(descr, *Tls_[descr.Process().WorkerId()]);
        }

        TBackendCheckResult CheckBackends(IWorkerCtl& proc, bool runtimeCheck) const noexcept final {
            return CheckBackends(proc, runtimeCheck, *Tls_[proc.WorkerId()]);
        }

        void DoDispose(IWorkerCtl* process) noexcept final {
            try {
                DoDispose(process, *Tls_[process->WorkerId()]);
                Tls_[process->WorkerId()].Destroy();
            } catch (...) {
                Cerr << "Unexpected exception while dispose balancer module " << CurrentExceptionMessage() << Endl;
                throw;
            }
        }

    protected:
        Tls& GetTls(IWorkerCtl* process) const noexcept {
            return *Tls_[process->WorkerId()];
        }

        Tls& GetTlsById(size_t id) const noexcept {
            return *Tls_[id];
        }

    private:
        virtual THolder<Tls> DoInitTls(IWorkerCtl*) = 0;
        virtual TError DoRun(const TConnDescr&, Tls&) const = 0;
        virtual TBackendCheckResult CheckBackends(IWorkerCtl& ctl, bool runtimeCheck, Tls&) const noexcept {
            return Base<IModule, T, N>::CheckBackends(ctl, runtimeCheck);
        }
        virtual void DoDispose(IWorkerCtl*, Tls&) {}

    private:
        TVector<THolder<Tls>> Tls_;
    };


    class TSubLoader: public THolder<IModule>, public TModuleParams, public IConfig::IFunc {
    public:
        TSubLoader(const TModuleParams& mp)
            : TModuleParams(mp)
        {
            Config->ForEach(this);
        }

    private:
        START_PARSE {
            Reset(Loader->MustLoad(key, Copy(value->AsSubConfig())).Release());

            return;
        } END_PARSE
    };

    void PrintOnce(TString message);

    class TPrintOnceScope {
    public:
        TPrintOnceScope();
        ~TPrintOnceScope();
    };

    template <typename T>
    class TConfigHolder {
    public:
        TConfigHolder& operator=(const T& config) {
            Config_ = config;
            return *this;
        }

        const T* operator->() const {
            return &Config_;
        }
    private:
        T Config_;
    };

}

#define MODULE(X) NODEIMPL(IModule, TModule, X)

#define MODULE_BASE(X, B) NODEIMPL_BASE(IModule, TModule, X, B)

#define Y_TLS(X) \
namespace { \
    namespace N ## X { \
        constexpr char NAME[] = #X; \
        struct TTls; \
        struct TModule; \
    } \
    using namespace N ## X; \
} \
struct N ## X::TTls final : public TTlsBase

#define MODULE_WITH_TLS_BASE(X, B) \
struct N ## X::TModule final : public TModuleWithTLS<TModule, TTls, NAME, B>, public NConfig::IConfig::IFunc

#define MODULE_WITH_TLS(X) MODULE_WITH_TLS_BASE(X, TGenericNode)

#define Y_WARN_ONCE(message) PrintOnce(TStringBuilder() << "WARNING in " << NAME << ": " << message)
