// Pruebas de Vector<T>. Cada prueba se corre en un proceso aparte, con sanitizers:
//   build/lab/vector_tests            lista los nombres
//   build/lab/vector_tests NOMBRE     corre una
#include <cassert>
#include <cstddef>
#include <cstring>
#include <iostream>
#include <random>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include "tracked.h"
#include "vector.h"

// Más larga que el buffer interno de std::string: copiarla pide memoria, y ASan la vigila.
const std::string LONG_TEXT(64, 'x');

template <typename T>
std::size_t filled(Vector<T>& v, int n) {
    for (int i = 0; i < n; i++) {
        v.push_back(T(i));
    }
    return v.size();
}

void starts_empty() {
    Vector<int> v;
    assert(v.size() == 0);
    assert(v.capacity() == 0);
    v.check_invariants();
}

void push_and_read() {
    Vector<int> v;
    for (int i = 0; i < 100; i++) {
        v.push_back(i * 3);
        v.check_invariants();
    }
    assert(v.size() == 100);
    for (int i = 0; i < 100; i++) {
        assert(v[i] == i * 3);
    }
    v[7] = -1;
    const Vector<int>& view = v;
    assert(view[7] == -1);
}

void capacity_doubles() {
    Vector<int> v;
    std::size_t expected = 0;
    for (int i = 0; i < 1000; i++) {
        if (v.size() == expected) {
            expected = expected == 0 ? 1 : 2 * expected;
        }
        v.push_back(i);
        assert(v.capacity() == expected);
    }
}

void pop_keeps_capacity() {
    Vector<int> v;
    filled(v, 5);
    assert(v.capacity() == 8);
    v.pop_back();
    v.pop_back();
    assert(v.size() == 3);
    assert(v.capacity() == 8);
    assert(v[2] == 2);
    v.push_back(50);
    assert(v[3] == 50);
    v.check_invariants();
}

void reserve_is_exact() {
    Vector<int> v;
    filled(v, 3);
    v.reserve(100);
    assert(v.capacity() == 100);
    assert(v.size() == 3);
    assert(v[0] == 0 && v[1] == 1 && v[2] == 2);
    v.reserve(10);
    assert(v.capacity() == 100);
    v.check_invariants();
}

void reserve_on_empty_then_push() {
    Vector<std::string> v;
    v.reserve(2);
    assert(v.capacity() == 2 && v.size() == 0);
    v.push_back(LONG_TEXT);
    assert(v[0] == LONG_TEXT);
    v.check_invariants();
}

void copy_is_deep() {
    Vector<std::string> a;
    a.push_back(LONG_TEXT);
    a.push_back("b");
    Vector<std::string> b(a);
    assert(b.size() == 2);
    assert(b.capacity() == 2);
    b[0] = "changed";
    b.push_back("c");
    assert(a[0] == LONG_TEXT);
    assert(a.size() == 2);
    a.check_invariants();
    b.check_invariants();
}

void copy_of_empty_has_no_block() {
    Vector<int> a;
    a.reserve(10);
    Vector<int> b(a);
    assert(b.size() == 0);
    assert(b.capacity() == 0);
    b.check_invariants();
    b.push_back(1);
    assert(b[0] == 1);
}

void copy_assign_replaces() {
    Vector<std::string> a, b;
    a.push_back(LONG_TEXT);
    b.push_back("old");
    b.push_back("old");
    b.push_back("old");
    b = a;
    assert(b.size() == 1);
    assert(b[0] == LONG_TEXT);
    b[0] = "changed";
    assert(a[0] == LONG_TEXT);
    b.check_invariants();
}

void copy_assign_to_itself() {
    Vector<std::string> a;
    a.push_back(LONG_TEXT);
    a.push_back("b");
    Vector<std::string>& alias = a;
    a = alias;
    assert(a.size() == 2);
    assert(a[0] == LONG_TEXT);
    a.check_invariants();
}

void move_builds_nothing() {
    Vector<Tracked> a;
    filled(a, 8);
    Tracked::reset();
    Vector<Tracked> b(std::move(a));
    assert(Tracked::counts.copies == 0);
    assert(Tracked::counts.moves == 0);
    assert(Tracked::counts.default_built == 0);
    assert(b.size() == 8);
    assert(b[7].value() == 7);
    b.check_invariants();
}

void moved_from_can_be_reused() {
    Vector<int> a;
    filled(a, 8);
    Vector<int> b(std::move(a));
    assert(a.size() == 0);
    assert(a.capacity() == 0);
    a.check_invariants();
    a.push_back(1);
    assert(a[0] == 1);
    Vector<int> c;
    c = std::move(b);
    assert(b.size() == 0);
    b.check_invariants();
    b.push_back(2);
    assert(b[0] == 2);
    assert(c.size() == 8);
}

