#include <Nazara/Core/MovablePtr.hpp>
#include <Nazara/Core/StackVector.hpp>
#include <Catch/catch.hpp>
#include <array>
#include <numeric>

// This is a quick way to check that checks are valid
#define USE_STD_VECTOR 0

class DestructionCounter
{
	public:
		DestructionCounter() :
		m_counter(nullptr),
		m_value(0)
		{
		}

		DestructionCounter(std::size_t* counter, int value) :
		m_counter(counter),
		m_value(value)
		{
			if (m_counter)
				(*m_counter)++;
		}

		DestructionCounter(const DestructionCounter& counter) :
		m_counter(counter.m_counter),
		m_value(counter.m_value)
		{
			if (m_counter)
				(*m_counter)++;
		}

		DestructionCounter(DestructionCounter&& counter) :
		m_counter(counter.m_counter),
		m_value(counter.m_value)
		{
			if (m_counter)
				(*m_counter)++;
		}

		~DestructionCounter()
		{
			if (m_counter)
			{
				assert(*m_counter > 0);
				(*m_counter)--;
			}
		}

		operator int() const
		{
			return m_value;
		}

		DestructionCounter& operator=(const DestructionCounter& counter)
		{
			if (m_counter)
			{
				assert(*m_counter > 0);
				(*m_counter)--;
			}

			m_counter = counter.m_counter;
			m_value = counter.m_value;

			if (m_counter)
				(*m_counter)++;

			return *this;
		}

		DestructionCounter& operator=(DestructionCounter&& counter)
		{
			if (this == &counter)
				return *this;

			if (m_counter)
			{
				assert(*m_counter > 0);
				(*m_counter)--;
			}

			m_counter = counter.m_counter;
			m_value = counter.m_value;

			if (m_counter)
				(*m_counter)++;

			return *this;
		}

	private:
		std::size_t* m_counter;
		int m_value;
};

