// Lista simplemente enlazada, con puntero al primero y al último.
#ifndef SLIST_H
#define SLIST_H

#include <cassert>
#include <cstddef>

// Invariantes:
// - head_ es nulo si y solo si tail_ es nulo si y solo si size_ == 0.
// - Siguiendo next desde head_ se visitan size_ nodos, y el último es tail_.
// - tail_->next es nulo.
template <typename T>
class SinglyList {
public:
    SinglyList() = default;

    // La lista es owner de sus nodos: este ciclo los libera todos.
    ~SinglyList() {
        while (head_ != nullptr) {
            pop_front();
        }
    }

    // Copiar y mover quedan prohibidos para no distraernos. La regla de cinco va en el laboratorio.
    SinglyList(const SinglyList&) = delete;
    SinglyList& operator=(const SinglyList&) = delete;

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

    T& front() {
        assert(!empty());
        return head_->value;
    }

    void push_front(const T& value) {
        head_ = new Node{value, head_};
        if (tail_ == nullptr) {
            tail_ = head_;
        }
        size_++;
    }

    void pop_front() {
        assert(!empty());
        Node* old_head = head_;
        head_ = old_head->next;
        delete old_head;
        if (head_ == nullptr) {
            tail_ = nullptr;
        }
        size_--;
    }

    void push_back(const T& value) {
        Node* node = new Node{value, nullptr};
        if (tail_ == nullptr) {
            head_ = node;
        } else {
            tail_->next = node;
        }
        tail_ = node;
        size_++;
    }

    // Llegar al elemento i cuesta i saltos.
    T& at(std::size_t i) {
        assert(i < size_);
        Node* node = head_;
        for (std::size_t step = 0; step < i; step++) {
            node = node->next;
        }
        return node->value;
    }

    void check_invariants() const {
        assert((head_ == nullptr) == (size_ == 0));
        assert((tail_ == nullptr) == (size_ == 0));
        std::size_t count = 0;
        const Node* last = nullptr;
        for (const Node* node = head_; node != nullptr; node = node->next) {
            last = node;
            count++;
            assert(count <= size_);
        }
        assert(count == size_);
        assert(last == tail_);
    }

private:
    struct Node {
        T value;
        Node* next;
    };

    Node* head_ = nullptr;
    Node* tail_ = nullptr;
    std::size_t size_ = 0;
};

#endif