void move_assign_frees_old_block() {
    Vector<std::string> a, b;
    a.push_back(LONG_TEXT);
    b.push_back(LONG_TEXT);
    b.push_back(LONG_TEXT);
    b = std::move(a);
    assert(b.size() == 1);
    b.check_invariants();
}

void move_assign_to_itself() {
    Vector<std::string> a;
    a.push_back(LONG_TEXT);
    Vector<std::string>& alias = a;
    a = std::move(alias);
    a.check_invariants();
    Vector<std::string> b;
    b = a;
    b.check_invariants();
}

// Con movimiento noexcept, crecer no copia: 16 movimientos de los argumentos y 15 al crecer.
void growth_moves_when_noexcept() {
    Vector<Tracked> v;
    Tracked::reset();
    filled(v, 16);
    assert(Tracked::counts.copies == 0);
    assert(Tracked::counts.moves == 16 + 15);
}

// Sin noexcept, crecer copia: 1 + 2 + 4 + 8.
void growth_copies_without_noexcept() {
    Vector<TrackedRiskyMove> v;
    TrackedRiskyMove::reset();
    filled(v, 16);
    assert(TrackedRiskyMove::counts.copies == 15);
    assert(TrackedRiskyMove::counts.moves == 16);
}

void lvalue_push_copies_once() {
    Vector<Tracked> v;
    v.reserve(4);
    Tracked item(9);
    Tracked::reset();
    v.push_back(item);
    assert(Tracked::counts.copies == 1);
    assert(Tracked::counts.moves == 0);
    assert(v[0].value() == 9);
    assert(item.value() == 9);
}

void rvalue_push_does_not_copy() {
    Vector<Tracked> v;
    v.reserve(4);
    Tracked item(9);
    Tracked::reset();
    v.push_back(std::move(item));
    assert(Tracked::counts.copies == 0);
    assert(Tracked::counts.moves == 1);
    assert(v[0].value() == 9);
}

// Si value vive en el bloque viejo, crecer no debe invalidarlo antes de leerlo.
void push_own_element_when_full() {
    Vector<std::string> v;
    v.push_back(LONG_TEXT);
    for (int i = 0; i < 10; i++) {
        v.push_back(v[0]);
    }
    assert(v.size() == 11);
    for (std::size_t i = 0; i < v.size(); i++) {
        assert(v[i] == LONG_TEXT);
    }
}

// Lo que dicen los límites de new T[]: un objeto vivo por casilla, y todos mueren al final.
void every_slot_is_alive() {
    Tracked::reset();
    {
        Vector<Tracked> v;
        v.reserve(100);
        assert(Tracked::counts.default_built == 100);
        assert(Tracked::counts.alive() == 100);
    }
    assert(Tracked::counts.alive() == 0);
}

// Crecer construye el bloque entero: 1 + 2 + 4 + 8 + 16 objetos sin argumentos.
void growth_builds_every_slot() {
    Vector<Tracked> v;
    Tracked::reset();
    filled(v, 16);
    assert(v.capacity() == 16);
    assert(Tracked::counts.default_built == 31);
}

class MoveOnly {
public:
    MoveOnly() = default;
    explicit MoveOnly(int value) : value_(value) {}
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&& other) noexcept : value_(std::exchange(other.value_, -1)) {}
    MoveOnly& operator=(MoveOnly&& other) noexcept {
        value_ = std::exchange(other.value_, -1);
        return *this;
    }
    int value() const { return value_; }

private:
    int value_ = 0;
};

void move_only_elements() {
    Vector<MoveOnly> v;
    filled(v, 20);
    assert(v.size() == 20);
    for (int i = 0; i < 20; i++) {
        assert(v[i].value() == i);
    }
}

// Se puede copiar y su movimiento no es noexcept: al crecer se copia. Tiene un std::string
// para que ASan vea una fuga si el bloque nuevo no se libera.
class FailsOnCopy {
public:
    inline static int copies_left = 1000;

    FailsOnCopy() = default;
    explicit FailsOnCopy(int value) : text_(LONG_TEXT + std::to_string(value)) {}
    FailsOnCopy(const FailsOnCopy& other) : text_(other.text_) { spend(); }
    FailsOnCopy& operator=(const FailsOnCopy& other) {
        spend();
        text_ = other.text_;
        return *this;
    }
    FailsOnCopy(FailsOnCopy&& other) : text_(std::move(other.text_)) {}
    FailsOnCopy& operator=(FailsOnCopy&& other) {
        text_ = std::move(other.text_);
        return *this;
    }
    const std::string& text() const { return text_; }

private:
    static void spend() {
        if (copies_left == 0) {
            throw std::runtime_error("falló una copia");
        }
        copies_left--;
    }
    std::string text_;
};

