/*********************************************************************
 * \file   SlotMap.cpp
 *
 * \author Romain BOULLARD
 * \date   August 2026
 *********************************************************************/
#include <Utils/Containers/SlotMap.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace Bigfoot
{
class SlotMapKeyFixture: public ::testing::Test
{
  protected:
};

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

TEST_F(SlotMapKeyFixture, INVALID_VERSION_IsZero)
{
    EXPECT_EQ(SlotMapKey::INVALID_VERSION, 0);
}

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

TEST_F(SlotMapKeyFixture, MAX_INDEX_IsMaxU32)
{
    EXPECT_EQ(SlotMapKey::MAX_INDEX, std::numeric_limits<std::uint32_t>::max());
}

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

TEST_F(SlotMapKeyFixture, Valid_ShouldReturnFalseIfVersionIsInvalid)
{
    EXPECT_FALSE(SlotMapKey().Valid());
    EXPECT_FALSE(SlotMapKey(0, 0).Valid());
    EXPECT_FALSE(SlotMapKey(SlotMapKey::INVALID_VERSION, 0).Valid());
    EXPECT_FALSE(SlotMapKey(0, 42).Valid());
    EXPECT_FALSE(SlotMapKey(SlotMapKey::INVALID_VERSION, 42).Valid());
}

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

TEST_F(SlotMapKeyFixture, Valid_ShouldReturnTrueIfVersionisNotInvalid)
{
    EXPECT_TRUE(SlotMapKey(1, 0).Valid());
    EXPECT_TRUE(SlotMapKey(1, 42).Valid());
}

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

TEST_F(SlotMapKeyFixture, GetVersion_ShouldReturnTheVersion)
{
    EXPECT_EQ(SlotMapKey(0, 0).GetVersion(), 0);
    EXPECT_EQ(SlotMapKey(42, 69).GetVersion(), 42);
}

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

TEST_F(SlotMapKeyFixture, GetIndex_ShouldReturnTheIndex)
{
    EXPECT_EQ(SlotMapKey(0, 0).GetIndex(), 0);
    EXPECT_EQ(SlotMapKey(42, 69).GetIndex(), 69);
}

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

class SlotMapValueMock
{
  public:
    virtual ~SlotMapValueMock() = default;
    virtual void Destruct() = 0;
};

class SlotMapValue
{
  public:
    inline static SlotMapValueMock* ms_mock = nullptr;

    SlotMapValue() = default;

    SlotMapValue(const std::uint32_t p_value):
        m_value(p_value)
    {
    }

    SlotMapValue(const SlotMapValue& p_value) = default;
    SlotMapValue(SlotMapValue&& p_value) = default;

    ~SlotMapValue()
    {
        if (ms_mock)
        {
            ms_mock->Destruct();
        }
    }

    SlotMapValue& operator=(const SlotMapValue& p_value) = default;
    SlotMapValue& operator=(SlotMapValue&& p_value) = default;

    operator std::uint32_t() const
    {
        return m_value;
    }

  private:
    std::uint32_t m_value = 0;
};

template<class SLOTMAP>
class SlotMapFixture: public ::testing::Test
{
  protected:
    class SlotMapValueMockImpl: public SlotMapValueMock
    {
      public:
        MOCK_METHOD(void, Destruct, (), (override));
    };

    std::unique_ptr<SlotMapValueMockImpl> m_mock;
    SLOTMAP m_slotMap;

    void SetUp() override
    {
        m_mock = std::make_unique<::testing::NiceMock<SlotMapValueMockImpl>>();
        SlotMapValue::ms_mock = m_mock.get();
    }

    void TearDown() override
    {
        SlotMapValue::ms_mock = nullptr;
        m_mock.reset();
    }
};

class SlotMapNameGenerator
{
  public:
    template<typename T>
    static std::string GetName(int)
    {
        if constexpr (std::is_same_v<T, DenseSlotMap<SlotMapValue>>)
        {
            return "DenseSlotMap";
        }
        if constexpr (std::is_same_v<T, StableSlotMap<SlotMapValue>>)
        {
            return "StableSlotMap";
        }
    }
};

using SlotMapTypes = ::testing::Types<DenseSlotMap<SlotMapValue>, StableSlotMap<SlotMapValue>>;
TYPED_TEST_SUITE(SlotMapFixture, SlotMapTypes, SlotMapNameGenerator);

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

TYPED_TEST(SlotMapFixture, Has_ReturnTrueIfSlotMapHasTheKey)
{
    const SlotMapKey key = this->m_slotMap.Insert(42);
    EXPECT_TRUE(this->m_slotMap.Has(key));
}

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

TYPED_TEST(SlotMapFixture, Has_ReturnFalseIfSlotMapDoesNotHaveTheKey)
{
    std::ignore = this->m_slotMap.Insert(42);
    EXPECT_FALSE(this->m_slotMap.Has(SlotMapKey {1, 23}));
}

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

TYPED_TEST(SlotMapFixture, Has_ReturnFalseForStaleKey)
{
    const SlotMapKey staleKey = this->m_slotMap.Insert(42);
    this->m_slotMap.Remove(staleKey);
    std::ignore = this->m_slotMap.Insert(69);

    EXPECT_FALSE(this->m_slotMap.Has(staleKey));
}

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

TYPED_TEST(SlotMapFixture, Size_ShouldReturnTheSizeOfTheSlotMap)
{
    EXPECT_EQ(this->m_slotMap.Size(), 0);
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    const SlotMapKey key2 = this->m_slotMap.Insert(69);
    EXPECT_EQ(this->m_slotMap.Size(), 2);
    this->m_slotMap.Remove(key1);
    EXPECT_EQ(this->m_slotMap.Size(), 1);
    this->m_slotMap.Remove(key2);
    EXPECT_EQ(this->m_slotMap.Size(), 0);
}

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

TYPED_TEST(SlotMapFixture, Empty_ShouldReturnTrueIfTheSlotMapIsEmpty)
{
    EXPECT_TRUE(this->m_slotMap.Empty());
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    const SlotMapKey key2 = this->m_slotMap.Insert(69);
    this->m_slotMap.Remove(key1);
    this->m_slotMap.Remove(key2);
    EXPECT_TRUE(this->m_slotMap.Empty());
}

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

TYPED_TEST(SlotMapFixture, Empty_ShouldReturnFalseIfTheSlotMapIsNotEmpty)
{
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    EXPECT_FALSE(this->m_slotMap.Empty());
    const SlotMapKey key2 = this->m_slotMap.Insert(69);
    EXPECT_FALSE(this->m_slotMap.Empty());
    this->m_slotMap.Remove(key1);
    EXPECT_FALSE(this->m_slotMap.Empty());
    this->m_slotMap.Remove(key2);
}

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

TYPED_TEST(SlotMapFixture, Insert_ShouldReturnAValidSlotKey)
{
    EXPECT_TRUE(this->m_slotMap.Insert(42).Valid());
}

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

TYPED_TEST(SlotMapFixture, Insert_ShouldRecycleIndicesWhenPossible)
{
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    const SlotMapKey key2 = this->m_slotMap.Insert(69);
    
    this->m_slotMap.Remove(key1);
    this->m_slotMap.Remove(key2);

    const SlotMapKey key11 = this->m_slotMap.Insert(42);
    const SlotMapKey key21 = this->m_slotMap.Insert(69);

    EXPECT_EQ(key1.GetVersion() + 1, key11.GetVersion());
    EXPECT_EQ(key1.GetIndex(), key21.GetIndex()); //LIFO

    EXPECT_EQ(key2.GetVersion() + 1, key21.GetVersion());
    EXPECT_EQ(key2.GetIndex(), key11.GetIndex());  //LIFO
}

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

TYPED_TEST(SlotMapFixture, Get_ShouldReturnTheValue)
{
    const SlotMapKey key = this->m_slotMap.Insert(42);
    EXPECT_EQ(*this->m_slotMap.Get(key), 42);
    EXPECT_EQ(*const_cast<const TypeParam&>(this->m_slotMap).Get(key), 42);
}

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

TYPED_TEST(SlotMapFixture, Get_ShouldNullptrIfTheValueIsNotInMap)
{
    const SlotMapKey key {1, 0};
    EXPECT_EQ(this->m_slotMap.Get(key), nullptr);
    EXPECT_EQ(const_cast<const TypeParam&>(this->m_slotMap).Get(key), nullptr);
}

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

TYPED_TEST(SlotMapFixture, Get_ShouldReturnNullptrForStaleKey)
{
    const SlotMapKey staleKey = this->m_slotMap.Insert(42);
    this->m_slotMap.Remove(staleKey);
    std::ignore = this->m_slotMap.Insert(69);

    EXPECT_EQ(this->m_slotMap.Get(staleKey), nullptr);
    EXPECT_EQ(const_cast<const TypeParam&>(this->m_slotMap).Get(staleKey), nullptr);
}

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

TYPED_TEST(SlotMapFixture, Remove_ShouldRemoveTheValue)
{
    const SlotMapKey key = this->m_slotMap.Insert(42);
    EXPECT_TRUE(this->m_slotMap.Has(key));

    EXPECT_CALL(*this->m_mock, Destruct()).Times(1);
    this->m_slotMap.Remove(key);
    EXPECT_FALSE(this->m_slotMap.Has(key));
}

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

TYPED_TEST(SlotMapFixture, Remove_ShouldBeANoOpForStaleKey)
{
    const SlotMapKey staleKey = this->m_slotMap.Insert(42);
    this->m_slotMap.Remove(staleKey);
    const SlotMapKey freshKey = this->m_slotMap.Insert(69);

    this->m_slotMap.Remove(staleKey);

    EXPECT_TRUE(this->m_slotMap.Has(freshKey));
    EXPECT_EQ(*this->m_slotMap.Get(freshKey), 69);
    EXPECT_EQ(this->m_slotMap.Size(), 1);
}

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

TYPED_TEST(SlotMapFixture, Reset_ShouldClearTheMapAndOldKeysAreValid)
{
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    const SlotMapKey key2 = this->m_slotMap.Insert(69);

    EXPECT_CALL(*this->m_mock, Destruct()).Times(2);
    this->m_slotMap.Reset();

    EXPECT_EQ(this->m_slotMap.Size(), 0);
    EXPECT_TRUE(this->m_slotMap.Empty());

    EXPECT_FALSE(this->m_slotMap.Has(key1));
    EXPECT_FALSE(this->m_slotMap.Has(key2));

    EXPECT_EQ(this->m_slotMap.Insert(42), key1);
    EXPECT_EQ(this->m_slotMap.Insert(69), key2);
}

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

TYPED_TEST(SlotMapFixture, Clear_ShouldClearTheMapAndOldKeysAreInvalid)
{
    const SlotMapKey key1 = this->m_slotMap.Insert(42);
    const SlotMapKey key2 = this->m_slotMap.Insert(69);

    EXPECT_CALL(*this->m_mock, Destruct()).Times(2);
    this->m_slotMap.Clear();

    EXPECT_EQ(this->m_slotMap.Size(), 0);
    EXPECT_TRUE(this->m_slotMap.Empty());

    EXPECT_FALSE(this->m_slotMap.Has(key1));
    EXPECT_FALSE(this->m_slotMap.Has(key2));

    EXPECT_NE(this->m_slotMap.Insert(42), key1);
    EXPECT_NE(this->m_slotMap.Insert(69), key2);
}

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

TYPED_TEST(SlotMapFixture, Iterator)
{
    EXPECT_EQ(this->m_slotMap.begin(), this->m_slotMap.end());

    std::ignore = this->m_slotMap.Insert(1);
    const SlotMapKey key2 = this->m_slotMap.Insert(2);
    std::ignore = this->m_slotMap.Insert(3);

    // Forward traversal follows insertion order
    std::uint32_t expected = 1;
    for (auto it = this->m_slotMap.begin(); it != this->m_slotMap.end(); ++it)
    {
        EXPECT_EQ(*it, expected);
        ++expected;
    }

    // Pre-increment advances and returns *this
    auto preIt = this->m_slotMap.begin();
    auto& preIncrementResult = ++preIt;
    EXPECT_EQ(&preIncrementResult, &preIt);
    EXPECT_EQ(*preIt, 2);

    // Post-increment advances but returns the old value
    auto postIt = this->m_slotMap.begin();
    const auto postIncrementResult = postIt++;
    EXPECT_EQ(*postIncrementResult, 1);
    EXPECT_EQ(*postIt, 2);

    // Pre-decrement retreats and returns *this
    auto preDecIt = this->m_slotMap.end();
    --preDecIt;   // now at 3
    auto& preDecrementResult = --preDecIt;   // now at 2
    EXPECT_EQ(&preDecrementResult, &preDecIt);
    EXPECT_EQ(*preDecIt, 2);

    // Post-decrement retreats but returns the old value
    auto postDecIt = this->m_slotMap.end();
    --postDecIt;   // now at 3
    const auto postDecrementResult = postDecIt--;
    EXPECT_EQ(*postDecrementResult, 3);
    EXPECT_EQ(*postDecIt, 2);

    // Removed slots are skipped during traversal
    this->m_slotMap.Remove(key2);

    std::size_t count = 0;
    std::uint32_t sum = 0;
    for (const std::uint32_t value: this->m_slotMap)
    {
        ++count;
        sum += value;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    // Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
    this->m_slotMap.Reset();

    constexpr std::uint32_t elementCount = 100;
    eastl::array<SlotMapKey, elementCount> keys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        keys[value] = this->m_slotMap.Insert(value);
    }

    expected = 0;
    for (const std::uint32_t value: this->m_slotMap)
    {
        EXPECT_EQ(value, expected);
        ++expected;
    }
    EXPECT_EQ(expected, elementCount);

    // Explicitly cross the page boundary (index 63 -> 64) forward and backward
    auto boundaryIt = this->m_slotMap.begin();
    for (std::uint32_t step = 0; step < 63; ++step)
    {
        ++boundaryIt;
    }
    EXPECT_EQ(*boundaryIt, 63);
    ++boundaryIt;
    EXPECT_EQ(*boundaryIt, 64);
    --boundaryIt;
    EXPECT_EQ(*boundaryIt, 63);

    // Pre/post-increment/decrement across the boundary
    auto preBoundaryIt = this->m_slotMap.begin();
    auto& preBoundaryIncrementResult = ++preBoundaryIt;
    EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
    EXPECT_EQ(*preBoundaryIt, 1);

    auto postBoundaryIt = this->m_slotMap.begin();
    const auto postBoundaryIncrementResult = postBoundaryIt++;
    EXPECT_EQ(*postBoundaryIncrementResult, 0);
    EXPECT_EQ(*postBoundaryIt, 1);

    auto preBoundaryDecIt = this->m_slotMap.end();
    --preBoundaryDecIt;   // now at 99
    auto& preBoundaryDecrementResult = --preBoundaryDecIt;   // now at 98
    EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
    EXPECT_EQ(*preBoundaryDecIt, 98);

    auto postBoundaryDecIt = this->m_slotMap.end();
    --postBoundaryDecIt;   // now at 99
    const auto postBoundaryDecrementResult = postBoundaryDecIt--;
    EXPECT_EQ(*postBoundaryDecrementResult, 99);
    EXPECT_EQ(*postBoundaryDecIt, 98);

    // Remove elements straddling the boundary and check the holes are skipped
    this->m_slotMap.Remove(keys[63]);
    this->m_slotMap.Remove(keys[64]);

    count = 0;
    for (const std::uint32_t value: this->m_slotMap)
    {
        EXPECT_NE(value, 63);
        EXPECT_NE(value, 64);
        ++count;
    }
    EXPECT_EQ(count, elementCount - 2);

    // A fully empty leading page must be skipped by begin()/SeekFirstFrom
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> leadingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        leadingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 0; value < 64; ++value)
    {
        this->m_slotMap.Remove(leadingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (const std::uint32_t value: this->m_slotMap)
    {
        ++count;
        sum += value;
    }
    EXPECT_EQ(count, elementCount - 64);
    EXPECT_EQ(sum, 2934u);   // 64 + 65 + ... + 99

    // A fully empty trailing page must be skipped when retreating past it
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> trailingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        trailingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 64; value < elementCount; ++value)
    {
        this->m_slotMap.Remove(trailingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (const std::uint32_t value: this->m_slotMap)
    {
        ++count;
        sum += value;
    }
    EXPECT_EQ(count, 64u);
    EXPECT_EQ(sum, 2016u);   // 0 + 1 + ... + 63
}

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

TYPED_TEST(SlotMapFixture, ConstIterator)
{
    const TypeParam& constMap = this->m_slotMap;

    // const_iterator is reachable two ways: cbegin()/cend(), or begin()/end() on a const object
    EXPECT_EQ(this->m_slotMap.cbegin(), this->m_slotMap.cend());
    EXPECT_EQ(constMap.begin(), constMap.end());

    std::ignore = this->m_slotMap.Insert(1);
    const SlotMapKey key2 = this->m_slotMap.Insert(2);
    std::ignore = this->m_slotMap.Insert(3);

    std::uint32_t expected = 1;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        ++expected;
    }

    expected = 1;
    for (auto it = constMap.begin(); it != constMap.end(); ++it)
    {
        EXPECT_EQ(*it, expected);
        ++expected;
    }

    // Pre-increment advances and returns *this
    auto preIt = this->m_slotMap.cbegin();
    auto& preIncrementResult = ++preIt;
    EXPECT_EQ(&preIncrementResult, &preIt);
    EXPECT_EQ(*preIt, 2);

    // Post-increment advances but returns the old value
    auto postIt = this->m_slotMap.cbegin();
    const auto postIncrementResult = postIt++;
    EXPECT_EQ(*postIncrementResult, 1);
    EXPECT_EQ(*postIt, 2);

    // Pre-decrement retreats and returns *this
    auto preDecIt = this->m_slotMap.cend();
    --preDecIt;   // now at 3
    auto& preDecrementResult = --preDecIt;   // now at 2
    EXPECT_EQ(&preDecrementResult, &preDecIt);
    EXPECT_EQ(*preDecIt, 2);

    // Post-decrement retreats but returns the old value
    auto postDecIt = this->m_slotMap.cend();
    --postDecIt;   // now at 3
    const auto postDecrementResult = postDecIt--;
    EXPECT_EQ(*postDecrementResult, 3);
    EXPECT_EQ(*postDecIt, 2);

    // Removed slots are skipped identically through cbegin()/cend() and the const-object overloads
    this->m_slotMap.Remove(key2);

    std::size_t count = 0;
    std::uint32_t sum = 0;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    count = 0;
    sum = 0;
    for (auto it = constMap.begin(); it != constMap.end(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    // Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
    this->m_slotMap.Reset();

    constexpr std::uint32_t elementCount = 100;
    eastl::array<SlotMapKey, elementCount> keys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        keys[value] = this->m_slotMap.Insert(value);
    }

    expected = 0;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        ++expected;
    }
    EXPECT_EQ(expected, elementCount);

    // Explicitly cross the page boundary (index 63 -> 64) forward and backward
    auto boundaryIt = this->m_slotMap.cbegin();
    for (std::uint32_t step = 0; step < 63; ++step)
    {
        ++boundaryIt;
    }
    EXPECT_EQ(*boundaryIt, 63);
    ++boundaryIt;
    EXPECT_EQ(*boundaryIt, 64);
    --boundaryIt;
    EXPECT_EQ(*boundaryIt, 63);

    // Pre/post-increment/decrement across the boundary
    auto preBoundaryIt = this->m_slotMap.cbegin();
    auto& preBoundaryIncrementResult = ++preBoundaryIt;
    EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
    EXPECT_EQ(*preBoundaryIt, 1);

    auto postBoundaryIt = this->m_slotMap.cbegin();
    const auto postBoundaryIncrementResult = postBoundaryIt++;
    EXPECT_EQ(*postBoundaryIncrementResult, 0);
    EXPECT_EQ(*postBoundaryIt, 1);

    auto preBoundaryDecIt = this->m_slotMap.cend();
    --preBoundaryDecIt;   // now at 99
    auto& preBoundaryDecrementResult = --preBoundaryDecIt;   // now at 98
    EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
    EXPECT_EQ(*preBoundaryDecIt, 98);

    auto postBoundaryDecIt = this->m_slotMap.cend();
    --postBoundaryDecIt;   // now at 99
    const auto postBoundaryDecrementResult = postBoundaryDecIt--;
    EXPECT_EQ(*postBoundaryDecrementResult, 99);
    EXPECT_EQ(*postBoundaryDecIt, 98);

    // Remove elements straddling the boundary and check the holes are skipped
    this->m_slotMap.Remove(keys[63]);
    this->m_slotMap.Remove(keys[64]);

    count = 0;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        EXPECT_NE(*it, 63);
        EXPECT_NE(*it, 64);
        ++count;
    }
    EXPECT_EQ(count, elementCount - 2);

    // A fully empty leading page must be skipped by cbegin()/SeekFirstFrom
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> leadingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        leadingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 0; value < 64; ++value)
    {
        this->m_slotMap.Remove(leadingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, elementCount - 64);
    EXPECT_EQ(sum, 2934u);   // 64 + 65 + ... + 99

    // A fully empty trailing page must be skipped when retreating past it
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> trailingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        trailingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 64; value < elementCount; ++value)
    {
        this->m_slotMap.Remove(trailingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 64u);
    EXPECT_EQ(sum, 2016u);   // 0 + 1 + ... + 63
}

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

TYPED_TEST(SlotMapFixture, ReverseIterator)
{
    EXPECT_EQ(this->m_slotMap.rbegin(), this->m_slotMap.rend());

    std::ignore = this->m_slotMap.Insert(1);
    const SlotMapKey key2 = this->m_slotMap.Insert(2);
    std::ignore = this->m_slotMap.Insert(3);

    // Reverse traversal mirrors insertion order
    std::uint32_t expected = 3;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        --expected;
    }

    // Pre-increment advances and returns *this
    auto preIt = this->m_slotMap.rbegin();
    auto& preIncrementResult = ++preIt;
    EXPECT_EQ(&preIncrementResult, &preIt);
    EXPECT_EQ(*preIt, 2);

    // Post-increment advances but returns the old value
    auto postIt = this->m_slotMap.rbegin();
    const auto postIncrementResult = postIt++;
    EXPECT_EQ(*postIncrementResult, 3);
    EXPECT_EQ(*postIt, 2);

    // Pre-decrement retreats and returns *this
    auto preDecIt = this->m_slotMap.rend();
    --preDecIt;   // now at 1
    auto& preDecrementResult = --preDecIt;   // now at 2
    EXPECT_EQ(&preDecrementResult, &preDecIt);
    EXPECT_EQ(*preDecIt, 2);

    // Post-decrement retreats but returns the old value
    auto postDecIt = this->m_slotMap.rend();
    --postDecIt;   // now at 1
    const auto postDecrementResult = postDecIt--;
    EXPECT_EQ(*postDecrementResult, 1);
    EXPECT_EQ(*postDecIt, 2);

    // Removed slots are skipped during traversal
    this->m_slotMap.Remove(key2);

    std::size_t count = 0;
    std::uint32_t sum = 0;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    // Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
    this->m_slotMap.Reset();

    constexpr std::uint32_t elementCount = 100;
    eastl::array<SlotMapKey, elementCount> keys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        keys[value] = this->m_slotMap.Insert(value);
    }

    expected = elementCount - 1;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        --expected;
    }

    // Explicitly cross the page boundary (value 64 -> 63) forward and backward
    auto boundaryIt = this->m_slotMap.rbegin();
    while (*boundaryIt != 64)
    {
        ++boundaryIt;
    }
    ++boundaryIt;
    EXPECT_EQ(*boundaryIt, 63);
    --boundaryIt;
    EXPECT_EQ(*boundaryIt, 64);

    // Pre/post-increment/decrement across the boundary
    auto preBoundaryIt = this->m_slotMap.rbegin();
    auto& preBoundaryIncrementResult = ++preBoundaryIt;
    EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
    EXPECT_EQ(*preBoundaryIt, 98);

    auto postBoundaryIt = this->m_slotMap.rbegin();
    const auto postBoundaryIncrementResult = postBoundaryIt++;
    EXPECT_EQ(*postBoundaryIncrementResult, 99);
    EXPECT_EQ(*postBoundaryIt, 98);

    auto preBoundaryDecIt = this->m_slotMap.rend();
    --preBoundaryDecIt;   // now at 0
    auto& preBoundaryDecrementResult = --preBoundaryDecIt;   // now at 1
    EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
    EXPECT_EQ(*preBoundaryDecIt, 1);

    auto postBoundaryDecIt = this->m_slotMap.rend();
    --postBoundaryDecIt;   // now at 0
    const auto postBoundaryDecrementResult = postBoundaryDecIt--;
    EXPECT_EQ(*postBoundaryDecrementResult, 0);
    EXPECT_EQ(*postBoundaryDecIt, 1);

    // Remove elements straddling the boundary and check the holes are skipped
    this->m_slotMap.Remove(keys[63]);
    this->m_slotMap.Remove(keys[64]);

    count = 0;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        EXPECT_NE(*it, 63);
        EXPECT_NE(*it, 64);
        ++count;
    }
    EXPECT_EQ(count, elementCount - 2);

    // A fully empty leading page must be skipped when retreating past it (rend() side)
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> leadingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        leadingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 0; value < 64; ++value)
    {
        this->m_slotMap.Remove(leadingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, elementCount - 64);
    EXPECT_EQ(sum, 2934u);   // 64 + 65 + ... + 99

    // A fully empty trailing page must be skipped by rbegin()/SeekLastFrom
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> trailingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        trailingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 64; value < elementCount; ++value)
    {
        this->m_slotMap.Remove(trailingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 64u);
    EXPECT_EQ(sum, 2016u);   // 0 + 1 + ... + 63
}

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

