// Deque sobre un buffer circular que crece. Completen cada método marcado con not_implemented.
#ifndef ARRAY_DEQUE_H
#define ARRAY_DEQUE_H

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

#include "not_implemented.h"

// Invariantes:
// - capacity_ == 0 si y solo si data_ es nulo.
// - size_ <= capacity_.
// - Si capacity_ > 0, entonces front_ < capacity_. Si capacity_ == 0, entonces front_ == 0.
// - El elemento i está en data_[(front_ + i) mod capacity_].
//
// T debe tener constructor por defecto, porque el buffer se reserva con new T[]: las casillas
// sin elemento también guardan un T.
template <typename T>
class ArrayDeque {
public:
    // Con V = T se puede escribir el elemento; con V = const T, solo leerlo.
    // Un iterador guarda el índice lógico: 0 es el frente y size_ es el fin.
    template <typename V>
    class Iter {
    public:
        V& operator*() const { not_implemented("Iter::operator*"); }
        Iter& operator++() { not_implemented("Iter::operator++"); }
        bool operator==(const Iter& other) const { return data_ == other.data_ && index_ == other.index_; }
        bool operator!=(const Iter& other) const { return !(*this == other); }

    private:
        friend class ArrayDeque;
        Iter(V* data, std::size_t capacity, std::size_t front, std::size_t index)
            : data_(data), capacity_(capacity), front_(front), index_(index) {}
        V* data_;
        std::size_t capacity_;
        std::size_t front_;
        std::size_t index_;
    };

    using iterator = Iter<T>;
    using const_iterator = Iter<const T>;

    ArrayDeque() { not_implemented("ArrayDeque()"); }
    ~ArrayDeque() { not_implemented("~ArrayDeque"); }

    ArrayDeque(const ArrayDeque&) { not_implemented("constructor de copia"); }
    ArrayDeque& operator=(const ArrayDeque&) { not_implemented("asignación de copia"); }
    // Al mover, el origen queda vacío y se puede seguir usando.
    ArrayDeque(ArrayDeque&&) noexcept { not_implemented("constructor de movimiento"); }
    ArrayDeque& operator=(ArrayDeque&&) noexcept { not_implemented("asignación de movimiento"); }

    std::size_t size() const { return size_; }
    bool empty() const { return size_ == 0; }
    std::size_t capacity() const { return capacity_; }

    void push_front(const T&) { not_implemented("push_front(const T&)"); }
    void push_front(T&&) { not_implemented("push_front(T&&)"); }
    void push_back(const T&) { not_implemented("push_back(const T&)"); }
    void push_back(T&&) { not_implemented("push_back(T&&)"); }

    // Precondición: no está vacío. La casilla que queda libre debe soltar lo que guardaba.
    void pop_front() { not_implemented("pop_front"); }
    void pop_back() { not_implemented("pop_back"); }

    T& front() { not_implemented("front"); }
    const T& front() const { not_implemented("front const"); }
    T& back() { not_implemented("back"); }
    const T& back() const { not_implemented("back const"); }

    // Precondición: i < size().
    T& operator[](std::size_t) { not_implemented("operator[]"); }
    const T& operator[](std::size_t) const { not_implemented("operator[] const"); }

    iterator begin() { not_implemented("begin"); }
    iterator end() { not_implemented("end"); }
    const_iterator begin() const { not_implemented("begin const"); }
    const_iterator end() const { not_implemented("end const"); }

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

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

#endif
