#pragma once

#include <library/cpp/config/sax.h>

#include <util/generic/hash.h>
#include <util/generic/mapfindptr.h>
#include <util/generic/ptr.h>
#include <util/generic/singleton.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>


namespace NSrvKernel {

class TModuleParams;

template <class I>
class INodeHandle {
public:
    virtual ~INodeHandle() noexcept = default;

    const TString& Name() const noexcept {
        return DoName();
    }

    THolder<I> Construct(const TModuleParams& mp) const {
        return THolder<I>(DoConstruct(mp));
    }

private:
    virtual const TString& DoName() const /* noexcept */ = 0;
    virtual I* DoConstruct(const TModuleParams& mp) const = 0;
};

template <class I>
class INodeFactory {
public:
    template <class... Args>
    THolder<I> Load(const TStringBuf& name, const TModuleParams& mp, Args&&... args) const {
        if (const INodeHandle<I>* const handle = SelectHandle(name)) {
            return handle->Construct(mp, std::forward<Args>(args)...);
        }

        return nullptr;
    }

    THolder<I> MustLoad(const TStringBuf& name, const TModuleParams& mp) const try {
        if (THolder<I> node = Load(name, mp)) {
            return node;
        }

        ythrow yexception() << "can not load node(" << name << ')';
    } catch (const NConfig::TConfigParseError& e) {
        ythrow NConfig::TConfigParseError() << "Error when loading node <" << name << ">:\n" << e.what();
    }

    INodeHandle<I>* SelectHandle(const TStringBuf& name) const noexcept {
        return DoSelectHandle(name);
    }

    TVector<TString> ListHandleNames() const {
        return DoListHandleNames();
    }

    virtual ~INodeFactory() = default;

private:
    virtual INodeHandle<I>* DoSelectHandle(const TStringBuf& name) const noexcept = 0;
    virtual TVector<TString> DoListHandleNames() const = 0;
};

template <class I>
class TNodeFactory: public INodeFactory<I> {
public:
    TNodeFactory() noexcept
        : TNodeFactory(nullptr)
    {}

    TNodeFactory(const INodeFactory<I>* slave) noexcept
        : Slave_(slave)
    {}

    template <class T>
    void AddHandle(INodeHandle<I>* const handle, const T& name) {
        RegistredHandles_[name] = handle;
    }

    void AddHandle(INodeHandle<I>* const handle) {
        AddHandle(handle, handle->Name());
    }

private:
    INodeHandle<I>* DoSelectHandle(const TStringBuf& name) const noexcept override {
        if (INodeHandle<I>* const* const handle = MapFindPtr(RegistredHandles_, name)) {
            return *handle;
        }

        if (Slave_) {
            return Slave_->SelectHandle(name);
        }

        return nullptr;
    }

    TVector<TString> DoListHandleNames() const override {
        TVector<TString> res;

        for (const auto& it : RegistredHandles_) {
            res.push_back(it.first);
        }

        return res;
    }
private:
    THashMap<TString, INodeHandle<I>*> RegistredHandles_;
    const INodeFactory<I>* const Slave_ = nullptr;
};


template <class I, class T, const char* N>
class TGenericNode;

template <class I, class T, const char* N>
class TGenericNodeBase: public I {
private:
    class THandle: public INodeHandle<I> {
    private:
        const TString& DoName() const noexcept override {
            return Name_;
        }

        I* DoConstruct(const TModuleParams& mp) const override {
            return TGenericNode<I, T, N>::Construct(mp);
        }

    private:
        TString Name_{N};
    };

private:
    static inline I* Construct(const TModuleParams& mp) {
        return new T(mp);
    }

public:
    static inline INodeHandle<I>* Handle() {
        return Singleton<THandle>();
    }
};

template <class I, class T, const char* N>
class TGenericNode : public TGenericNodeBase<I,T,N> {};

}

#define NODEBASE(I) \
template <class T, const char* N> \
class TGenericNode<I, T, N> : public TGenericNodeBase<I, T, N>

#define NODEIMPL_BASE(I, T, X, B) \
namespace { \
    namespace N ## X { \
        constexpr char NAME[] = #X; \
        struct T; \
    } \
    using namespace N ## X; \
} \
struct N ## X::T final : public B<I, T, NAME>, public NConfig::IConfig::IFunc

#define NODEIMPL(I, T, X) NODEIMPL_BASE(I, T, X, TGenericNode)
