#pragma once

#include <util/generic/hash.h>
#include <util/generic/string.h>
#include <util/string/builder.h>

namespace NSrvKernel {
    class TBackendConfig;

    class IBackendProtocolImpl {
    public:
        virtual ~IBackendProtocolImpl() = default;

        virtual TStringBuf GetProtocolName() const = 0;
    };

    template <typename T>
    class TBackendProtocolImpl
        : public IBackendProtocolImpl
    {
    public:
        TStringBuf GetProtocolName() const final {
            return T::ProtocolName();
        }
    };

    class TBackendProtocolFactory {
    public:
        using TFactoryFunc = std::function<THolder<IBackendProtocolImpl>(const TBackendConfig& config)>;

        template <typename T>
        bool HasProtocolImpl() const {
            return Map_.contains(T::ProtocolName());
        }

        template <typename T>
        void RegisterProtocolImpl() {
            RegisterProtocolImpl<T>([](const TBackendConfig& config) {
                return MakeHolder<T>(config);
            });
        }

        template <typename T>
        void RegisterProtocolImpl(TFactoryFunc factoryFunc) {
            Map_[T::ProtocolName()] = std::move(factoryFunc);
        }

        THolder<IBackendProtocolImpl> Create(TStringBuf protocol, const TBackendConfig& config) {
            if (auto* f = Map_.FindPtr(protocol)) {
                auto res = (*f)(config);
                Y_VERIFY(res->GetProtocolName() == protocol);
                return res;
            }

            return nullptr;
        }
    private:
        THashMap<TString, TFactoryFunc> Map_;
    };

    class TBackendProtocols {
    public:
        template <typename T>
        T* GetImpl() const {
            if (const THolder<IBackendProtocolImpl>* p = Impls_.FindPtr(T::ProtocolName())) {
                IBackendProtocolImpl* res = p->Get();
                Y_VERIFY(res->GetProtocolName() == T::ProtocolName());
                return static_cast<T*>(res);
            }

            return nullptr;
        }

        IBackendProtocolImpl* AddImpl(TBackendProtocolFactory& factory, TStringBuf protocol, const TBackendConfig& config) {
            THolder<IBackendProtocolImpl> impl = factory.Create(protocol, config);
            Y_ENSURE(impl, TStringBuilder() << "unknown protocol [" << protocol << "]");
            Y_VERIFY(impl->GetProtocolName() == protocol);
            Impls_[protocol] = std::move(impl);
            return Impls_[protocol].Get();
        }
    private:
        THashMap<TString, THolder<IBackendProtocolImpl>> Impls_;
    };
}
