#pragma once

#include <iterator>
#include <memory>

namespace std {

  template <class T>
  class list {

  public:
    struct node {
      T value;
      node *prev;
      node *next;
    };

    template <class V>
    struct generic_iterator {

      node *the_node;
      node *last_node;

      using iterator_category = std::bidirectional_iterator_tag;
      using reference = V &;
      using pointer = V *;
      using value_type = V;
      using difference_type = size_t;

      bool operator ==(const generic_iterator &other) const {
        return the_node == other.the_node;
      }

      bool operator !=(const generic_iterator &other) const {
        return the_node != other.the_node;
      }

      V &operator *() {
        return the_node->value;
      }

      V *operator ->() {
        return &the_node->value;
      }

      generic_iterator &operator ++() {
        the_node = the_node->next;
        return *this;
      }

      generic_iterator &operator --() {
        if (the_node == 0)
          the_node = last_node;
        else
          the_node = the_node->prev;
        return *this;
      }

      generic_iterator operator -(size_t count) const {
        generic_iterator it = { .the_node = the_node };
        for (size_t i = 0; i < count; ++i)
          --it;
        return it;
      }

    };

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

    using       reverse_iterator = std::reverse_iterator<      iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  private:
    node *first_node;
    node *last_node;
    size_t count;

  public:
    void push_back(const T &value) {
      node *n = new node { .value = value,
        .prev = last_node, .next = 0 };
      if (last_node) last_node->next = n;
      else first_node = n;
      last_node = n;
      ++count;
    }

    void push_back(T &&value) {
      node *n = new node {
        .value = std::move(value),
        .prev = last_node, .next = 0 };
      if (last_node) last_node->next = n;
      else first_node = n;
      last_node = n;
      ++count;
    }

    iterator erase(iterator pos) {
      --count;
      auto *n = pos.the_node;
      auto *r = n->next;
      if (n->prev) n->prev->next = n->next;
      else            first_node = n->next;
      if (n->next) n->next->prev = n->prev;
      else             last_node = n->prev;
      delete n;
      return iterator {
        .the_node = r,
        .last_node = last_node
      };
    }

    iterator begin() noexcept {
      return iterator {
        .the_node = first_node,
        .last_node = last_node
      };
    }

    iterator end() noexcept {
      return iterator {
        .the_node = 0,
        .last_node = last_node
      };
    }

    const_iterator begin() const noexcept {
      return iterator {
        .the_node = first_node,
        .last_node = last_node
      };
    }

    const_iterator end() const noexcept {
      return iterator {
        .the_node = 0,
        .last_node = last_node
      };
    }

    const_iterator cbegin() const noexcept {
      return iterator {
        .the_node = first_node,
        .last_node = last_node
      };
    }

    const_iterator cend() const noexcept {
      return iterator {
        .the_node = 0,
        .last_node = last_node
      };
    }

    reverse_iterator rbegin() noexcept {
      return reverse_iterator(
        iterator {
          .the_node = 0,
          .last_node = last_node
        });
    }

    reverse_iterator rend() noexcept {
      return reverse_iterator(
        iterator {
          .the_node = first_node,
          .last_node = last_node
        });
    }

    const_reverse_iterator rbegin() const noexcept {
      return const_reverse_iterator(
        iterator {
          .the_node = 0,
          .last_node = last_node
        });
    }

    const_reverse_iterator rend() const noexcept {
      return const_reverse_iterator(
        iterator {
          .the_node = first_node,
          .last_node = last_node
        });
    }

    const_reverse_iterator crbegin() const noexcept {
      return const_reverse_iterator(
        iterator {
          .the_node = 0,
          .last_node = last_node
        });
    }

    const_reverse_iterator crend() const noexcept {
      return const_reverse_iterator(
        iterator {
          .the_node = first_node,
          .last_node = last_node
        });
    }

    size_t remove(const T &value) {
      size_t removed = 0;
      auto it = begin();
      while (it != end())
        if (*it == value) {
          it = erase(it);
          ++removed;
        }
        else
          ++it;
      count -= removed;
      return removed;
    }

    list() : first_node(0), last_node(0), count(0) {}

    list(const list &other) : first_node(0), last_node(0), count(0) {
      for (node *n = other.first_node; n; n = n->next)
        push_back(n->value);
    }

    list(list &&other) : first_node(other.first_node),
      last_node(other.last_node), count(other.count) {
      other.first_node = 0;
      other.last_node = 0;
      other.count = 0;
    }

    void clear() {

      if (count == 0) return;

      for (node *n = first_node->next; n; n = n->next)
        delete n->prev;
      delete last_node;

      first_node = 0;
      last_node = 0;
      count = 0;

    }

    ~list() {
      clear();
    }

    list &operator =(const list &other) {
      clear();
      for (node *n = other.first_node; n; n = n->next)
        push_back(n->value);
      return *this;
    }

    list &operator =(list &&other) {
      clear();
      first_node = other.first_node;
      last_node = other.last_node;
      count = other.count;
      other.first_node = 0;
      other.last_node = 0;
      other.count = 0;
      return *this;
    }

          T &back()       { return last_node->value; }
    const T &back() const { return last_node->value; }

    size_t size() const noexcept { return count; }

  };

}
