#include "bx/hash.h"
#include <cassert>
#include <cstdint>

template <typename T> struct HashMapInsertResult
{
    T& Obj = nullptr;
    bool ElementExists = false;
};

template <typename T> struct HashMapInsertResultConst
{
    const T& Obj = nullptr;
    bool ElementExists = false;
};

// BitSize 16 will make the HashTable ~200kb with 65k entries
template <typename T, uint32_t BitSize> struct HashMapCore
{
    static constexpr uint32_t SlotCount = (1 << BitSize);
    static constexpr uint32_t RequiredDataSize = SlotCount * sizeof(T);

    T* Data = nullptr;
    uint32_t HashTable[SlotCount]{0};

    // External array that should be RequiredDataSize bytes big
    void Init(T* data, uint64_t dataSize)
    {
        Data = data;
        assert(data != nullptr);
        assert(dataSize == RequiredDataSize);
    }

    bool Has(uint32_t hash) const
    {
        return HashTable[FindIdx(hash)] == hash;
    }

    HashMapInsertResult<T> Get(uint32_t hash)
    {
        uint16_t idx = FindIdx(hash);
        return {Data[idx], HashTable[idx] == hash};
    }

    const T& Get(uint32_t hash) const
    {
        uint16_t idx = FindIdx(hash);
        return {Data[idx], HashTable[idx] == hash};
    }

    bool Remove(uint32_t hash); // TODO: requires tombstones so later entries survive

  private:
    uint16_t FindIdx(uint32_t hash) const
    {
        uint32_t mask = SlotCount - 1;
        uint32_t idx = hash & mask;
        uint32_t tableEntry = HashTable[idx];
        uint32_t stepCount = 0;
        while (tableEntry != hash && tableEntry != 0 && stepCount < UINT16_MAX)
        {
            idx = (idx + 1) % SlotCount;
            tableEntry = HashTable[idx];
            ++stepCount;
        }
        assert(stepCount < UINT16_MAX);
        return idx;
    }
};

namespace HashMapTests
{
    struct AAA
    {
        int X = 3;
        int Y = 42;
    };

    inline void Test()
    {
        HashMapCore<AAA, 16> testMap;
        AAA* backingData = new AAA[testMap.RequiredDataSize]();
        testMap.Init(backingData, testMap.RequiredDataSize);
        AAA test{};
        AAA test2{5, 6};
        testMap.Get(bx::hash<bx::HashMurmur3>(test)).Obj = test;
        testMap.Get(bx::hash<bx::HashMurmur3>(test2)).Obj = test2;
        delete[] backingData;
    }

} // namespace HashMapTests
