// Vector<T>: arreglo dinámico que duplica su capacidad al llenarse.
//
// Invariantes:
//   - size_ <= capacity_
//   - data_ es nullptr si y solo si capacity_ == 0
//   - data_ apunta a un bloque de capacity_ objetos T vivos, pedido con new T[capacity_];
//     los primeros size_ son los elementos del Vector
//
// Límites de usar new T[capacity_]:
//   - T necesita constructor sin argumentos, y asignación
//   - hay capacity_ objetos vivos, no size_: reserve los construye todos
//   - pop_back no destruye el elemento: sigue vivo hasta que se reasigne o llegue delete[]
//
// Cada función marcada con TODO es de ustedes: borren el assert(false) y escríbanla.
// Las firmas y check_invariants no se cambian. Pueden agregar funciones privadas.
#ifndef VECTOR_H
#define VECTOR_H

#include <cassert>
#include <cstddef>
#include <utility>

template <typename T>
class Vector {
public:
    Vector() = default;

    // TODO: liberar el bloque.
    ~Vector() {}

    // TODO: copia profunda. La copia queda con capacity igual a size, y sin bloque si está vacía.
    Vector(const Vector& /*other*/) { assert(false && "falta el constructor de copia"); }

    // TODO: asignación de copia. Debe resistir a = a.
    Vector& operator=(const Vector& /*other*/) {
        assert(false && "falta la asignación de copia");
        return *this;
    }

    // TODO: tomar el bloque de other, sin pedir memoria. other queda vacío y se puede volver a usar.
    Vector(Vector&& /*other*/) noexcept { assert(false && "falta el constructor de movimiento"); }

    // TODO: asignación de movimiento. No debe perder el bloque que *this ya tenía, y debe resistir
    // a = std::move(a).
    Vector& operator=(Vector&& /*other*/) noexcept {
        assert(false && "falta la asignación de movimiento");
        return *this;
    }

    // TODO: insertar una copia. Si no hay espacio, duplicar la capacidad (de 0 pasa a 1).
    // Ojo: value puede estar dentro de este mismo bloque, como en v.push_back(v[0]).
    void push_back(const T& /*value*/) { assert(false && "falta push_back(const T&)"); }

    // TODO: insertar moviendo el argumento.
    void push_back(T&& /*value*/) { assert(false && "falta push_back(T&&)"); }

    // TODO: quitar el último. Exigir con assert que no esté vacío. La capacidad no cambia.
    void pop_back() { assert(false && "falta pop_back"); }

    // TODO: las dos versiones. Exigir con assert que i sea válido.
    T& operator[](std::size_t /*i*/) {
        assert(false && "falta operator[]");
        return data_[0];
    }
    const T& operator[](std::size_t /*i*/) const {
        assert(false && "falta operator[] const");
        return data_[0];
    }

    // TODO
    std::size_t size() const {
        assert(false && "falta size");
        return 0;
    }

    // TODO
    std::size_t capacity() const {
        assert(false && "falta capacity");
        return 0;
    }

    // TODO: dejar la capacidad exactamente en new_capacity, si es mayor que la actual; si no,
    // no hacer nada. Trasladar con std::move_if_noexcept. Si un traslado lanza una excepción,
    // el Vector debe quedar como estaba y sin fugas: usen el molde de copy_block.cpp, que está en los apuntes.
    void reserve(std::size_t /*new_capacity*/) { assert(false && "falta reserve"); }

    void check_invariants() const {
        assert(size_ <= capacity_);
        assert((data_ == nullptr) == (capacity_ == 0));
    }

private:
    T* data_ = nullptr;
    std::size_t size_ = 0;
    std::size_t capacity_ = 0;
};

#endif
