Source File: ex61_dlist.hpp

#include <functional>

void my_assert(bool test, const std::string& message) {
  if(!test) throw std::runtime_error(message);
}

template<typename T>
struct DListNode {
  T data;
  DListNode<T> *next = nullptr;
  DListNode<T> *prev = nullptr;
};

template<typename T>
class DList {
  DListNode<T> *head = nullptr;
  DListNode<T> *tail = nullptr;

public:
  DListNode<T>* first() {
    return head;
  }

  DListNode<T>* last() {
    return tail;
  }

  void push_back(const T& data) {
    auto* temp = new DListNode<T>{.data = data};

    if(tail == nullptr) {
      // empty list
      head = temp;
      tail = temp;
    } else {
      // has elements
      tail->next = temp;
      temp->prev = tail;
      tail = temp;
    }
  }

  void push_front(const T& data) {
    auto* temp = new DListNode<T>{.data = data};

    if(head == nullptr) {
      // empty list
      head = temp;
      tail = temp;
    } else {
      // has elements
      head->prev = temp;
      temp->next = head;
      head = temp;
    }
  }

  T pop_front() {
    my_assert(head != nullptr, "empty list!");

    auto result = head->data;

    if(head->next == nullptr) {
      // one
      delete head;
      head = nullptr;
      tail = nullptr;
    } else {
      // many, shuffle head down
      head = head->next; // move down one
      delete head->prev; // delete the old head
      head->prev = nullptr; // close it off
    }

    return result;
  }

  T pop_back() {
    // zero, abort
    my_assert(tail != nullptr, "empty list!");

    // has either one or many, aways return result
    auto result = tail->data;

    // different operation for one vs many
    if(tail->prev == nullptr) {
      // one, delete tail and clear head/tail
      delete tail;
      head = nullptr;
      tail = nullptr;
    } else {
      // many, shuffle tail around
      tail = tail->prev; // move back one
      delete tail->next; // delete old tail
      tail->next = nullptr; // close it off
    }

    return result;
  }

  void for_each(std::function<void(const T&)> cb) {
    for(auto* cur = head; cur != nullptr; cur = cur->next) {
      cb(cur->data);
    }
  }

  int count() {
    int res = 0;

    for_each([&](const auto& val) { res++; });

    return res;
  }
};