#include <iostream>
#include <memory>
#include <utility>

class Node {
public:
    Node(const int value, std::unique_ptr<Node> next)
        : next_(std::move(next)), value_(value) {}

    explicit Node(const int v)
        : Node(v, nullptr) {}

    const Node* next() const { return next_.get(); }
    Node* next() { return next_.get(); }
    int value() const { return value_; }
    void setNext(std::unique_ptr<Node> next) { next_ = std::move(next); }
    std::unique_ptr<Node> getNext() { return std::move(next_); }

private:
    std::unique_ptr<Node> next_;
    int value_;
};

// ht
// 1
// h    t
// 2 -> 1
// h         t
// 3 -> 2 -> 1
// h              t
// 4 -> 3 -> 2 -> 1
// h                   t
// 4 -> 3 -> 2 -> 1 -> 5
//
// 3 -> 2 -> 1 -> 5 
// 3 -> 1 -> 5

class List {
public:
    void pushFront(int value) {
        if (!head_) {
            pushHead(value);
            return;
        }

        head_ = std::make_unique<Node>(value, std::move(head_));
    }

    void pushBack(int value) {
        if (!tail_) {
            pushHead(value);
            return;
        }

        tail_->setNext(std::make_unique<Node>(value));
        tail_ = tail_->next();
    }

    bool erase(int value) {
        // Remove head
        if (head_ && head_->value() == value) {
            head_ = head_->getNext();
            if (!head_) {
                tail_ = nullptr;
            }
            return true;
        }

        if (auto* node = findPrevNode(value)) {
            // Remove tail
            if (node->next() == tail_) {
                tail_ = node;
            }
            node->setNext(std::move(node->next()->getNext()));
            return true;
        } 

        return false;
    }

    const Node* findByValue(const int value) const {
        const auto* current = head_.get();

        while (current) {
            if (current->value() == value) {
                return current;
            }
            current = current->next();
        }

        return nullptr;
    }

    void print() const {
        const auto* current = head_.get();

        while (current) {
            std::cout << current->value() << ' ';
            current = current->next();
        }
        std::cout << '\n';
    }

private:
    // ht
    // 1
    // h    t
    // 2 -> 1
    // h         t
    // 2 -> 1 -> 3
    // 2 -> 3
    void pushHead(int value) {
        head_ = std::make_unique<Node>(value);
        tail_ = head_.get();
    }

    Node* findPrevNode(const int value) const {
        auto* current = head_.get();
        Node* prev = nullptr;

        while (current) {
            if (current->value() == value) {
                return prev;
            }
            prev = current;
            current = current->next();
        }

        return nullptr;
    }

    std::unique_ptr<Node> head_;
    Node* tail_{nullptr};
};

int main() {
    List list;

    list.pushFront(4);
    list.pushFront(2);
    list.pushFront(7);
    list.pushFront(9);
    list.print();

    if (const auto* const node = list.findByValue(1)) {
        std::cout << node->value() << '\n';
    } else {
        std::cout << "can't find node with value 1\n";
    }

    if (const auto* const node = list.findByValue(7)) {
        std::cout << node->value() << '\n';
    } else {
        std::cout << "can't find node with value 7\n";
    }

    list.pushBack(10);
    list.print();
    list.pushBack(12);
    list.pushFront(3);
    list.pushFront(5);
    list.pushBack(13);
    list.print();

    list.erase(2);
    list.print();

    list.erase(5);
    list.print();

    list.erase(13);
    list.print();

    list.erase(12);
    list.print();

    list.pushBack(40);
    list.print();

    return 0;
}
