#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP

#include <memory>
#include <type_traits>
#include <utility>
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "component.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "storage.hpp"
#include "view.hpp"

namespace entt {

/**
 * @brief Converts a registry to a view.
 * @tparam Registry Basic registry type.
 */
template<typename Registry>
class as_view {
    template<typename... Get, typename... Exclude>
    [[nodiscard]] auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
        return reg->template view<constness_as_t<typename Get::element_type, Get>...>(exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
    }

public:
    /*! @brief Type of registry to convert. */
    using registry_type = Registry;
    /*! @brief Underlying entity identifier. */
    using entity_type = typename registry_type::entity_type;

    /**
     * @brief Constructs a converter for a given registry.
     * @param source A valid reference to a registry.
     */
    as_view(registry_type &source) noexcept
        : reg{&source} {}

    /**
     * @brief Conversion function from a registry to a view.
     * @tparam Get Type of storage used to construct the view.
     * @tparam Exclude Types of storage used to filter the view.
     * @return A newly created view.
     */
    template<typename Get, typename Exclude>
    operator basic_view<Get, Exclude>() const {
        return dispatch(Get{}, Exclude{});
    }

private:
    registry_type *reg;
};

/**
 * @brief Converts a registry to a group.
 * @tparam Registry Basic registry type.
 */
template<typename Registry>
class as_group {
    template<typename... Owned, typename... Get, typename... Exclude>
    [[nodiscard]] auto dispatch(owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>) const {
        if constexpr(std::is_const_v<registry_type>) {
            return reg->template group_if_exists<typename Owned::element_type...>(get_t<typename Get::element_type...>{}, exclude_t<typename Exclude::element_type...>{});
        } else {
            return reg->template group<constness_as_t<typename Owned::element_type, Owned>...>(get_t<constness_as_t<typename Get::element_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
        }
    }

public:
    /*! @brief Type of registry to convert. */
    using registry_type = Registry;
    /*! @brief Underlying entity identifier. */
    using entity_type = typename registry_type::entity_type;

    /**
     * @brief Constructs a converter for a given registry.
     * @param source A valid reference to a registry.
     */
    as_group(registry_type &source) noexcept
        : reg{&source} {}

    /**
     * @brief Conversion function from a registry to a group.
     * @tparam Owned Types of _owned_ by the group.
     * @tparam Get Types of storage _observed_ by the group.
     * @tparam Exclude Types of storage used to filter the group.
     * @return A newly created group.
     */
    template<typename Owned, typename Get, typename Exclude>
    operator basic_group<Owned, Get, Exclude>() const {
        return dispatch(Owned{}, Get{}, Exclude{});
    }

private:
    registry_type *reg;
};

/**
 * @brief Helper to create a listener that directly invokes a member function.
 * @tparam Member Member function to invoke on an element of the given type.
 * @tparam Registry Basic registry type.
 * @param reg A registry that contains the given entity and its elements.
 * @param entt Entity from which to get the element.
 */
template<auto Member, typename Registry = std::decay_t<nth_argument_t<0u, decltype(Member)>>>
void invoke(Registry &reg, const typename Registry::entity_type entt) {
    static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
    (reg.template get<member_class_t<decltype(Member)>>(entt).*Member)(reg, entt);
}

/**
 * @brief Returns the entity associated with a given element.
 *
 * @warning
 * Currently, this function only works correctly with the default storage as it
 * makes assumptions about how the elements are laid out.
 *
 * @tparam Args Storage type template parameters.
 * @param storage A storage that contains the given element.
 * @param instance A valid element instance.
 * @return The entity associated with the given element.
 */
template<typename... Args>
typename basic_storage<Args...>::entity_type to_entity(const basic_storage<Args...> &storage, const typename basic_storage<Args...>::value_type &instance) {
    using traits_type = component_traits<typename basic_storage<Args...>::value_type, typename basic_storage<Args...>::entity_type>;
    static_assert(traits_type::page_size != 0u, "Unexpected page size");
    const auto *page = storage.raw();

    // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
    for(std::size_t pos{}, count = storage.size(); pos < count; pos += traits_type::page_size, ++page) {
        if(const auto dist = (std::addressof(instance) - *page); dist >= 0 && dist < static_cast<decltype(dist)>(traits_type::page_size)) {
            return *(static_cast<const typename basic_storage<Args...>::base_type &>(storage).rbegin() + static_cast<decltype(dist)>(pos) + dist);
        }
    }
    // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)

    return null;
}

/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;

/**
 * @brief Signal connection helper for registries.
 * @tparam Registry Basic registry type.
 */
template<typename Registry>
struct sigh_helper<Registry> {
    /*! @brief Registry type. */
    using registry_type = Registry;

    /**
     * @brief Constructs a helper for a given registry.
     * @param ref A valid reference to a registry.
     */
    sigh_helper(registry_type &ref)
        : bucket{&ref} {}

    /**
     * @brief Binds a properly initialized helper to a given signal type.
     * @tparam Type Type of signal to bind the helper to.
     * @param id Optional name for the underlying storage to use.
     * @return A helper for a given registry and signal type.
     */
    template<typename Type>
    auto with(const id_type id = type_hash<Type>::value()) noexcept {
        return sigh_helper<registry_type, Type>{*bucket, id};
    }

    /**
     * @brief Returns a reference to the underlying registry.
     * @return A reference to the underlying registry.
     */
    [[nodiscard]] registry_type &registry() noexcept {
        return *bucket;
    }

private:
    registry_type *bucket;
};

/**
 * @brief Signal connection helper for registries.
 * @tparam Registry Basic registry type.
 * @tparam Type Type of signal to connect listeners to.
 */
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
    /*! @brief Registry type. */
    using registry_type = Registry;

    /**
     * @brief Constructs a helper for a given registry.
     * @param ref A valid reference to a registry.
     * @param id Optional name for the underlying storage to use.
     */
    sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
        : sigh_helper<Registry>{ref},
          name{id} {}

    /**
     * @brief Forwards the call to `on_construct` on the underlying storage.
     * @tparam Candidate Function or member to connect.
     * @tparam Args Type of class or type of payload, if any.
     * @param args A valid object that fits the purpose, if any.
     * @return This helper.
     */
    template<auto Candidate, typename... Args>
    auto on_construct(Args &&...args) {
        this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
        return *this;
    }

    /**
     * @brief Forwards the call to `on_update` on the underlying storage.
     * @tparam Candidate Function or member to connect.
     * @tparam Args Type of class or type of payload, if any.
     * @param args A valid object that fits the purpose, if any.
     * @return This helper.
     */
    template<auto Candidate, typename... Args>
    auto on_update(Args &&...args) {
        this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
        return *this;
    }

    /**
     * @brief Forwards the call to `on_destroy` on the underlying storage.
     * @tparam Candidate Function or member to connect.
     * @tparam Args Type of class or type of payload, if any.
     * @param args A valid object that fits the purpose, if any.
     * @return This helper.
     */
    template<auto Candidate, typename... Args>
    auto on_destroy(Args &&...args) {
        this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
        return *this;
    }

private:
    id_type name;
};

/**
 * @brief Deduction guide.
 * @tparam Registry Basic registry type.
 */
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;

} // namespace entt

#endif
