// Corre las pruebas: deque_tests array, deque_tests linked, o con el nombre de una sola prueba.
// Las dos representaciones pasan por la misma lista de pruebas comunes.
#include <cstring>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "array_deque.h"
#include "deque_suite.h"
#include "linked_deque.h"

namespace {

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

template <template <typename> class Deque>
std::vector<Test> common_tests() {
    using Ints = Deque<int>;
    return {
        {"new_is_empty", test_new_is_empty<Ints>},
        {"push_back_keeps_order", test_push_back_keeps_order<Ints>},
        {"push_front_reverses", test_push_front_reverses<Ints>},
        {"as_stack", test_as_stack<Ints>},
        {"as_queue", test_as_queue<Ints>},
        {"growth_while_wrapped", test_growth_while_wrapped<Ints>},
        {"index_reads_and_writes", test_index_reads_and_writes<Ints>},
        {"range_for", test_range_for<Ints>},
        {"iterator_operators", test_iterator_operators<Ints>},
        {"iterator_after_wrap", test_iterator_after_wrap<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>},
        {"no_objects_left_behind", test_no_objects_left_behind<Deque<Tracked>>},
        {"rvalue_push_does_not_copy", test_rvalue_push_does_not_copy<Deque<Tracked>>},
        {"lvalue_push_copies_once", test_lvalue_push_copies_once<Deque<Tracked>>},
        {"push_own_element", test_push_own_element<Deque<std::string>>},
        {"pop_releases_value", test_pop_releases_value<Deque<std::shared_ptr<int>>>},
        {"random_ints", [] {
             for (unsigned seed : {1u, 2u, 3u}) {
                 random_against_std_deque<Deque<int>, int>(seed, 20000, [](unsigned x) {
                     return static_cast<int>(x % 1000);
                 });
             }
         }},
        {"random_strings", [] {
             random_against_std_deque<Deque<std::string>, std::string>(4u, 10000, [](unsigned x) {
                 return std::to_string(x % 1000) + std::string(x % 40, '-');
             });
         }},
    };
}

std::vector<Test> tests_for(const std::string& kind) {
    if (kind == "array") {
        std::vector<Test> tests = common_tests<ArrayDeque>();
        tests.push_back({"few_allocations", test_array_few_allocations<ArrayDeque<int>>});
        tests.push_back({"capacity_doubles", test_array_capacity_doubles<ArrayDeque<int>>});
        tests.push_back({"ring_reuses_space", test_array_ring_reuses_space<ArrayDeque<int>>});
        tests.push_back({"move_is_noexcept", test_array_move_is_noexcept<ArrayDeque<int>>});
        return tests;
    }
    std::vector<Test> tests = common_tests<LinkedDeque>();
    tests.push_back({"one_allocation_per_push", test_linked_one_allocation_per_push<LinkedDeque<int>>});
    tests.push_back({"elements_stay_in_place", test_linked_elements_stay_in_place<LinkedDeque<int>>});
    tests.push_back({"move_assign_is_noexcept", test_linked_move_assign_is_noexcept<LinkedDeque<int>>});
    tests.push_back({"shuffle_nodes", test_linked_shuffle_nodes<LinkedDeque<int>>});
    return tests;
}

}  // namespace

int main(int argc, char** argv) {
    if (argc < 2 || (std::strcmp(argv[1], "array") != 0 && std::strcmp(argv[1], "linked") != 0)) {
        std::cerr << "uso: deque_tests array|linked [prueba]\n";
        return 2;
    }
    bool found = false;
    for (const Test& test : tests_for(argv[1])) {
        if (argc == 3 && std::strcmp(argv[2], "--list") == 0) {
            std::cout << test.name << "\n";
            found = true;
        } else if (argc == 2 || std::strcmp(argv[2], test.name) == 0) {
            test.run();
            std::cout << argv[1] << " " << test.name << ": ok" << std::endl;
            found = true;
        }
    }
    if (!found) {
        std::cerr << "no existe esa prueba\n";
        return 2;
    }
    return 0;
}
