// Pruebas de las funciones hash y de ChainTable: casos dirigidos y operaciones al azar,
// con semilla fija, contra std::unordered_map.
#include <cassert>
#include <cstdint>
#include <iostream>
#include <random>
#include <string>
#include <unordered_map>

#include "chain_table.h"
#include "hash_functions.h"
#include "tracked.h"

namespace {

// Vectores de la página oficial de FNV (test_fnv.c, fnv1a_64_vector).
void test_fnv1a_official_vectors() {
    assert(fnv1a("") == 0xcbf29ce484222325ULL);
    assert(fnv1a("a") == 0xaf63dc4c8601ec8cULL);
    assert(fnv1a("b") == 0xaf63df4c8601f1a5ULL);
    assert(fnv1a("foobar") == 0x85944171f73967e8ULL);
    // Un byte mayor que 127: falla si no se convierte a unsigned char.
    assert(fnv1a(std::string("\xff\x00\x00\x01", 4)) == 0x6961196491cc682dULL);
    assert(fnv1a(std::string("\x01\x00\x00\xff", 4)) == 0xad2bb1774799dfe9ULL);
}

void test_sum_hash() {
    assert(sum_hash("") == 0);
    assert(sum_hash("ab") == 97 + 98);
    assert(sum_hash("amor") == sum_hash("roma"));
    assert(sum_hash("ad") == sum_hash("bc"));
    // "á" son dos bytes en UTF-8: 0xC3 y 0xA1.
    assert(sum_hash("á") == 0xC3 + 0xA1);
}

void test_set_and_get() {
    ChainTable<int> table(4, fnv1a);
    assert(table.get("lima") == nullptr);
    table.set("lima", 1);
    table.set("cusco", 2);
    assert(*table.get("lima") == 1);
    table.set("lima", 3);
    assert(*table.get("lima") == 3);
    assert(table.size() == 2);
    assert(table.check_invariants());
}

void test_collisions_keep_both() {
    ChainTable<int> table(16, sum_hash);
    table.set("amor", 1);
    table.set("roma", 2);
    assert(*table.get("amor") == 1);
    assert(*table.get("roma") == 2);
    assert(table.chain_length(sum_hash("amor") % 16) == 2);
    // Se inserta adelante: "roma" está primero, y "amor" cuesta dos comparaciones.
    assert(table.comparisons_for("roma") == 1);
    assert(table.comparisons_for("amor") == 2);
}

void test_no_objects_left_behind() {
    {
        ChainTable<Tracked> table(4, fnv1a);
        for (int i = 0; i < 100; i++) {
            table.set("k" + std::to_string(i % 40), Tracked(i));
        }
        assert(table.size() == 40);
    }
    assert(Tracked::counts().live == 0);
}

void test_random_against_library() {
    std::mt19937 rng(42);
    ChainTable<int> table(64, fnv1a);
    std::unordered_map<std::string, int> model;
    for (int step = 0; step < 50000; step++) {
        std::string key = "k" + std::to_string(rng() % 2000);
        if (rng() % 2 == 0) {
            int value = static_cast<int>(rng() % 1000);
            table.set(key, value);
            model[key] = value;
        } else {
            const int* found = table.get(key);
            auto it = model.find(key);
            assert((found == nullptr) == (it == model.end()));
            if (found != nullptr) {
                assert(*found == it->second);
            }
        }
        assert(table.size() == model.size());
    }
    assert(table.check_invariants());
}

}  // namespace

int main() {
    test_fnv1a_official_vectors();
    test_sum_hash();
    test_set_and_get();
    test_collisions_keep_both();
    test_no_objects_left_behind();
    test_random_against_library();
    std::cout << "ok\n";
}
