#include <psemek/test/test.hpp>

#include <psemek/ecs/container.hpp>
#include <psemek/ecs/declare_uuid.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>

using namespace psemek;
using namespace psemek::ecs;

namespace
{

	struct component_1
	{
		int value;

		psemek_ecs_declare_uuid("component_1")
	};

	struct component_2
	{
		int value;

		psemek_ecs_declare_uuid("component_2")
	};

}

test_case(ecs_apply_empty)
{
	container container;

	int const count = 2048;
	for (int i = 0; i < count; ++i)
		container.create();

	int call_count = 0;
	container.apply<>([&](ecs::container &, handle const &){ ++call_count; });
	container.apply<>([&](ecs::container &){ ++call_count; });
	container.apply<>([&](handle const &){ ++call_count; });
	container.apply<>([&]{ ++call_count; });
	expect_equal(count * 4, call_count);
}

test_case(ecs_apply_components_1)
{
	container container;
	random::generator rng;

	int const expected_count = 1024 * 1024;
	int expected_sum = 0;

	for (int i = 0; i < expected_count; ++i)
	{
		int value = random::uniform(rng, -1024, 1024);
		expected_sum += value;
		container.create(component_1{value});
	}

	int count = 0;
	int sum = 0;
	container.apply<component_1>([&](component_1 const & component){
		++count;
		sum += component.value;
	});

	expect_equal(count, expected_count);
	expect_equal(sum, expected_sum);
}

test_case(ecs_apply_components_2)
{
	container container;
	random::generator rng;

	int const expected_count = 1024*1024;
	int expected_sum = 0;

	for (int i = 0; i < expected_count; ++i)
	{
		int value = random::uniform(rng, -1024, 1024);
		int type = random::uniform(rng, 0, 2);
		if (type == 0)
			container.create(component_1{value});
		else if (type == 1)
			container.create(component_2{value});
		else if (type == 2)
			container.create(component_1{value}, component_2{value});

		expected_sum += value;
	}

	int count = 0;
	int sum = 0;
	container.apply<component_1>([&](component_1 const & component){
		++count;
		sum += component.value;
	});
	container.apply<component_2>([&](component_2 const & component){
		++count;
		sum += component.value;
	});
	container.apply<component_1, component_2>([&](component_1 const & component1, component_2 const &){
		--count;
		sum -= component1.value;
	});

	expect_equal(count, expected_count);
	expect_equal(sum, expected_sum);
}

test_case(ecs_apply_without)
{
	container container;

	int const count = 1024;

	int call_count = 0;

	for (int i = 0; i < count; ++i)
		container.create();

	call_count = 0;
	container.apply<without<component_2>>([&]{ ++call_count; });
	expect_equal(count, call_count);

	call_count = 0;
	container.apply<component_1, without<component_2>>([&](component_1 const &){ ++call_count; });
	expect_equal(0, call_count);

	for (int i = 0; i < count; ++i)
		container.create(component_1{i});

	call_count = 0;
	container.apply<without<component_2>>([&]{ ++call_count; });
	expect_equal(count * 2, call_count);

	call_count = 0;
	container.apply<component_1, without<component_2>>([&](component_1 const &){ ++call_count; });
	expect_equal(count, call_count);

	for (int i = 0; i < count; ++i)
		container.create(component_2{i});

	call_count = 0;
	container.apply<without<component_2>>([&]{ ++call_count; });
	expect_equal(count * 2, call_count);

	call_count = 0;
	container.apply<component_1, without<component_2>>([&](component_1 const &){ ++call_count; });
	expect_equal(count, call_count);

	for (int i = 0; i < count; ++i)
		container.create(component_1{i}, component_2{i});

	call_count = 0;
	container.apply<without<component_2>>([&]{ ++call_count; });
	expect_equal(count * 2, call_count);

	call_count = 0;
	container.apply<component_1, without<component_2>>([&](component_1 const &){ ++call_count; });
	expect_equal(count, call_count);
}

