// Pruebas de lo que muestra la clase: la comparación por grupos y la tabla de juguete.
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <random>
#include <string>
#include <unordered_map>
#include <unordered_set>

#include "mini_swiss.h"

namespace {

// La versión lenta, para comparar: byte por byte.
bool byte_matches(std::uint64_t ctrl, int slot, unsigned char h2) {
    return static_cast<unsigned char>(ctrl >> (slot * 8)) == h2;
}

void test_fnv_vectors() {
    assert(demo_hash("") == 0xcbf29ce484222325ULL);
    assert(demo_hash("a") == 0xaf63dc4c8601ec8cULL);
    assert(demo_hash("foobar") == 0x85944171f73967e8ULL);
}

// match_h2 nunca pierde una coincidencia, y solo marca de más justo después de una real.
void test_match_h2_against_bytes() {
    std::mt19937_64 rng(2026);
    for (int round = 0; round < 200000; round++) {
        const std::uint64_t ctrl = rng() & 0x7F7F7F7F7F7F7F7FULL;  // bytes de casillas llenas
        const unsigned char h2 = rng() & 0x7F;
        const std::uint64_t marks = match_h2(ctrl, h2);
        bool earlier_real = false;
        for (int slot = 0; slot < 8; slot++) {
            const bool marked = (marks >> (slot * 8 + 7)) & 1;
            const bool real = byte_matches(ctrl, slot, h2);
            if (real) {
                assert(marked);
                earlier_real = true;
            } else if (marked) {
                assert(earlier_real);
            }
        }
    }
}

void test_match_h2_ignores_empty_and_deleted() {
    const std::uint64_t ctrl = kLowBits * kCtrlEmpty;
    const std::uint64_t mixed = (ctrl & ~std::uint64_t{0xFF}) | kCtrlDeleted;
    for (int h2 = 0; h2 < 128; h2++) {
        assert(match_h2(ctrl, static_cast<unsigned char>(h2)) == 0);
        assert(match_h2(mixed, static_cast<unsigned char>(h2)) == 0);
    }
}

void test_match_empty() {
    assert(match_empty(kLowBits * kCtrlEmpty) == kHighBits);
    assert(match_empty(kLowBits * kCtrlDeleted) == 0);
    assert(match_empty(kLowBits * 0x7F) == 0);
    assert(match_empty(kLowBits * 0x00) == 0);
}

// La norma pide que insertar no invalide punteros a los elementos, aunque cambien las casillas.
void test_unordered_map_keeps_pointers() {
    std::unordered_map<std::string, int> map;
    map["fija"] = 7;
    const int* address = &map["fija"];
    const std::size_t buckets = map.bucket_count();
    for (int i = 0; i < 10000; i++) {
        map[std::to_string(i)] = i;
    }
    assert(map.bucket_count() > buckets);
    assert(address == &map["fija"]);
    assert(*address == 7);
}

void test_mini_swiss_against_std() {
    std::mt19937 rng(2026);
    MiniSwiss swiss(256);
    std::unordered_set<std::string> model;
    for (int i = 0; i < 1500; i++) {  // 1500 de 2048 casillas
        const std::string key = "k" + std::to_string(rng());
        if (model.insert(key).second) {
            swiss.insert(key);
        }
    }
    for (const std::string& key : model) {
        assert(swiss.contains(key));
    }
    for (int i = 0; i < 5000; i++) {
        const std::string key = "z" + std::to_string(i);
        assert(!swiss.contains(key));
    }
}

}  // namespace

int main() {
    test_fnv_vectors();
    test_match_h2_against_bytes();
    test_match_h2_ignores_empty_and_deleted();
    test_match_empty();
    test_unordered_map_keeps_pointers();
    test_mini_swiss_against_std();
    std::cout << "ok\n";
    return 0;
}
