// Claves elegidas a propósito para que caigan todas en la misma casilla.
// Las tablas no tienen defensa: la función hash es pública y no usa ningún secreto.
#include <cstddef>
#include <cstdio>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

#include "bench.h"
#include "chain_table.h"

// Las primeras claves "k0", "k1", ... cuyo hash es múltiplo de divisor.
template <typename Hash>
std::vector<std::string> crafted_keys(std::size_t count, std::size_t divisor, Hash hash) {
    std::vector<std::string> keys;
    for (std::size_t i = 0; keys.size() < count; i++) {
        std::string key = "k" + std::to_string(i);
        if (hash(key) % divisor == 0) {
            keys.push_back(key);
        }
    }
    return keys;
}

std::vector<std::string> plain_keys(std::size_t count) {
    std::vector<std::string> keys;
    for (std::size_t i = 0; i < count; i++) {
        keys.push_back("k" + std::to_string(i));
    }
    return keys;
}

void own_table(const char* name, const std::vector<std::string>& keys, std::size_t m) {
    ChainTable<int> table(m, fnv1a);
    for (const std::string& key : keys) {
        table.set(key, 1);
    }
    std::size_t longest = 0;
    for (std::size_t i = 0; i < m; i++) {
        longest = std::max(longest, table.chain_length(i));
    }
    double total = 0;
    for (const std::string& key : keys) {
        total += static_cast<double>(table.comparisons_for(key));
    }
    std::printf("%-10s m=%-5zu la mayor cadena %5zu, comparaciones por busqueda %8.1f\n", name, m, longest,
                total / static_cast<double>(keys.size()));
}

void library_table(const char* name, const std::vector<std::string>& keys, std::size_t buckets) {
    std::size_t largest = 0;
    double seconds = median_seconds(5, [&] {
        std::unordered_map<std::string, int> table;
        table.reserve(buckets);
        for (const std::string& key : keys) {
            table[key] = 1;
        }
        largest = table.bucket_size(table.bucket(keys[0]));
        keep(table.size());
    });
    std::printf("%-10s la casilla de keys[0] tiene %5zu, %9.2f ms\n", name, largest, seconds * 1000);
}

int main() {
    const std::size_t n = 4000;
    // Si el hash es múltiplo de 8192, también lo es de 4096, 2048, ... Crecer hasta 8192 no ayuda.
    std::vector<std::string> crafted = crafted_keys(n, 8192, fnv1a);
    std::printf("%zu claves; la ultima elegida es %s\n", n, crafted.back().c_str());
    own_table("normales", plain_keys(n), 4096);
    own_table("elegidas", crafted, 4096);
    own_table("elegidas", crafted, 8192);

    std::unordered_map<std::string, int> probe;
    probe.reserve(n);
    std::size_t buckets = probe.bucket_count();
    std::printf("unordered_map con reserve(%zu): bucket_count %zu\n", n, buckets);
    std::vector<std::string> against_library = crafted_keys(n, buckets, std::hash<std::string>{});
    library_table("normales", plain_keys(n), n);
    library_table("elegidas", against_library, n);
}
