#ifndef TILE_BASE_DEPENDENCY_REGISTRY_H
#define TILE_BASE_DEPENDENCY_REGISTRY_H

#pragma once

#include "tile/base/demangle.h"
#include "tile/base/internal/lazy_init.h"
#include "tile/base/internal/logging.h"
#include "tile/base/internal/macro.h"
#include "tile/base/internal/test_prod.h"
#include "tile/base/make_unique.h"
#include "tile/base/maybe_owning.h"
#include "tile/base/never_destroyed.h"
#include "tile/base/slice.h"

#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <utility>

// class dependency
#define TILE_DECLARE_CLASS_DEPENDENCY_REGISTRY(Registry, Interface, ...)                                               \
    extern ::tile::detail::dependency_registry::ClassRegistry<struct Registry, Interface, ##__VA_ARGS__> &Registry

#define TILE_DEFINE_CLASS_DEPENDENCY_REGISTRY(Registry, Interface, ...)                                                \
    ::tile::detail::dependency_registry::ClassRegistry<struct Registry, Interface, ##__VA_ARGS__> &Registry =          \
        **::tile::internal::LazyInit<::tile::NeverDestroyed<                                                           \
            ::tile::detail::dependency_registry::ClassRegistry<struct Registry, Interface, ##__VA_ARGS__>>>()

