// IntBuffer convertido en template: arreglo de tamaño fijo, de cualquier tipo T.
#ifndef BUFFER_H
#define BUFFER_H

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

// Exige que T tenga constructor sin argumentos, porque new T[size]() construye todos los
// elementos al reservar, y que se pueda asignar, porque la copia copia elemento por elemento.
//
// Invariante: data_ == nullptr si y solo si size_ == 0.
template <typename T>
class Buffer {
public:
    explicit Buffer(std::size_t size) : data_(size == 0 ? nullptr : new T[size]()), size_(size) {}

    ~Buffer() {
        delete[] data_;
    }

    Buffer(const Buffer& other) : Buffer(other.size_) {
        for (std::size_t i = 0; i < size_; i++) {
            data_[i] = other.data_[i];
        }
    }

    Buffer& operator=(const Buffer& other) {
        Buffer copy(other);
        swap(copy);
        return *this;
    }

    Buffer(Buffer&& other) noexcept : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }

    Buffer& operator=(Buffer&& other) noexcept {
        Buffer taken(std::move(other));
        swap(taken);
        return *this;
    }

    std::size_t size() const {
        return size_;
    }

    T& at(std::size_t index) {
        assert(index < size_);
        return data_[index];
    }

    const T& at(std::size_t index) const {
        assert(index < size_);
        return data_[index];
    }

    void swap(Buffer& other) noexcept {
        std::swap(data_, other.data_);
        std::swap(size_, other.size_);
    }

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

private:
    T* data_;
    std::size_t size_;
};

#endif