TYPED_TEST(SlotMapFixture, ConstReverseIterator)
{
    const TypeParam& constMap = this->m_slotMap;

    // const_reverse_iterator is reachable two ways: crbegin()/crend(), or rbegin()/rend() on a
    // const object
    EXPECT_EQ(this->m_slotMap.crbegin(), this->m_slotMap.crend());
    EXPECT_EQ(constMap.rbegin(), constMap.rend());

    std::ignore = this->m_slotMap.Insert(1);
    const SlotMapKey key2 = this->m_slotMap.Insert(2);
    std::ignore = this->m_slotMap.Insert(3);

    std::uint32_t expected = 3;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        --expected;
    }

    expected = 3;
    for (auto it = constMap.rbegin(); it != constMap.rend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        --expected;
    }

    // Pre-increment advances and returns *this
    auto preIt = this->m_slotMap.crbegin();
    auto& preIncrementResult = ++preIt;
    EXPECT_EQ(&preIncrementResult, &preIt);
    EXPECT_EQ(*preIt, 2);

    // Post-increment advances but returns the old value
    auto postIt = this->m_slotMap.crbegin();
    const auto postIncrementResult = postIt++;
    EXPECT_EQ(*postIncrementResult, 3);
    EXPECT_EQ(*postIt, 2);

    // Pre-decrement retreats and returns *this
    auto preDecIt = this->m_slotMap.crend();
    --preDecIt;   // now at 1
    auto& preDecrementResult = --preDecIt;   // now at 2
    EXPECT_EQ(&preDecrementResult, &preDecIt);
    EXPECT_EQ(*preDecIt, 2);

    // Post-decrement retreats but returns the old value
    auto postDecIt = this->m_slotMap.crend();
    --postDecIt;   // now at 1
    const auto postDecrementResult = postDecIt--;
    EXPECT_EQ(*postDecrementResult, 1);
    EXPECT_EQ(*postDecIt, 2);

    // Removed slots are skipped identically through crbegin()/crend() and the const-object overloads
    this->m_slotMap.Remove(key2);

    std::size_t count = 0;
    std::uint32_t sum = 0;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    count = 0;
    sum = 0;
    for (auto it = constMap.rbegin(); it != constMap.rend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 2);
    EXPECT_EQ(sum, 4);

    // Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
    this->m_slotMap.Reset();

    constexpr std::uint32_t elementCount = 100;
    eastl::array<SlotMapKey, elementCount> keys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        keys[value] = this->m_slotMap.Insert(value);
    }

    expected = elementCount - 1;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        EXPECT_EQ(*it, expected);
        --expected;
    }

    // Explicitly cross the page boundary (value 64 -> 63) forward and backward
    auto boundaryIt = this->m_slotMap.crbegin();
    while (*boundaryIt != 64)
    {
        ++boundaryIt;
    }
    ++boundaryIt;
    EXPECT_EQ(*boundaryIt, 63);
    --boundaryIt;
    EXPECT_EQ(*boundaryIt, 64);

    // Pre/post-increment/decrement across the boundary
    auto preBoundaryIt = this->m_slotMap.crbegin();
    auto& preBoundaryIncrementResult = ++preBoundaryIt;
    EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
    EXPECT_EQ(*preBoundaryIt, 98);

    auto postBoundaryIt = this->m_slotMap.crbegin();
    const auto postBoundaryIncrementResult = postBoundaryIt++;
    EXPECT_EQ(*postBoundaryIncrementResult, 99);
    EXPECT_EQ(*postBoundaryIt, 98);

    auto preBoundaryDecIt = this->m_slotMap.crend();
    --preBoundaryDecIt;   // now at 0
    auto& preBoundaryDecrementResult = --preBoundaryDecIt;   // now at 1
    EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
    EXPECT_EQ(*preBoundaryDecIt, 1);

    auto postBoundaryDecIt = this->m_slotMap.crend();
    --postBoundaryDecIt;   // now at 0
    const auto postBoundaryDecrementResult = postBoundaryDecIt--;
    EXPECT_EQ(*postBoundaryDecrementResult, 0);
    EXPECT_EQ(*postBoundaryDecIt, 1);

    // Remove elements straddling the boundary and check the holes are skipped
    this->m_slotMap.Remove(keys[63]);
    this->m_slotMap.Remove(keys[64]);

    count = 0;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        EXPECT_NE(*it, 63);
        EXPECT_NE(*it, 64);
        ++count;
    }
    EXPECT_EQ(count, elementCount - 2);

    // A fully empty leading page must be skipped when retreating past it (crend() side)
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> leadingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        leadingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 0; value < 64; ++value)
    {
        this->m_slotMap.Remove(leadingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, elementCount - 64);
    EXPECT_EQ(sum, 2934u);   // 64 + 65 + ... + 99

    // A fully empty trailing page must be skipped by crbegin()/SeekLastFrom
    this->m_slotMap.Reset();
    eastl::array<SlotMapKey, elementCount> trailingKeys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        trailingKeys[value] = this->m_slotMap.Insert(value);
    }
    for (std::uint32_t value = 64; value < elementCount; ++value)
    {
        this->m_slotMap.Remove(trailingKeys[value]);
    }

    count = 0;
    sum = 0;
    for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
    {
        ++count;
        sum += *it;
    }
    EXPECT_EQ(count, 64u);
    EXPECT_EQ(sum, 2016u);   // 0 + 1 + ... + 63
}

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

