#include <cassert>
#include <iostream>
#include <memory>

using namespace std;

class List {
public:
	List() = default;
	// ~List() = default; also works.
	~List() {
		cout << "Enter List destructor\n";
		// Release |front_| explicitly to see how the Node is released.
		front_ = nullptr;
		cout << "List destructor done\n";
	}

	int front() const {
		assert(front_);

		return front_->element;
	}
	
	bool empty() const {
		return size_ == 0;
	}
	
	size_t size() const	{
		return size_;
	}
	
	void push_front(int element) {
		unique_ptr<Node> new_front = make_unique<Node>(element, move(front_));
		
		front_ = move(new_front);
		size_++;
	}
	
	void pop_front() {
		front_ = move(front_->next_node);
		size_--;
	}
	
	void insert_at(int index, int element) {
		assert(index <= size_);
		
		// |front_| will be updated to the new node. Handle it separately.
		if (index == 0) {
			push_front(element);
			return;
		}
		
		// Here we don't need the ownership of the node when finding the insert
		// point. Just use the raw pointer.
		Node* front_node = front_.get();
		for (size_t i = 0; i < index - 1; i++) {
			front_node = front_node->next_node.get();
		}
		
		unique_ptr<Node> new_node = make_unique<Node>(
			element, move(front_node->next_node));
		front_node->next_node = move(new_node);
		size_++;
	}
	
	void print() const {
		// We don't need the ownership of the node when triversing the list.
		// Just use the raw pointer.
		Node* p = front_.get();
		while (p != nullptr) {
			cout << p->element << " -> ";
			p = p->next_node.get();
		}
		cout << "NULL" << endl << "size = " << size_ << endl;
	}

private:
	// The list client doesn't need to know the detail about Node, so put it in
	// private. Node only has public data members. Just use struct. 
	struct Node {
		Node(int e, unique_ptr<Node> n) 
			: element(e), next_node(move(n)) {}
		~Node() {
			cout << "Node " << element << " is released.\n";
		}
	
		int element;
		unique_ptr<Node> next_node;
	};

	unique_ptr<Node> front_;
	size_t size_ = 0;
};

int main() {
	List myList;
	myList.push_front(23);
	myList.push_front(24);
	myList.push_front(25);
	myList.push_front(27);
	myList.push_front(2);
	myList.push_front(32);
	myList.print();
	myList.insert_at(4, 89);
	myList.print();
	myList.pop_front();
	myList.print();
	
	cout << "Exit main(). After that List destructor will be triggered.\n";
	return 0;
}