test_case(ecs_apply_batch_invoke)
{
	container container;

	int const count = 2048;
	for (int i = 0; i < count; ++i)
		container.create(component_1{i});

	int call_count = 0;
	container.batch_apply<component_1>([&](ecs::container &, util::span<handle const>, util::span<component_1> components){ call_count += components.size(); });
	container.batch_apply<component_1>([&](ecs::container &, util::span<component_1> components){ call_count += components.size(); });
	container.batch_apply<component_1>([&](util::span<handle const>, util::span<component_1> components){ call_count += components.size(); });
	container.batch_apply<component_1>([&](util::span<component_1> components){ call_count += components.size(); });
	expect_equal(count * 4, call_count);
}

test_case(ecs_apply_batch_components)
{
	container container;
	random::generator rng;

	int const expected_count = 1024*1024;
	int expected_sum = 0;

	for (int i = 0; i < expected_count; ++i)
	{
		int value = random::uniform(rng, -1024, 1024);
		int type = random::uniform(rng, 0, 2);
		if (type == 0)
			container.create(component_1{value});
		else if (type == 1)
			container.create(component_2{value});
		else if (type == 2)
			container.create(component_1{value}, component_2{value});

		expected_sum += value;
	}

	int count = 0;
	int sum = 0;
	container.batch_apply<component_1>([&](util::span<component_1 const> components){
		count += components.size();
		for (auto & component : components)
			sum += component.value;
	});
	container.batch_apply<component_2>([&](util::span<component_2 const> components){
		count += components.size();
		for (auto & component : components)
			sum += component.value;
	});
	container.batch_apply<component_1, component_2>([&](util::span<component_1 const> components1, util::span<component_2 const>){
		count -= components1.size();
		for (auto & component : components1)
			sum -= component.value;
	});

	expect_equal(count, expected_count);
	expect_equal(sum, expected_sum);
}

test_case(ecs_apply_remove_forward)
{
	container container;

	std::vector<handle> handles;
	int const count = 1024 * 1024;
	for (int i = 0; i < count; ++i)
		handles.push_back(container.create());

	int call_count = 0;
	container.apply([&]{
		++call_count;
		if (call_count == count)
			for (auto h : handles)
				container.destroy(h);
	});

	for (auto h : handles)
		expect(!container.alive(h));
}

test_case(ecs_apply_remove_reversed)
{
	container container;

	std::vector<handle> handles;
	int const count = 1024 * 1024;
	for (int i = 0; i < count; ++i)
		handles.push_back(container.create());
	std::reverse(handles.begin(), handles.end());

	int call_count = 0;
	container.apply([&]{
		++call_count;
		if (call_count == count)
			for (auto h : handles)
				container.destroy(h);
	});

	expect_equal(call_count, call_count);

	for (auto h : handles)
		expect(!container.alive(h));
}

test_case(ecs_apply_remove_random)
{
	container container;
	random::generator rng;

	std::vector<handle> handles;
	int const count = 1024 * 1024;
	for (int i = 0; i < count; ++i)
		handles.push_back(container.create());
	std::shuffle(handles.begin(), handles.end(), rng);

	int call_count = 0;
	container.apply([&]{
		++call_count;
		if (call_count == count)
			for (auto h : handles)
				container.destroy(h);
	});

	expect_equal(call_count, call_count);

	for (auto h : handles)
		expect(!container.alive(h));
}

test_case(ecs_apply_create)
{
	container container;

	int const count = 1024 * 1024;
	for (int i = 0; i < count; ++i)
		container.create();

	int call_count = 0;
	container.apply([&]{
		++call_count;
		container.create();
	});
	expect_equal(call_count, count);

	call_count = 0;
	container.apply([&]{
		++call_count;
	});
	expect_equal(call_count, 2 * count);
}

test_case(ecs_apply_const)
{
	container container;

	container.create(component_1{10}, component_2{20});
	container.apply<component_1, component_2 const>([](component_1 & value1, component_2 const & value2){
		expect_equal(value1.value, 10);
		expect_equal(value2.value, 20);
	});
}

