/*********************************************************************
 * \file   AssetLibrary.hpp
 *
 * \author Romain BOULLARD
 * \date   July 2026
 *********************************************************************/
#ifndef BIGFOOT_ENGINE_ASSET_ASSETLIBRARY_HPP
#define BIGFOOT_ENGINE_ASSET_ASSETLIBRARY_HPP
#include <Engine/Asset/AssetContainer.hpp>
#include <Engine/Asset/AssetTypeID.hpp>

#include <ankerl/unordered_dense.h>

namespace Bigfoot
{
class AssetLibrary
{
  public:
    AssetLibrary() = default;

    AssetLibrary(const AssetLibrary& p_library) = default;
    AssetLibrary(AssetLibrary&& p_library) = default;

    ~AssetLibrary() = default;

    /**
     * Insert an AssetContainer pointer to the library
     *
     * \tparam ASSET The Asset type
     * \param p_assetContainer The asset container
     * \return The AssetContainer for the Asset type. Nullptr if not found
     */
    template<class ASSET>
    [[nodiscard]]
    bool Insert(AssetContainer<ASSET>* p_assetContainer)
    {
        static const AssetTypeID typeID = ASSET::GetTypeID();
        if (m_assetContainers.contains(typeID))
        {
            return false;
        }

        const auto inserted = m_assetContainers.emplace(typeID, p_assetContainer);

        return inserted.second;
    }

    /**
     * Get the AssetContainer for a given Asset type
     *
     * \tparam ASSET The Asset type
     * \return The AssetContainer for the Asset type. Nullptr if not found
     */
    template<class ASSET>
    [[nodiscard]]
    AssetContainer<ASSET>* GetAssetContainer()
    {
        static const AssetTypeID typeID = ASSET::GetTypeID();
        return static_cast<AssetContainer<ASSET>*>(GetAssetContainer(typeID));
    }

    /**
     * Get the AssetContainer for a given Asset type
     *
     * \tparam ASSET The Asset type
     * \return The AssetContainer for the Asset type. Nullptr if not found
     */
    template<class ASSET>
    [[nodiscard]]
    const AssetContainer<ASSET>* GetAssetContainer() const
    {
        static const AssetTypeID typeID = ASSET::GetTypeID();
        return static_cast<const AssetContainer<ASSET>*>(GetAssetContainer(typeID));
    }

    /**
     * Get the AssetContainer for a given AssetType
     *
     * \param p_typeID The asset type ID
     * \return The AssetContainer for the AssetType. Nullptr if not found
     */
    [[nodiscard]]
    AssetContainerBase* GetAssetContainer(const AssetTypeID p_typeID)
    {
        if (const auto container = m_assetContainers.find(p_typeID); container != m_assetContainers.end())
        {
            return container->second;
        }

        return nullptr;
    }

    /**
     * Get the AssetContainer for a given AssetType
     *
     * \param p_typeID The asset type ID
     * \return The AssetContainer for the AssetType. Nullptr if not found
     */
    [[nodiscard]]
    const AssetContainerBase* GetAssetContainer(const AssetTypeID p_typeID) const
    {
        if (const auto container = m_assetContainers.find(p_typeID); container != m_assetContainers.end())
        {
            return container->second;
        }

        return nullptr;
    }

    /**
     * Find The associated container for a given UUID identifying an Asset
     *
     * \param p_uuid The UUID
     * \return The AssetContainer containing the asset. Nullptr if not found
     */
    [[nodiscard]]
    AssetContainerBase* FindAssetContainerContainerForUUID(const UUID& p_uuid)
    {
        for (const auto& [typeID, container]: m_assetContainers)
        {
            if (container->Has(p_uuid))
            {
                return container;
            }
        }

        return nullptr;
    }

    /**
     * Find The associated container for a given UUID identifying an Asset
     *
     * \param p_uuid The UUID
     * \return The AssetContainer containing the asset. Nullptr if not found
     */
    [[nodiscard]]
    const AssetContainerBase* FindAssetContainerContainerForUUID(const UUID& p_uuid) const
    {
        for (const auto& [typeID, container]: m_assetContainers)
        {
            if (container->Has(p_uuid))
            {
                return container;
            }
        }

        return nullptr;
    }

    AssetLibrary& operator=(const AssetLibrary& p_library) = default;
    AssetLibrary& operator=(AssetLibrary&& p_library) = default;

  private:
    // All the asset containers
    ankerl::unordered_dense::segmented_map<AssetTypeID, AssetContainerBase*> m_assetContainers;
};

/****************************************************************************************/

// The global asset library for Bigfoot
inline AssetLibrary g_assetLibrary;
inline AssetLibrary* g_activeAssetLibrary = &g_assetLibrary;

/****************************************************************************************/

class ScopedActiveAssetLibrary
{
  public:
    ScopedActiveAssetLibrary(AssetLibrary* p_assetLibrary):
        m_previous(g_activeAssetLibrary)
    {
        g_activeAssetLibrary = p_assetLibrary;
    }

    ScopedActiveAssetLibrary(const ScopedActiveAssetLibrary& p_scopedActiveAssetLibrary) = default;
    ScopedActiveAssetLibrary(ScopedActiveAssetLibrary&& p_scopedActiveAssetLibrary) = default;

    ~ScopedActiveAssetLibrary()
    {
        g_activeAssetLibrary = m_previous;
    }

    ScopedActiveAssetLibrary& operator=(const ScopedActiveAssetLibrary& p_scopedActiveAssetLibrary) = default;
    ScopedActiveAssetLibrary& operator=(ScopedActiveAssetLibrary&& p_scopedActiveAssetLibrary) = default;

  private:
    /*
     * The previously active asset library.
     */
    AssetLibrary* m_previous = nullptr;
};
}   // namespace Bigfoot

#endif
