// Pruebas públicas de Stack<T>. Cada una se corre aparte: test_stack nombre.
#include <cassert>
#include <iostream>
#include <random>
#include <stack>
#include <string>
#include <type_traits>
#include <utility>

#include "alloc_count.h"
#include "stack.h"

namespace {

// Cuenta cuántas veces se copia un elemento.
class Counted {
public:
    Counted() = default;
    explicit Counted(int value) : value_(value) {}
    Counted(const Counted& other) : value_(other.value_) {
        copies()++;
    }
    Counted& operator=(const Counted& other) {
        value_ = other.value_;
        copies()++;
        return *this;
    }
    Counted(Counted&&) noexcept = default;
    Counted& operator=(Counted&&) noexcept = default;
    ~Counted() = default;
    int value() const {
        return value_;
    }
    static int& copies() {
        static int count = 0;
        return count;
    }

private:
    int value_ = 0;
};

void test_int_lifo() {
    Stack<int> s(3);
    s.check_invariants();
    assert(s.empty() && !s.full() && s.capacity() == 3);
    s.push(1);
    s.push(2);
    s.push(3);
    s.check_invariants();
    assert(s.full() && s.size() == 3 && s.top() == 3);
    s.pop();
    assert(s.top() == 2);
    s.top() = 20;
    assert(s.top() == 20);
    const Stack<int>& view = s;
    assert(view.top() == 20);
}

void test_string_lifo() {
    Stack<std::string> s(2);
    s.push("hola");
    s.push("un texto bastante largo para que el string pida memoria");
    assert(s.top().size() > 20);
    s.pop();
    assert(s.top() == "hola");
    s.check_invariants();
}

void test_zero_capacity() {
    Stack<std::string> s(0);
    s.check_invariants();
    assert(s.empty() && s.full());
}

template <typename T, typename Make>
void same_as_library(unsigned seed, Make make) {
    std::mt19937 random(seed);
    std::uniform_int_distribution<int> action(0, 3);
    const std::size_t capacity = 200;
    Stack<T> s(capacity);
    std::stack<T> reference;
    for (int i = 0; i < 5000; i++) {
        int choice = action(random);
        if (choice == 3) {
            // Copias y movimientos, entre operaciones normales.
            Stack<T> copy = s;
            copy.check_invariants();
            s = std::move(copy);
            copy.check_invariants();
        } else if ((choice == 0 && !reference.empty()) || reference.size() == capacity) {
            s.pop();
            reference.pop();
        } else {
            T value = make(i);
            s.push(value);
            reference.push(value);
        }
        s.check_invariants();
        assert(s.size() == reference.size());
        assert(s.empty() == reference.empty());
        assert(s.full() == (reference.size() == capacity));
        if (!reference.empty()) {
            assert(s.top() == reference.top());
        }
    }
}

void test_random_int() {
    same_as_library<int>(2026, [](int i) { return i; });
}

void test_random_string() {
    same_as_library<std::string>(2026, [](int i) { return "elemento numero " + std::to_string(i); });
}

void test_copies_are_independent() {
    Stack<std::string> a(3);
    a.push("uno");
    Stack<std::string> b = a;
    b.top() = "dos";
    assert(a.top() == "uno" && b.top() == "dos");
    Stack<std::string> c(1);
    c = a;
    c.push("tres");
    assert(a.size() == 1 && c.size() == 2 && c.capacity() == 3);
    Stack<std::string>& same = a;
    a = same;
    assert(a.top() == "uno" && a.capacity() == 3);
    a.check_invariants();
}

void test_move_does_not_copy() {
    Stack<Counted> a(5);
    a.push(Counted(1));
    a.push(Counted(2));
    std::size_t news = array_news();
    int copies = Counted::copies();
    Stack<Counted> b = std::move(a);
    assert(array_news() == news);
    assert(Counted::copies() == copies);
    assert(b.top().value() == 2 && b.size() == 2);
    assert(a.empty() && a.capacity() == 0);
    a.check_invariants();
    Stack<Counted> c(1);
    news = array_news();
    c = std::move(b);
    assert(array_news() == news);
    assert(Counted::copies() == copies);
    assert(c.top().value() == 2 && b.capacity() == 0);
    b.check_invariants();
    Stack<Counted>& same = c;
    c = std::move(same);
    assert(c.top().value() == 2 && c.size() == 2);
    c.check_invariants();
}

void test_noexcept() {
    Stack<std::string> a(1);
    Stack<std::string> b = std::move(a);
    assert(a.capacity() == 0 && b.capacity() == 1);
    assert(std::is_nothrow_move_constructible_v<Stack<std::string>>);
    assert(std::is_nothrow_move_assignable_v<Stack<std::string>>);
}

struct Pair {
    char symbol;
    int line;
};

void test_plain_struct() {
    Stack<Pair> s(2);
    s.push(Pair{'(', 4});
    assert(s.top().symbol == '(' && s.top().line == 4);
    s.pop();
    assert(s.empty());
}

}  // namespace

int main(int argc, char* argv[]) {
    std::string name = argc > 1 ? argv[1] : "";
    if (name == "int_lifo") test_int_lifo();
    else if (name == "string_lifo") test_string_lifo();
    else if (name == "zero_capacity") test_zero_capacity();
    else if (name == "random_int") test_random_int();
    else if (name == "random_string") test_random_string();
    else if (name == "copies_are_independent") test_copies_are_independent();
    else if (name == "move_does_not_copy") test_move_does_not_copy();
    else if (name == "noexcept") test_noexcept();
    else if (name == "plain_struct") test_plain_struct();
    else {
        std::cerr << "prueba desconocida: " << name << "\n";
        return 2;
    }
    return 0;
}
