/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include <catch2/catch_all.hpp>

#include <vector>
#include <stdexcept>

#include "Data/Containers/Array.hpp"

class NonTrivial
{
public:
	static int destructorCount;

	NonTrivial() = default;
	~NonTrivial() { destructorCount++; }
};

int NonTrivial::destructorCount = 0;

using namespace OpenVulkano;

TEST_CASE("Default constructor", "[Array]")
{
	Array<int> array;
	REQUIRE(array.Size() == 0);
	REQUIRE(array.Empty());
}

TEST_CASE("Constructor with size", "[Array]")
{
	Array<int> array(5);
	REQUIRE(array.Size() == 5);

	Array<int> arr(5, 42);
	REQUIRE(arr.Size() == 5);
	for (size_t i = 0; i < arr.Size(); ++i)
	{
		REQUIRE(arr[i] == 42);
	}
}

TEST_CASE("Constructor with initializer list", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	REQUIRE(array.Size() == 3);
	REQUIRE(array[0] == 1);
	REQUIRE(array[1] == 2);
	REQUIRE(array[2] == 3);
}

TEST_CASE("Copy constructor", "[Array]")
{
	Array<int> array1 { 1, 2, 3 };
	Array<int> array2(array1);
	REQUIRE(array2.Size() == 3);
	REQUIRE(array2 == array1);
}

TEST_CASE("Move constructor", "[Array]")
{
	Array<int> array1 { 1, 2, 3 };
	Array<int> array2(std::move(array1));
	REQUIRE(array2.Size() == 3);
	REQUIRE(array1.Size() == 0);
	REQUIRE(array1.Empty());
}

TEST_CASE("Constructor from std::vector", "[Array]")
{
	std::vector<int> vec { 4, 5, 6 };
	Array<int> array(vec);
	REQUIRE(array.Size() == 3);
	REQUIRE(array[0] == 4);
	REQUIRE(array[1] == 5);
	REQUIRE(array[2] == 6);
}

TEST_CASE("Constructor with default value", "[Array]")
{
	Array<int> array(3, 7);
	REQUIRE(array.Size() == 3);
	for (size_t i = 0; i < 3; ++i)
	{
		REQUIRE(array[i] == 7);
	}
}

TEST_CASE("Resize method", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	array = { 4, 5 };
	REQUIRE(array.Size() == 2);
	REQUIRE(array[0] == 4);
	REQUIRE(array[1] == 5);
}

TEST_CASE("Destructor for non-trivially destructible types", "[Array]")
{
	{
		NonTrivial::destructorCount = 0;
		Array<NonTrivial> array(3);
	}
	REQUIRE(NonTrivial::destructorCount == 3);
}

TEST_CASE("At() method with bounds checking", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	REQUIRE(array.At(1) == 2);
	REQUIRE_THROWS_AS(array.At(5), std::out_of_range);
}

TEST_CASE("Assignment operator", "[Array]")
{
	Array<int> array1 { 1, 2, 3 };
	Array<int> array2;
	array2 = array1;
	REQUIRE(array2 == array1);
}

TEST_CASE("Move assignment operator", "[Array]")
{
	Array<int> array1 { 1, 2, 3 };
	Array<int> array2;
	array2 = std::move(array1);
	REQUIRE(array2.Size() == 3);
	REQUIRE(array1.Size() == 0);
}

TEST_CASE("Comparison operators", "[Array]")
{
	Array<int> array1 { 1, 2, 3 };
	Array<int> array2 { 1, 2, 3 };
	Array<int> array3 { 4, 5, 6 };

	REQUIRE(array1 == array2);
	REQUIRE(array1 != array3);
	REQUIRE(array1 < array3);
	REQUIRE(array3 > array1);
	REQUIRE(array1 <= array2);
	REQUIRE(array1 >= array2);
}

TEST_CASE("Fill method", "[Array]")
{
	Array<int> array(5);
	array.Fill(42);
	for (size_t i = 0; i < 5; ++i)
	{
		REQUIRE(array[i] == 42);
	}
}

TEST_CASE("Front and Back methods", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	REQUIRE(array.Front() == 1);
	REQUIRE(array.Back() == 3);
}

TEST_CASE("Iterators", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	int sum = 0;
	for (auto it = array.Begin(); it != array.End(); ++it)
	{
		sum += *it;
	}
	REQUIRE(sum == 6);

	sum = 0;
	for (auto it = array.ReverseBegin(); it != array.ReverseEnd(); ++it)
	{
		sum += *it;
	}
	REQUIRE(sum == 6);

	Array<int> arr = { 1, 2, 3, 4, 5 };
	{
		int sum = 0;
		for (auto it = arr.Begin(); it != arr.End(); ++it)
		{
			sum += *it;
		}
		REQUIRE(sum == 15);
	}

	{
		std::vector<int> reversedElements;
		for (auto it = arr.ReverseBegin(); it != arr.ReverseEnd(); ++it)
		{
			reversedElements.push_back(*it);
		}
		REQUIRE(reversedElements == std::vector<int>({ 5, 4, 3, 2, 1 }));
	}

	{
		const Array<int> constArr = { 1, 2, 3, 4, 5 };
		int sum = 0;
		for (auto it = constArr.Begin(); it != constArr.End(); ++it)
		{
			sum += *it;
		}
		REQUIRE(sum == 15);
	}

	{
		const Array<int> constArr = { 1, 2, 3, 4, 5 };
		std::vector<int> reversedElements;
		for (auto it = constArr.ReverseBegin(); it != constArr.ReverseEnd(); ++it)
		{
			reversedElements.push_back(*it);
		}
		REQUIRE(reversedElements == std::vector<int>({ 5, 4, 3, 2, 1 }));
	}
}

TEST_CASE("Data method", "[Array]")
{
	Array<int> array { 1, 2, 3 };
	int* data = array.Data();
	REQUIRE(data[0] == 1);
	REQUIRE(data[1] == 2);
	REQUIRE(data[2] == 3);
}


TEST_CASE("Swap method", "[Array]")
{
	Array<int> arr1 = { 1, 2, 3 };
	Array<int> arr2 = { 4, 5, 6 };
	arr1.Swap(arr2);
	REQUIRE(arr1[0] == 4);
	REQUIRE(arr2[0] == 1);
}