#pragma once

#include <Core/FxBitset.hpp>
#include <Core/FxHash.hpp>
#include <Core/FxSizedArray.hpp>
#include <map>

/**
 * @brief Cache section that can hold any number of items.
 */
template <typename TItemType>
struct FxItemCacheSection_MultiItem
{
    FxBitset ItemsInUse;
    FxSizedArray<TItemType> Items;

public:
    void Create(uint32 max_items)
    {
        Items.Create(max_items);
        ItemsInUse.InitZero(max_items);
    }

    TItemType* RequestNewItem()
    {
        int index = ItemsInUse.FindNextFreeBit();

        if (index == FxBitset::scNoFreeBits) {
            return nullptr;
        }

        ItemsInUse.Set(index);

        return &Items[index];
    }

    void ReleaseItem(TItemType* item)
    {
        auto item_index = Items.GetItemIndex(item);

        // If the item was found, clear the bit
        if (item_index != FxSizedArray<TItemType>::scItemNotFound) {
            ItemsInUse.Unset(item_index);
        }
    }
};


/**
 * @brief Cache section that holds a single item.
 */
template <typename TItemType>
struct FxItemCacheSection_SingleItem
{
    using ItemType = TItemType;

    TItemType* pItem = nullptr;
    bool bInUse : 1 = 0;

public:
    void Create(uint32 max_items) { pItem = FxMemPool::Alloc<TItemType>(sizeof(TItemType)); }

    TItemType* RequestNewItem()
    {
        if (bInUse) {
            return nullptr;
        }

        bInUse = true;

        return pItem;
    }

    void ReleaseItem(TItemType* item)
    {
        if (pItem != item) {
            return;
        }

        bInUse = false;
    }
};

template <typename T, typename TItemType>
concept C_IsItemCacheSection =
    C_IsAnyBaseOf<T, FxItemCacheSection_MultiItem<TItemType>, FxItemCacheSection_SingleItem<TItemType>>;

template <typename TKeyType, typename TItemType, typename TCacheSectionType>
    requires C_IsItemCacheSection<TCacheSectionType, TItemType>
class FxItemCache
{
public:
    using KeyType = TKeyType;

public:
    FxItemCache() = default;

    void Create(uint32 max_items_per_section) { mItemsPerSection = max_items_per_section; }


    bool IsItemInCache(TKeyType key)
    {
        auto section_it = mCache.find(key);

        return (section_it == mCache.end());
    }

    TItemType* RequestGenericItem(TKeyType key)
    {
        auto section_it = mCache.find(key);

        if (section_it == mCache.end()) {
            CreateCacheSection(key, mItemsPerSection);
            auto section_it = mCache.find(key);

            FxAssert(section_it != mCache.end());
        }

        TCacheSectionType& section = section_it->second;

        return section.RequestNewItem();
    }

    void ReleaseGenericItem(TKeyType key, TItemType* item)
    {
        auto section_it = mCache.find(key);

        if (section_it == mCache.end()) {
            FxLogError("Could not find cache section to free from!");
            return;
        }

        TCacheSectionType& section = section_it->second;

        auto item_index = section.Items.GetItemIndex(item);

        // If the item was found, clear the bit
        if (item_index != FxSizedArray<TItemType>::scItemNotFound) {
            section.ItemsInUse.Unset(item_index);
        }
    }

    void Destroy();

    virtual ~FxItemCache() { Destroy(); }

protected:
    TCacheSectionType& CreateCacheSection(TKeyType key, uint32 number_of_items)
    {
        TCacheSectionType cache_section {};

        cache_section.Items.InitCapacity(number_of_items);
        cache_section.ItemsInUse.InitZero(number_of_items);

        auto iter = mCache.insert({ key, std::move(cache_section) });

        return iter.first->second;
    }

public:
    std::unordered_map<TKeyType, TCacheSectionType> mCache;

protected:
    uint32 mItemsPerSection = 0;
};

/**
 * @brief A temporary handle that is available only for the current scope.
 */
template <typename TKeyType, typename TItemType, typename TCacheType>
struct FxItemCacheHandle
{
    using CacheType = TCacheType;
    using KeyType = TKeyType;

public:
    FxItemCacheHandle(TCacheType* cache, TItemType* item, TKeyType key) : Item(item), Key(key), mCache(cache) {}
    FxItemCacheHandle(const FxItemCacheHandle& other) = delete;
    FxItemCacheHandle(FxItemCacheHandle&& other)
    {
        Item = other.Item;
        Key = other.Key;

        mCache = other.mCache;

        other.Item = nullptr;
    }

    /**
     * @brief Releases the handle before the destructor is called.
     */
    FX_FORCE_INLINE void Release()
    {
        if (Item == nullptr) {
            return;
        }

        mCache->ReleaseGenericItem(Key, Item);
    }

    ~FxItemCacheHandle() { Release(); }

    TItemType* operator->() { return Item; }
    TItemType& operator*() { return *Item; }

public:
    TItemType* Item = nullptr;
    TKeyType Key;

private:
    CacheType* mCache;
};
