// Pruebas públicas del laboratorio. Comprueban cuentas y resultados, nunca tiempos.
#include <algorithm>
#include <cstddef>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#include "check.h"
#include "measure.h"
#include "vector_ops.h"

namespace {

void three_steps_per_item(long long n, Work& work) {
    for (long long i = 0; i < 3 * n; i++) {
        work.step();
    }
}

void test_fill_ratios() {
    std::vector<Row> rows(3);
    rows[0].seconds = 1.0;
    rows[1].seconds = 4.0;
    rows[2].seconds = 2.0;
    fill_ratios(rows);
    check(rows[0].ratio == 0.0, "fill_ratios: la primera fila queda en 0");
    check(rows[1].ratio == 4.0, "fill_ratios: segunda fila");
    check(rows[2].ratio == 0.5, "fill_ratios: tercera fila");
}

void test_doubling_experiment() {
    std::vector<Row> rows = doubling_experiment(three_steps_per_item, 100, 4, 3);
    check(rows.size() == 4, "doubling_experiment: una fila por cada n");
    long long n = 100;
    for (std::size_t i = 0; i < rows.size(); i++) {
        check(rows[i].n == n, "doubling_experiment: n se duplica desde first_n");
        check(rows[i].steps == 3 * n, "doubling_experiment: steps son los de una sola llamada");
        check(rows[i].seconds > 0, "doubling_experiment: falta el tiempo");
        n *= 2;
    }
    check(rows[0].ratio == 0.0, "doubling_experiment: la primera razón es 0");
    check(rows[1].ratio == rows[1].seconds / rows[0].seconds,
          "doubling_experiment: falta llamar a fill_ratios");
}

void test_write_csv() {
    Row first;
    first.n = 8;
    first.steps = 16;
    first.seconds = 0.5;
    Row second;
    second.n = 16;
    second.steps = 32;
    second.seconds = 1.5;
    second.ratio = 3;
    std::ostringstream out;
    write_csv(out, "demo", {first, second});
    check(out.str() == "demo,8,16,0.5,0\ndemo,16,32,1.5,3\n", "write_csv: formato de las líneas");
}

void test_fill() {
    check(fill_back(0).empty(), "fill_back(0) es vacío");
    check(fill_front(0).empty(), "fill_front(0) es vacío");
    check(fill_back(4) == std::vector<int>({0, 1, 2, 3}), "fill_back(4)");
    check(fill_front(4) == std::vector<int>({3, 2, 1, 0}), "fill_front(4)");
}

void test_search_counts() {
    std::vector<int> values;
    for (int i = 0; i < 1000; i++) {
        values.push_back(2 * i);
    }
    long long comparisons = 0;
    check(linear_search(values, 0, comparisons) == 0, "lineal: primer elemento");
    check(comparisons == 1, "lineal: mejor caso, una comparación");
    comparisons = 0;
    check(linear_search(values, 1, comparisons) == -1, "lineal: ausente");
    check(comparisons == 1000, "lineal: peor caso, n comparaciones");
    comparisons = 0;
    check(linear_search(values, 20, comparisons) == 10, "lineal: en medio");
    check(comparisons == 11, "lineal: índice 10, once comparaciones");

    // Con 1000 elementos: piso de log2(1000) = 9. Se admite una vuelta de más.
    for (int key : {-5, 0, 1, 998, 1998, 1999, 5000}) {
        comparisons = 0;
        binary_search(values, key, comparisons);
        check(comparisons >= 1, "binaria: debe contar lo que mira");
        check(comparisons <= 11, "binaria: demasiadas comparaciones para n = 1000");
    }
    std::vector<int> empty;
    comparisons = 0;
    check(binary_search(empty, 3, comparisons) == -1, "binaria: arreglo vacío");
    check(linear_search(empty, 3, comparisons) == -1, "lineal: arreglo vacío");
    check(comparisons == 0, "arreglo vacío: ninguna comparación");
}

// Operaciones al azar, con semilla fija, comparando contra la biblioteca estándar.
void test_search_random() {
    std::mt19937 generator(2026);
    for (int round = 0; round < 300; round++) {
        std::size_t n = generator() % 60;
        std::vector<int> values(n);
        for (int& value : values) {
            value = static_cast<int>(generator() % 100);
        }
        std::sort(values.begin(), values.end());
        for (int key = -1; key <= 100; key++) {
            bool present = std::binary_search(values.begin(), values.end(), key);
            long long comparisons = 0;
            int linear = linear_search(values, key, comparisons);
            int binary = binary_search(values, key, comparisons);
            check((linear != -1) == present, "lineal: no coincide con std::binary_search");
            check((binary != -1) == present, "binaria: no coincide con std::binary_search");
            if (present) {
                check(values[static_cast<std::size_t>(linear)] == key, "lineal: índice equivocado");
                check(values[static_cast<std::size_t>(binary)] == key, "binaria: índice equivocado");
            }
        }
    }
}

}  // namespace

int main() {
    test_fill_ratios();
    test_doubling_experiment();
    test_write_csv();
    test_fill();
    test_search_counts();
    test_search_random();
    std::printf("ok\n");
    return 0;
}
