/*********************************************************************
 * \file   Reference.hpp
 *
 * \author Romain BOULLARD
 * \date   April 2026
 *********************************************************************/
#ifndef BIGFOOT_ENGINE_ASSET_REFERENCE_HPP
#define BIGFOOT_ENGINE_ASSET_REFERENCE_HPP
#include <Engine/Asset/AssetLibrary.hpp>

#include <System/UUID/UUID.hpp>

#include <utility>

namespace Bigfoot
{
template<class ASSET>
AssetTypeID GetAssetTypeID();

template<class ASSET>
class HardReference
{
  public:
    HardReference() = default;

    HardReference(const UUID& p_uuid):
        m_uuid(p_uuid)
    {
        Acquire();
    }

    HardReference(const HardReference& p_ref):
        m_uuid(p_ref.m_uuid),
        m_handle(p_ref.m_handle)
    {
        Acquire();
    }

    HardReference(HardReference&& p_ref) noexcept:
        m_uuid(std::exchange(p_ref.m_uuid, UUID::NULL_UUID)),
        m_handle(std::exchange(p_ref.m_handle, nullptr))
    {
    }

    [[nodiscard]]
    const UUID& GetUUID() const
    {
        return m_uuid;
    }

    [[nodiscard]]
    const ASSET* Get() const
    {
        return m_handle ? static_cast<const ASSET*>(m_handle->GetAsset()) : nullptr;
    }

    [[nodiscard]]
    ASSET* Get()
    {
        return m_handle ? static_cast<ASSET*>(m_handle->GetAsset()) : nullptr;
    }

    ~HardReference()
    {
        Release();
    }

    HardReference& operator=(const HardReference& p_ref)
    {
        if (this != &p_ref)
        {
            Release();
            m_uuid = p_ref.m_uuid;
            m_handle = p_ref.m_handle;
            Acquire();
        }
        return *this;
    }

    HardReference& operator=(HardReference&& p_ref) noexcept
    {
        if (this != &p_ref)
        {
            Release();
            m_uuid = std::exchange(p_ref.m_uuid, UUID::NULL_UUID);
            m_handle = std::exchange(p_ref.m_handle, nullptr);
        }
        return *this;
    }

    [[nodiscard]]
    bool operator==(const HardReference& p_ref) const
    {
        return m_uuid == p_ref.m_uuid;
    }

  private:
    void Acquire()
    {
        if (!m_uuid)
        {
            return;
        }

        AssetContainerBase* assetContainer = nullptr;
        if constexpr (std::is_same_v<ASSET, AssetBase>)
        {
            assetContainer = g_activeAssetLibrary->FindAssetContainerContainerForUUID(m_uuid);
        }
        else
        {
            assetContainer = g_activeAssetLibrary->GetAssetContainer(GetAssetTypeID<ASSET>());
        }

        if (assetContainer)
        {
            if (!m_handle)
            {
                m_handle = &assetContainer->AcquireContainerHandle(m_uuid);
            }
            m_handle->IncrementSoftRefCount();
        }
    }

    void Release()
    {
        if (m_handle)
        {
            m_handle->DecrementHardRefCount();
            m_handle = nullptr;
        }
    }

    UUID m_uuid = UUID::NULL_UUID;
    ContainerHandle* m_handle = nullptr;
};

template<class ASSET>
class SoftReference
{
  public:
    SoftReference() = default;

    SoftReference(const UUID& p_uuid):
        m_uuid(p_uuid)
    {
        Acquire();
    }

    SoftReference(const SoftReference& p_ref):
        m_uuid(p_ref.m_uuid),
        m_handle(p_ref.m_handle)
    {
        Acquire();
    }

    SoftReference(SoftReference&& p_ref) noexcept:
        m_uuid(std::exchange(p_ref.m_uuid, UUID::NULL_UUID)),
        m_handle(std::exchange(p_ref.m_handle, nullptr))
    {
    }

    [[nodiscard]]
    const UUID& GetUUID() const
    {
        return m_uuid;
    }

    [[nodiscard]]
    const ASSET* Get() const
    {
        return m_handle ? static_cast<const ASSET*>(m_handle->GetAsset()) : nullptr;
    }

    [[nodiscard]]
    ASSET* Get()
    {
        return m_handle ? static_cast<ASSET*>(m_handle->GetAsset()) : nullptr;
    }

    ~SoftReference()
    {
        Release();
    }

    SoftReference& operator=(const SoftReference& p_ref)
    {
        if (this != &p_ref)
        {
            Release();
            m_uuid = p_ref.m_uuid;
            m_handle = p_ref.m_handle;
            Acquire();
        }
        return *this;
    }

    SoftReference& operator=(SoftReference&& p_ref) noexcept
    {
        if (this != &p_ref)
        {
            Release();
            m_uuid = std::exchange(p_ref.m_uuid, UUID::NULL_UUID);
            m_handle = std::exchange(p_ref.m_handle, nullptr);
        }
        return *this;
    }

    [[nodiscard]]
    bool operator==(const SoftReference& p_ref) const
    {
        return m_uuid == p_ref.m_uuid;
    }

  private:
    void Acquire()
    {
        if (!m_uuid)
        {
            return;
        }

        AssetContainerBase* assetContainer = nullptr;
        if constexpr (std::is_same_v<ASSET, AssetBase>)
        {
            assetContainer = g_activeAssetLibrary->FindAssetContainerContainerForUUID(m_uuid);
        }
        else
        {
            assetContainer = g_activeAssetLibrary->GetAssetContainer(GetAssetTypeID<ASSET>());
        }

        if (assetContainer)
        {
            if (!m_handle)
            {
                m_handle = &assetContainer->AcquireContainerHandle(m_uuid);
            }
            m_handle->IncrementSoftRefCount();
        }
    }

    void Release()
    {
        if (m_handle)
        {
            m_handle->DecrementSoftRefCount();
            m_handle = nullptr;
        }
    }

    UUID m_uuid = UUID::NULL_UUID;
    ContainerHandle* m_handle = nullptr;
};
}   // namespace Bigfoot
#endif