SCENARIO("StackVector", "[CORE][STACKVECTOR]")
{
	GIVEN("A StackVector to contain multiple int")
	{
		std::size_t counter = 0;
		{
			volatile std::size_t capacity = 50;
#if USE_STD_VECTOR
			std::vector<DestructionCounter> vector;
			vector.reserve(capacity);
#else
			Nz::StackVector<DestructionCounter> vector = NazaraStackVector(DestructionCounter, capacity);
#endif

			WHEN("At construction, the vector is empty but has capacity")
			{
				CHECK(vector.capacity() == capacity);
				CHECK(vector.empty());
				CHECK(vector.size() == 0);
#if !USE_STD_VECTOR
				CHECK(vector.max_size() == capacity);
#endif
			}

			WHEN("Resizing it changes its size and create/destroy elements")
			{
				vector.resize(vector.capacity());
				CHECK(vector.size() == vector.capacity());
				CHECK(counter == 0);
				vector.resize(0);
				CHECK(vector.empty());
				CHECK(vector.size() == 0);
				CHECK(counter == 0);
			}

			WHEN("Resizing it allocates elements")
			{
				vector.resize(vector.capacity(), DestructionCounter(&counter, 0));
				CHECK(vector.size() == vector.capacity());
				CHECK(counter == capacity);
				vector.resize(0);
				CHECK(vector.empty());
				CHECK(vector.size() == 0);
				CHECK(counter == 0);
			}

			WHEN("Emplacing five elements, vector size increase accordingly")
			{
				for (std::size_t i = 0; i < 5; ++i)
				{
#if USE_STD_VECTOR
					vector.emplace_back(&counter, int(i));
#else
					CHECK(vector.emplace_back(&counter, int(i)) == int(i));
#endif
				}

				CHECK(!vector.empty());
				CHECK(vector.size() == 5);

				std::array<int, 5> expectedValues = { 0, 1, 2, 3, 4 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("Pushing three elements, vector size increase accordingly")
			{
				for (std::size_t i = 0; i < 3; ++i)
				{
					DestructionCounter val(&counter, int(i));
#if USE_STD_VECTOR
					vector.push_back(val);
#else
					CHECK(vector.push_back(val) == val);
#endif
				}

				CHECK(!vector.empty());
				CHECK(vector.size() == 3);
				{
					std::array<int, 3> expectedValues = { 0, 1, 2 };
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}

				THEN("We resize to five")
				{
					vector.resize(5);

					CHECK(!vector.empty());
					CHECK(vector.size() == 5);

					std::array<int, 5> expectedValues = { 0, 1, 2, 0, 0 };
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));

					AND_THEN("We resize it back to zero")
					{
						vector.resize(0);

						CHECK(vector.empty());
						CHECK(vector.size() == 0);
					}
					AND_THEN("We clear it")
					{
						vector.clear();

						CHECK(vector.empty());
						CHECK(vector.size() == 0);
						CHECK(counter == 0);
					}
				}
			}

			WHEN("We generate its content will iota")
			{
				vector.resize(10);
				for (std::size_t i = 0; i < vector.size(); ++i)
					vector[i] = DestructionCounter(&counter, -5 + int(i));

				{
					std::array<int, 10> expectedValues = { -5, -4, -3, -2, -1, 0, 1, 2, 3, 4 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}

				AND_WHEN("We pop back some elements")
				{
					for (std::size_t i = 0; i < 5; ++i)
						vector.pop_back();

					std::array<int, 5> expectedValues = { -5, -4, -3, -2, -1 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}
				AND_WHEN("We erase elements at the beginning")
				{
					vector.erase(vector.begin());
					vector.erase(vector.begin());

					std::array<int, 8> expectedValues = { -3, -2, -1, 0, 1, 2, 3, 4 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}
				AND_WHEN("We erase elements in the middle")
				{
					vector.erase(vector.begin() + 2);
					vector.erase(vector.begin() + 2);
					vector.erase(vector.begin() + 6);

					std::array<int, 7> expectedValues = { -5, -4, -1, 0, 1, 2, 4 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}
				AND_WHEN("We erase elements at the end")
				{
					vector.erase(vector.end() - 1);
					vector.erase(vector.end() - 1);

					std::array<int, 8> expectedValues = { -5, -4, -3, -2, -1, 0, 1, 2 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}
				AND_WHEN("We erase a range")
				{
					vector.erase(vector.begin() + 2, vector.end() - 3);

					std::array<int, 5> expectedValues = { -5, -4, 2, 3, 4 };
					CHECK(vector.size() == expectedValues.size());
					CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
				}
				AND_WHEN("We erase everything")
				{
					vector.erase(vector.begin(), vector.end());

					CHECK(vector.empty());
				}
			}

			WHEN("We generate its content using emplace")
			{
				for (std::size_t i = 0; i < 5; ++i)
					CHECK(*vector.emplace(vector.end(), &counter, int(i)) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 5);

				std::array<int, 5> expectedValues = { 0, 1, 2, 3, 4 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("We generate its content using emplace, in reverse order")
			{
				for (std::size_t i = 0; i < 5; ++i)
					CHECK(*vector.emplace(vector.begin(), &counter, int(i)) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 5);

				std::array<int, 5> expectedValues = { 4, 3, 2, 1, 0 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("We generate its content using emplace, at the middle")
			{
				for (std::size_t i = 0; i < 10; ++i)
					CHECK(*vector.emplace(vector.begin() + i / 2, &counter, int(i)) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 10);

				std::array<int, 10> expectedValues = { 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("We generate its content using insert")
			{
				for (std::size_t i = 0; i < 5; ++i)
					CHECK(*vector.insert(vector.end(), DestructionCounter(&counter, int(i))) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 5);

				std::array<int, 5> expectedValues = { 0, 1, 2, 3, 4 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("We generate its content using insert, in reverse order")
			{
				for (std::size_t i = 0; i < 5; ++i)
					CHECK(*vector.insert(vector.begin(), DestructionCounter(&counter, int(i))) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 5);

				std::array<int, 5> expectedValues = { 4, 3, 2, 1, 0 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}

			WHEN("We generate its content using insert, at the middle")
			{
				for (std::size_t i = 0; i < 10; ++i)
					CHECK(*vector.insert(vector.begin() + i / 2, DestructionCounter(&counter, int(i))) == int(i));

				CHECK(!vector.empty());
				CHECK(vector.size() == 10);

				std::array<int, 10> expectedValues = { 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 };
				CHECK(std::equal(vector.begin(), vector.end(), expectedValues.begin(), expectedValues.end()));
			}
		}

		CHECK(counter == 0);
	}
}
