#include <scoped_allocator>
#include <iostream>
#include <memory>
#include <cstdint>
#include <benchmark/benchmark.h>
#include <list>
#include "ArenaAllocator.hpp"


template<typename T>
struct Node
{
	using node_ptr = std::shared_ptr<Node<T>>;
	using weak_node_ptr = std::weak_ptr<Node<T>>;
	node_ptr next;
	weak_node_ptr prev;
	T data;

	Node(node_ptr&& next, weak_node_ptr&& prev, T&& data)
		: next(next), prev(prev), data(data) {};
};

template<typename T>
class List
{
	public:
		using value_type = T;
		using node_ptr = typename Node<value_type>::node_ptr;
		using weak_node_ptr = typename Node<value_type>::weak_node_ptr;
	private:
		node_ptr begin, currentNode, end;
		std::size_t size;

	public:
		bool next()
		{
			if (!this->currentNode || !this->currentNode->next)
				return false;
			this->currentNode = this->currentNode->next;
			return true;
		}

		bool prev()
		{
			if (!this->currentNode || !(this->currentNode->prev.lock()))
				return false;
			this->currentNode = this->currentNode->prev.lock();
			return true;
		}

		value_type* operator->() const
		{
			return &currentNode->data;
		}

		value_type& operator*() const
		{
			return currentNode->data;
		}


		void push_back(value_type&& to_insert)
		{
			if (!this->currentNode)
			{
				this->currentNode = std::make_shared<Node<T>>(nullptr, weak_node_ptr(), std::forward<value_type>(to_insert));
				this->end = currentNode;
				this->begin = currentNode;
				return;
			}

			end->next = std::make_shared<Node<value_type>>(nullptr, std::forward<node_ptr>(end), std::forward<value_type>(to_insert));
			end = end->next;
		}

		void push_front(value_type&& to_insert)
		{
			if (!this->currentNode)
			{
				this->currentNode = std::make_shared<Node<T>>(nullptr, weak_node_ptr(), std::forward<value_type>(to_insert));
				this->end = currentNode;
				this->begin = currentNode;
				return;
			}

			auto new_begin = std::make_shared<Node<T>>(std::forward<node_ptr>(begin), weak_node_ptr(), std::forward<value_type>(to_insert));
			begin->prev = new_begin;
			begin = begin->prev.lock();
		}
};

static void bench_first_insert(benchmark::State& state)
{
	while (state.KeepRunning()) {
		List<int> l;
		l.push_back(3);
		benchmark::ClobberMemory();
	}
}

static void bench_subsequent_inserts(benchmark::State& state)
{
	while (state.KeepRunning()) {
		List<int> l;
		l.push_back(3);
		for(auto&& i = state.range(0); i >= 0; i--)
			l.push_back(42);
		benchmark::ClobberMemory();
	}
}

static void bench_list_first_insert(benchmark::State& state)
{
	while (state.KeepRunning()) {
		std::list<int> l;
		l.push_back(3);
		benchmark::ClobberMemory();
	}
}

static void bench_list_subsequent_inserts(benchmark::State& state)
{
	while (state.KeepRunning()) {
		std::list<int> l;
		l.push_back(3);
		for(auto&& i = state.range(0); i >= 0; i--)
			l.push_back(42);
		benchmark::ClobberMemory();
	}
}

BENCHMARK(bench_first_insert);
BENCHMARK(bench_list_first_insert);
BENCHMARK(bench_subsequent_inserts)->Range(2, 1024)->Repetitions(4)->ReportAggregatesOnly(true);
BENCHMARK(bench_list_subsequent_inserts)->Range(2, 1024)->Repetitions(4)->ReportAggregatesOnly(true);
BENCHMARK_MAIN();