class StableSlotMapFixture: public ::testing::Test
{
  protected:
    StableSlotMap<std::uint32_t> m_slotMap;
};

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

TEST_F(StableSlotMapFixture, PointersAreStable)
{
    const SlotMapKey key = m_slotMap.Insert(42);
    const std::uint32_t* const pointer = m_slotMap.Get(key);

    // Inserting enough elements to span multiple pages (PAGE_SIZE is 64) must not move already
    // -inserted data: unlike DenseSlotMap's contiguous eastl::vector, growth only appends new
    // Page objects (each individually heap-allocated), it never reallocates existing ones.
    constexpr std::uint32_t elementCount = 200;
    eastl::array<SlotMapKey, elementCount> keys;
    for (std::uint32_t value = 0; value < elementCount; ++value)
    {
        keys[value] = m_slotMap.Insert(value);
    }

    EXPECT_EQ(m_slotMap.Get(key), pointer);
    EXPECT_EQ(*pointer, 42);

    // Removing unrelated elements, including ones sharing the same page as key, must not move it
    // either: Remove() only destroys that slot in place, it never compacts the page's array.
    for (std::uint32_t value = 0; value < elementCount; value += 2)
    {
        m_slotMap.Remove(keys[value]);
    }

    EXPECT_EQ(m_slotMap.Get(key), pointer);
    EXPECT_EQ(*pointer, 42);

    // Re-inserting into the freed slots must not move it either
    for (std::uint32_t value = 0; value < elementCount / 2; ++value)
    {
        std::ignore = m_slotMap.Insert(value);
    }

    EXPECT_EQ(m_slotMap.Get(key), pointer);
    EXPECT_EQ(*pointer, 42);
}
}   // namespace Bigfoot
