// Pruebas de Buffer<T>, con int, std::string y un tipo cuya copia puede fallar.
#include <cassert>
#include <stdexcept>
#include <string>
#include <utility>

#include "buffer.h"
#include "check.h"

// Lanza una excepción en la copia número fail_at. El contador va en la clase, sin variables globales.
class Fragile {
public:
    Fragile() = default;
    Fragile& operator=(const Fragile& other) {
        value_ = other.value_;
        if (++assignments() == fail_at()) {
            throw std::runtime_error("copia fallida");
        }
        return *this;
    }
    static int& assignments() {
        static int count = 0;
        return count;
    }
    static int& fail_at() {
        static int position = 0;
        return position;
    }
    int value_ = 0;
};

int main() {
    run_test("int", [] {
        Buffer<int> a(3);
        assert(a.at(2) == 0);
        a.at(2) = 7;
        Buffer<int> b(a);
        b.at(2) = 8;
        assert(a.at(2) == 7 && b.at(2) == 8);
    });
    run_test("string", [] {
        Buffer<std::string> a(2);
        a.at(0) = "uno";
        Buffer<std::string> b(1);
        b = a;
        a.at(0) = "dos";
        assert(b.at(0) == "uno" && b.size() == 2);
        Buffer<std::string> c(std::move(a));
        assert(c.at(0) == "dos" && a.size() == 0);
        a.check_invariants();
        Buffer<std::string>& same = c;
        c = same;
        c = std::move(same);
        assert(c.at(0) == "dos");
    });
    run_test("si la copia falla, el constructor no deja fuga", [] {
        Buffer<Fragile> source(4);
        Fragile::assignments() = 0;
        Fragile::fail_at() = 2;
        bool thrown = false;
        try {
            Buffer<Fragile> copy(source);
        } catch (const std::runtime_error&) {
            thrown = true;
        }
        assert(thrown);
    });
    run_test("si la copia falla, el destino de la asignación no cambia", [] {
        Buffer<Fragile> source(4);
        Buffer<Fragile> target(2);
        target.at(0).value_ = 123;
        Fragile::assignments() = 0;
        Fragile::fail_at() = 2;
        bool thrown = false;
        try {
            target = source;
        } catch (const std::runtime_error&) {
            thrown = true;
        }
        assert(thrown);
        assert(target.size() == 2 && target.at(0).value_ == 123);
        Fragile::fail_at() = 0;
    });
    return 0;
}