void failed_growth_changes_nothing() {
    Vector<FailsOnCopy> v;
    v.reserve(4);
    filled(v, 4);
    FailsOnCopy::copies_left = 2;
    bool thrown = false;
    try {
        v.reserve(8);
    } catch (const std::runtime_error&) {
        thrown = true;
    }
    assert(thrown);
    assert(v.size() == 4);
    assert(v.capacity() == 4);
    for (int i = 0; i < 4; i++) {
        assert(v[i].text() == LONG_TEXT + std::to_string(i));
    }
    v.check_invariants();
}

// Crecer de a 1000 desde fuera, con reserve: 1000 + 2000 + 3000 + 4000 copias.
void step_growth_counts() {
    Vector<TrackedRiskyMove> v;
    TrackedRiskyMove::reset();
    for (int i = 0; i < 5000; i++) {
        if (v.size() == v.capacity()) {
            v.reserve(v.capacity() + 1000);
        }
        v.push_back(TrackedRiskyMove(i));
    }
    assert(v.capacity() == 5000);
    assert(TrackedRiskyMove::counts.copies == 10000);
}

// Operaciones al azar, con semilla fija, comparando contra std::vector.
void random_against_std_vector() {
    std::mt19937 random(404);
    Vector<std::string> mine;
    std::vector<std::string> model;
    for (int step = 0; step < 20000; step++) {
        unsigned choice = random() % 100;
        if (choice < 55) {
            std::string text = LONG_TEXT + std::to_string(step);
            mine.push_back(text);
            model.push_back(text);
        } else if (choice < 60 && !model.empty()) {
            // Insertar un elemento propio, en cualquier momento.
            std::size_t i = random() % model.size();
            mine.push_back(mine[i]);
            model.push_back(std::string(model[i]));
        } else if (choice < 90 && !model.empty()) {
            mine.pop_back();
            model.pop_back();
        } else if (choice < 93) {
            mine.reserve(random() % 300);
        } else if (choice < 96) {
            Vector<std::string> copy(mine);
            mine = copy;
        } else if (choice < 99) {
            Vector<std::string> moved(std::move(mine));
            mine = std::move(moved);
        } else if (!model.empty()) {
            std::size_t i = random() % model.size();
            mine[i] = "x";
            model[i] = "x";
        }
        mine.check_invariants();
        assert(mine.size() == model.size());
        if (!model.empty()) {
            std::size_t i = random() % model.size();
            assert(mine[i] == model[i]);
        }
    }
    for (std::size_t i = 0; i < model.size(); i++) {
        assert(mine[i] == model[i]);
    }
}

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

const NamedTest TESTS[] = {
    {"starts_empty", starts_empty},
    {"push_and_read", push_and_read},
    {"capacity_doubles", capacity_doubles},
    {"pop_keeps_capacity", pop_keeps_capacity},
    {"reserve_is_exact", reserve_is_exact},
    {"reserve_on_empty_then_push", reserve_on_empty_then_push},
    {"copy_is_deep", copy_is_deep},
    {"copy_of_empty_has_no_block", copy_of_empty_has_no_block},
    {"copy_assign_replaces", copy_assign_replaces},
    {"copy_assign_to_itself", copy_assign_to_itself},
    {"move_builds_nothing", move_builds_nothing},
    {"moved_from_can_be_reused", moved_from_can_be_reused},
    {"move_assign_frees_old_block", move_assign_frees_old_block},
    {"move_assign_to_itself", move_assign_to_itself},
    {"growth_moves_when_noexcept", growth_moves_when_noexcept},
    {"growth_copies_without_noexcept", growth_copies_without_noexcept},
    {"lvalue_push_copies_once", lvalue_push_copies_once},
    {"rvalue_push_does_not_copy", rvalue_push_does_not_copy},
    {"push_own_element_when_full", push_own_element_when_full},
    {"every_slot_is_alive", every_slot_is_alive},
    {"growth_builds_every_slot", growth_builds_every_slot},
    {"move_only_elements", move_only_elements},
    {"failed_growth_changes_nothing", failed_growth_changes_nothing},
    {"step_growth_counts", step_growth_counts},
    {"random_against_std_vector", random_against_std_vector},
};

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