#include <iostream>
#include <stdexcept>
#include <string>

template <typename T>
class CircularQueue {
private:
    T* data;
    std::size_t capacity;
    std::size_t frontIndex;
    std::size_t rearIndex;
    std::size_t count;

public:
    explicit CircularQueue(std::size_t capacity)
        : data(nullptr),
          capacity(capacity),
          frontIndex(0),
          rearIndex(0),
          count(0) {

        if (capacity == 0) {
            throw std::invalid_argument("Capacity must be greater than 0");
        }

        data = new T[capacity];
    }

    ~CircularQueue() {
        delete[] data;
    }

    CircularQueue(const CircularQueue&) = delete;
    CircularQueue& operator=(const CircularQueue&) = delete;

    bool isEmpty() const noexcept {
        return count == 0;
    }

    bool isFull() const noexcept {
        return count == capacity;
    }

    std::size_t size() const noexcept {
        return count;
    }

    std::size_t maxSize() const noexcept {
        return capacity;
    }

    void enqueue(const T& value) {
        if (isFull()) {
            throw std::overflow_error("Queue is full");
        }

        data[rearIndex] = value;
        rearIndex = (rearIndex + 1) % capacity;
        ++count;
    }

    void dequeue() {
        if (isEmpty()) {
            throw std::underflow_error("Queue is empty");
        }

        frontIndex = (frontIndex + 1) % capacity;
        --count;
    }

    T& front() {
        if (isEmpty()) {
            throw std::underflow_error("Queue is empty");
        }

        return data[frontIndex];
    }

    const T& front() const {
        if (isEmpty()) {
            throw std::underflow_error("Queue is empty");
        }

        return data[frontIndex];
    }

    T& back() {
        if (isEmpty()) {
            throw std::underflow_error("Queue is empty");
        }

        std::size_t index =
            (rearIndex + capacity - 1) % capacity;

        return data[index];
    }

    const T& back() const {
        if (isEmpty()) {
            throw std::underflow_error("Queue is empty");
        }

        std::size_t index =
            (rearIndex + capacity - 1) % capacity;

        return data[index];
    }

    void clear() noexcept {
        frontIndex = 0;
        rearIndex = 0;
        count = 0;
    }

    void print() const {
        if (isEmpty()) {
            std::cout << "Queue: empty\n";
            return;
        }

        std::cout << "Queue: ";

        for (std::size_t i = 0; i < count; ++i) {
            std::size_t index =
                (frontIndex + i) % capacity;

            std::cout << data[index];

            if (i + 1 < count) {
                std::cout << " ";
            }
        }

        std::cout << '\n';
    }
};

int main() {
    try {
        CircularQueue<int> queue(5);

        // Добавление элементов
        queue.enqueue(10);
        queue.enqueue(20);
        queue.enqueue(30);

        queue.print();

        // Первый элемент
        std::cout << "Front: " << queue.front() << '\n';

        // Последний элемент
        std::cout << "Back: " << queue.back() << '\n';

        // Удаление элементов
        queue.dequeue();
        queue.dequeue();

        queue.print();

        // Проверка циклического использования массива
        queue.enqueue(40);
        queue.enqueue(50);
        queue.enqueue(60);

        queue.print();

        // Размер и состояние
        std::cout << "Size: " << queue.size() << '\n';
        std::cout << "Capacity: " << queue.maxSize() << '\n';
        std::cout << "Empty: "
                  << (queue.isEmpty() ? "yes" : "no") << '\n';
        std::cout << "Full: "
                  << (queue.isFull() ? "yes" : "no") << '\n';

        // Проверка исключения при переполнении
        queue.enqueue(70);

        // Очистка очереди
        queue.clear();

        std::cout << "After clear:\n";
        queue.print();

    } catch (const std::exception& error) {
        std::cerr << "Error: " << error.what() << '\n';
        return 1;
    }

    return 0;
}