/*********************************************************************
 * \file   Asset.hpp
 *
 * \author Romain BOULLARD
 * \date   April 2026
 *********************************************************************/
#ifndef BIGFOOT_ENGINE_ASSET_ASSET_HPP
#define BIGFOOT_ENGINE_ASSET_ASSET_HPP
#include <Engine/Asset/Asset_generated.hpp>
#include <Engine/EngineAssertHandler.hpp>

#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <flatbuffers/reflection.h>
#include <rapidhash.h>

namespace Bigfoot
{
class AssetBase
{
  public:
    virtual ~AssetBase() = default;

  protected:
    AssetBase() = default;

    AssetBase(const AssetBase& p_asset) = default;
    AssetBase(AssetBase&& p_asset) = default;

    AssetBase& operator=(const AssetBase& p_asset) = default;
    AssetBase& operator=(AssetBase&& p_asset) = default;
};

template<class ASSET>
class Asset: public AssetBase
{
  public:
    using FLAT_ASSET = typename ASSET::FLAT;

    Asset()
    {
        m_header.type_id = GetTypeID();
        m_header.type_name = eastl::string {GetTypeName()};
    }

    explicit Asset(const eastl::span<const std::byte> p_flatbuffer):
        Asset()
    {
#ifdef BIGFOOT_NOT_OPTIMIZED
        flatbuffers::Verifier verifier {std::bit_cast<std::uint8_t*>(p_flatbuffer.data()), p_flatbuffer.size()};
        const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier);
        CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!");

        const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(p_flatbuffer.data());

        flatbuffers::Verifier innerAssetVerifier {asset->inner_asset()->data(), asset->inner_asset()->size()};
        const bool innerAssetVerified = innerAssetVerifier.VerifyBuffer<FLAT_ASSET>();
        CRITICAL_ASSERT(EngineAssertHandler, innerAssetVerified, "Given asset could not be verified!");
#else
        const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(p_flatbuffer.data());
#endif

        asset->header()->UnPackTo(&m_header);
        flatbuffers::GetRoot<FLAT_ASSET>(asset->inner_asset()->data())->UnPackTo(&m_asset, nullptr);
    }

    Asset(const Asset& p_asset) = default;
    Asset(Asset&& p_asset) = default;

    ~Asset() = default;

    [[nodiscard]]
    const ::Flat::Bigfoot::AssetHeaderT& GetHeader() const
    {
        return m_header;
    }

    void SetName(const eastl::string_view p_name)
    {
        m_header.name = p_name;
    }

    void SetVersion(const std::uint32_t p_version)
    {
        m_header.version = p_version;
    }

    [[nodiscard]]
    const typename FLAT_ASSET::NativeTableType& GetAsset() const
    {
        return m_asset;
    }

    [[nodiscard]]
    typename FLAT_ASSET::NativeTableType& GetAsset()
    {
        return m_asset;
    }

    [[nodiscard]]
    eastl::vector<std::byte> Save() const
    {
        flatbuffers::FlatBufferBuilder innerAssetFbb;
        innerAssetFbb.Finish(FLAT_ASSET::Pack(innerAssetFbb, &m_asset));

        ::Flat::Bigfoot::AssetT asset;
        asset.header = eastl::make_unique<::Flat::Bigfoot::AssetHeaderT>(m_header);
        asset.inner_asset = eastl::vector<std::uint8_t> {innerAssetFbb.GetBufferPointer(),
                                                         innerAssetFbb.GetBufferPointer() + innerAssetFbb.GetSize()};

        flatbuffers::FlatBufferBuilder assetFbb;
        assetFbb.Finish(::Flat::Bigfoot::Asset::Pack(assetFbb, &asset));

        return eastl::vector<std::byte> {std::bit_cast<std::byte*>(assetFbb.GetBufferPointer()),
                                         std::bit_cast<std::byte*>(assetFbb.GetBufferPointer() + assetFbb.GetSize())};
    }

    [[nodiscard]]
    static std::uint64_t GetTypeID()
    {
        static const std::uint64_t typeID = rapidhash(FLAT_ASSET::GetFullyQualifiedName(),
                                                      std::strlen(FLAT_ASSET::GetFullyQualifiedName()));
        return typeID;
    }

    [[nodiscard]]
    static eastl::string_view GetTypeName()
    {
        return FLAT_ASSET::GetFullyQualifiedName();
    }

    Asset& operator=(const Asset& p_asset) = default;
    Asset& operator=(Asset&& p_asset) = default;

  private:
    typename FLAT_ASSET::NativeTableType m_asset;
    ::Flat::Bigfoot::AssetHeaderT m_header;
};
}   // namespace Bigfoot

#endif
