// Corre una prueba por nombre: map_tests set_and_get. Sin argumentos, todas.
// Las de map_suite.h valen para cualquier diccionario. Las de abajo son propias del encadenamiento.
#include <cassert>
#include <cstddef>
#include <cstring>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "alloc_counter.h"
#include "hash_map.h"
#include "map_suite.h"
#include "tracked.h"

namespace {

using Ints = HashMap<int>;

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

void test_capacity_doubles_past_one() {
    Ints map;
    assert(map.capacity() == 8);
    std::vector<std::string> keys = make_keys(17);
    for (int i = 0; i < 8; i++) {
        map.set(keys[i], i);
    }
    assert(map.capacity() == 8);
    map.set(keys[8], 8);
    assert(map.capacity() == 16);
    for (int i = 9; i < 16; i++) {
        map.set(keys[i], i);
    }
    assert(map.capacity() == 16);
    map.set(keys[16], 16);
    assert(map.capacity() == 32);
    assert(map.check_invariants());
}

void test_overwrite_does_not_grow() {
    Ints map;
    std::vector<std::string> keys = make_keys(8);
    for (int i = 0; i < 8; i++) {
        map.set(keys[i], i);
    }
    for (int i = 0; i < 8; i++) {
        map.set(keys[i], -i);
    }
    assert(map.capacity() == 8);
    assert(map.size() == 8);
}

void test_remove_does_not_shrink() {
    Ints map;
    std::vector<std::string> keys = make_keys(100);
    for (int i = 0; i < 100; i++) {
        map.set(keys[i], i);
    }
    std::size_t capacity = map.capacity();
    for (int i = 0; i < 100; i++) {
        assert(map.remove(keys[i]));
    }
    assert(map.size() == 0);
    assert(map.capacity() == capacity);
    assert(map.check_invariants());
}

// Con α enorme nunca crece: 8 casillas y cadenas largas. Se borra al inicio, en el medio y al final.
void test_long_chains() {
    Ints map;
    map.set_max_load_factor(1000.0);
    std::vector<std::string> keys = make_keys(400);
    for (int i = 0; i < 400; i++) {
        map.set(keys[i], i);
    }
    assert(map.capacity() == 8);
    for (int i = 0; i < 400; i += 3) {
        assert(map.remove(keys[i]));
        assert(map.check_invariants());
    }
    for (int i = 0; i < 400; i++) {
        const int* found = map.get(keys[i]);
        assert((found == nullptr) == (i % 3 == 0));
    }
    for (int i = 0; i < 400; i++) {
        assert(map.remove(keys[i]) == (i % 3 != 0));
    }
    assert(map.size() == 0);
    assert(map.check_invariants());
}

void test_lower_load_factor_grows_now() {
    Ints map;
    std::vector<std::string> keys = make_keys(8);
    for (int i = 0; i < 8; i++) {
        map.set(keys[i], i);
    }
    map.set_max_load_factor(0.25);
    assert(map.capacity() == 32);
    assert(map.check_invariants());
    for (int i = 0; i < 8; i++) {
        assert(*map.get(keys[i]) == i);
    }
}

// Crecer vuelve a enlazar los nodos: una reserva nueva para las casillas, y nada más.
void test_growth_allocates_only_slots() {
    std::vector<std::string> keys = make_keys(1000);
    std::size_t before = allocation_count();
    {
        Ints map;
        for (int i = 0; i < 1000; i++) {
            map.set(keys[i], i);
        }
        std::size_t growths = 0;
        for (std::size_t capacity = map.capacity(); capacity > 8; capacity /= 2) {
            growths++;
        }
        // Una reserva inicial, una por crecimiento y una por nodo.
        assert(allocation_count() - before == 1 + growths + 1000);
    }
}

void test_growth_copies_no_values() {
    Tracked::counts() = Tracked::Counts{};
    {
        HashMap<Tracked> map;
        std::vector<std::string> keys = make_keys(500);
        Tracked value(7);
        for (const std::string& key : keys) {
            map.set(key, value);
        }
        // Una copia por nodo nuevo, y ninguna más al crecer.
        assert(Tracked::counts().copies == 500);
        assert(Tracked::counts().live == 501);
        map.set(keys[0], value);
        assert(Tracked::counts().copies == 501);
        assert(Tracked::counts().live == 501);
        assert(map.remove(keys[0]));
        assert(Tracked::counts().live == 500);
    }
    assert(Tracked::counts().live == 0);
}

void test_pointers_survive_growth() {
    Ints map;
    map.set("lima", 42);
    const int* before = map.get("lima");
    std::vector<std::string> keys = make_keys(1000);
    for (int i = 0; i < 1000; i++) {
        map.set(keys[i], i);
    }
    assert(map.get("lima") == before);
    assert(*before == 42);
}

void test_value_from_the_same_table() {
    HashMap<std::string> map;
    map.set("origen", std::string(100, 'v'));
    map.set("origen", *map.get("origen"));
    assert(*map.get("origen") == std::string(100, 'v'));
    // El crecimiento ocurre en medio de la llamada: value sigue siendo válido.
    for (int i = 0; i < 1000; i++) {
        map.set("k" + std::to_string(i), *map.get("origen"));
    }
    assert(*map.get("k999") == std::string(100, 'v'));
}

struct NoDefault {
    explicit NoDefault(int value) : value(value) {}
    int value;
};

void test_value_without_default_constructor() {
    HashMap<NoDefault> map;
    map.set("a", NoDefault(1));
    map.set("a", NoDefault(2));
    assert(map.get("a")->value == 2);
}

void test_no_objects_left_behind() {
    Tracked::counts() = Tracked::Counts{};
    {
        HashMap<Tracked> map;
        for (int i = 0; i < 100; i++) {
            map.set("k" + std::to_string(i), Tracked(i));
        }
        HashMap<Tracked> copy(map);
        HashMap<Tracked> assigned;
        assigned = copy;
        HashMap<Tracked> moved(std::move(copy));
        assigned = std::move(moved);
        assert(map.remove("k5"));
        assert(Tracked::counts().live == 199);
    }
    assert(Tracked::counts().live == 0);
}

void test_moves_are_noexcept() {
    static_assert(std::is_nothrow_move_constructible_v<Ints>);
    static_assert(std::is_nothrow_move_assignable_v<Ints>);
}

struct Test {
    const char* name;
    void (*run)();
};

const std::vector<Test> TESTS = {
    {"new_is_empty", test_new_is_empty<Ints>},
    {"set_and_get", test_set_and_get<Ints>},
    {"overwrite", test_overwrite<Ints>},
    {"contains", test_contains<Ints>},
    {"remove_present", test_remove_present<Ints>},
    {"remove_absent", test_remove_absent<Ints>},
    {"unusual_keys", test_unusual_keys<Ints>},
    {"growth", test_growth<Ints>},
    {"copy_is_independent", test_copy_is_independent<Ints>},
    {"self_assignment", test_self_assignment<Ints>},
    {"move_leaves_empty_and_usable", test_move_leaves_empty_and_usable<Ints>},
    {"random_small", test_random_small<Ints>},
    {"random_large", test_random_large<Ints>},
    {"run_map_suite", run_map_suite<Ints>},
    {"capacity_doubles_past_one", test_capacity_doubles_past_one},
    {"overwrite_does_not_grow", test_overwrite_does_not_grow},
    {"remove_does_not_shrink", test_remove_does_not_shrink},
    {"long_chains", test_long_chains},
    {"lower_load_factor_grows_now", test_lower_load_factor_grows_now},
    {"growth_allocates_only_slots", test_growth_allocates_only_slots},
    {"growth_copies_no_values", test_growth_copies_no_values},
    {"pointers_survive_growth", test_pointers_survive_growth},
    {"value_from_the_same_table", test_value_from_the_same_table},
    {"value_without_default_constructor", test_value_without_default_constructor},
    {"no_objects_left_behind", test_no_objects_left_behind},
    {"moves_are_noexcept", test_moves_are_noexcept},
};

}  // namespace

int main(int argc, char** argv) {
    for (const Test& test : TESTS) {
        if (argc < 2 || std::strcmp(argv[1], test.name) == 0) {
            test.run();
            if (argc >= 2) {
                return 0;
            }
        }
    }
    if (argc >= 2) {
        std::cerr << "no existe la prueba " << argv[1] << "\n";
        return 2;
    }
    std::cout << "ok\n";
}