#define TILE_REGISTER_CLASS_DEPENDENCY(Registry, ImplementationName, ImplementationClassName, ...)                     \
    TILE_REGISTER_CLASS_DEPENDENCY_FACTORY(                                                                            \
        Registry, ImplementationName,                                                                                  \
        TILE_REGISTER_CLASS_DEPENDENCY_FUNCTION_AUX_INVOKE(ImplementationClassName, ##__VA_ARGS__))

#define TILE_REGISTER_CLASS_DEPENDENCY_FACTORY(Registry, Name, Factory)                                                \
    __attribute__((constructor)) static void TILE_INTERNAL_PP_CAT(                                                     \
        tile_reserved_registry_dependency_class_register_, __COUNTER__)()                                              \
    {                                                                                                                  \
        auto registry =                                                                                                \
            ::tile::internal::LazyInit<::tile::NeverDestroyed<typename std::decay<decltype(Registry)>::type>>()        \
                ->Get();                                                                                               \
        ::tile::detail::dependency_registry::AddToRegistry(registry, Name, Factory);                                   \
    }

// object dependency
#define TILE_DECLARE_OBJECT_DEPENDENCY_REGISTRY(Registry, Interface)                                                   \
    extern ::tile::detail::dependency_registry::ObjectRegistry<struct Registry, Interface> &Registry

#define TILE_DEFINE_OBJECT_DEPENDENCY_REGISTRY(Registry, Interface)                                                    \
    ::tile::detail::dependency_registry::ObjectRegistry<struct Registry, Interface> &Registry =                        \
        **::tile::internal::LazyInit<                                                                                  \
            ::tile::NeverDestroyed<::tile::detail::dependency_registry::ObjectRegistry<struct Registry, Interface>>>()

#define TILE_REGISTER_OBJECT_DEPENDENCY(Registry, ObjectName, PointerOrFactory)                                        \
    __attribute__((constructor)) static void TILE_INTERNAL_PP_CAT(                                                     \
        tile_reserved_registry_dependency_object_register_, __COUNTER__)()                                             \
    {                                                                                                                  \
        auto registry =                                                                                                \
            ::tile::internal::LazyInit<::tile::NeverDestroyed<typename std::decay<decltype(Registry)>::type>>()        \
                ->Get();                                                                                               \
        ::tile::detail::dependency_registry::AddToRegistry(registry, ObjectName, PointerOrFactory);                    \
    }

namespace tile {
namespace detail {
namespace dependency_registry {

#define TILE_REGISTER_CLASS_DEPENDENCY_FUNCTION_AUX_INVOKE(Interface, ...)                                             \
    ::tile::detail::dependency_registry::FactoryAux<Interface, ##__VA_ARGS__>

template<typename Interface, typename... Args>
std::unique_ptr<Interface>
FactoryAux(Args &&...args)
{
    return make_unique<Interface>(std::forward<Args>(args)...);
}

template<typename T, typename... Args>
void
AddToRegistry(T *registry, Args &&...args)
{
    registry->Register(std::forward<Args>(args)...);
}

template<typename Tag, typename Interface, typename... FactoryArgs>
class ClassRegistry {
    using Factory = std::function<std::unique_ptr<Interface>(FactoryArgs...)>;

public:
    Factory TryGetFactory(Slice name) const noexcept
    {
        auto iter = factories_.find(name.ToString());
        if (iter == factories_.end()) { return nullptr; }

        auto constructor_ptr = iter->second.get();
        return
            [constructor_ptr](FactoryArgs... args) { return (*constructor_ptr)(std::forward<FactoryArgs>(args)...); };
    }

    Factory GetFactory(const std::string &name) const noexcept
    {
        auto result = TryGetFactory(name);
        TILE_CHECK(result, "Class [{}] implement interface [{}] is not found.", name, GetTypeName<Interface>());

        return result;
    }

    std::unique_ptr<Interface> TryNew(Slice name, FactoryArgs... args) const
    {
        auto iter = factories_.find(name.ToString());
        if (iter == factories_.end()) { return nullptr; }

        auto &constructor = iter->second;
        return (*constructor)(std::forward<FactoryArgs>(args)...);
    }

    std::unique_ptr<Interface> New(const std::string &name, FactoryArgs... args) const
    {
        auto result = TryNew(name, std::forward<FactoryArgs>(args)...);
        TILE_CHECK(result, "Class [{}] implement interface [{}] is not found.", name, GetTypeName<Interface>());
        return result;
    }

private:
    template<typename T, typename... Args>
    friend void AddToRegistry(T *registry, Args &&...args);

    void Register(const std::string &name, Factory factory)
    {
        TILE_CHECK(factories_.find(name) == factories_.end(), "Duplicate class dependency: ");
        factories_[name] = make_unique<Factory>(std::move(factory));
    }

private:
    std::unordered_map<std::string, std::unique_ptr<Factory>> factories_;
};

template<typename Tag, typename Interface>
class ObjectRegistry {
public:
    Interface *TryGet(Slice name) const
    {
        auto iter = objects_.find(name.ToString());
        if (iter == objects_.end()) { return nullptr; }
        auto &&e = *iter->second;
        std::call_once(e.flag, [&] { e.object = e.initializer(); });
        return e.object.Get();
    }

    Interface *Get(Slice name) const
    {
        auto result = TryGet(name);
        TILE_CHECK(result, "Object dependency [{}] implementing interface [{}] is not found", name,
                   GetTypeName<Interface>());
        return result;
    }

private:
    template<typename T, typename... Args>
    friend void AddToRegistry(T *registry, Args &&...args);

    void Register(const std::string &name, Interface *object)
    {
        TILE_DCHECK(objects_.find(name) == objects_.end());
        auto &&e = objects_[name];
        e        = make_unique<LazilyInstantiatedObject>();
        std::call_once(e->flag, [&] { e->object = MaybeOwning<Interface>(non_owning, object); });
    }

    void Register(const std::string &name, std::function<MaybeOwning<Interface>()> initializer)
    {
        TILE_CHECK(objects_.find(name) == objects_.end(), "Double registration of object dependency [{}]", name);

        objects_[name]              = make_unique<LazilyInstantiatedObject>();
        objects_[name]->initializer = std::move(initializer);
    }

private:
    struct LazilyInstantiatedObject {
        std::once_flag flag;
        MaybeOwning<Interface> object;
        std::function<MaybeOwning<Interface>()> initializer;
    };

    std::unordered_map<std::string, std::unique_ptr<LazilyInstantiatedObject>> objects_;
};

}// namespace dependency_registry
}// namespace detail
}// namespace tile

#endif// TILE_BASE_DEPENDENCY_REGISTRY_H
