#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")
	};

	void check_impl(container & container, int count0, int count1, int count2, int count12)
	{
		int call_count = 0;
		container.apply<>([&]{ ++call_count; });
		expect_equal(call_count, count0 + count1 + count2 + count12);

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

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

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

}

test_case(ecs_attach_empty)
{
	container container;

	auto h = container.create();

	expect(container.get(h).get_if<component_1>() == nullptr);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 1, 0, 0, 0);
}

test_case(ecs_attach_one)
{
	container container;

	auto h = container.create();

	container.attach(h, component_1{10});

	expect_equal(container.get(h).get<component_1>().value, 10);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 0, 1, 0, 0);
}

test_case(ecs_attach_twice)
{
	container container;

	auto h = container.create();

	container.attach(h, component_1{10});
	container.attach(h, component_1{11});

	expect_equal(container.get(h).get<component_1>().value, 11);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 0, 1, 0, 0);
}

test_case(ecs_attach_two)
{
	container container;

	auto h = container.create();

	container.attach(h, component_1{10});
	container.attach(h, component_2{20});

	expect_equal(container.get(h).get<component_1>().value, 10);
	expect_equal(container.get(h).get<component_2>().value, 20);

	check_impl(container, 0, 0, 0, 1);
}

test_case(ecs_detach_empty)
{
	container container;

	auto h = container.create();

	container.detach<component_1>(h);

	expect(container.get(h).get_if<component_1>() == nullptr);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 1, 0, 0, 0);
}

test_case(ecs_detach_nonexistent)
{
	container container;

	auto h = container.create(component_1{10});

	container.detach<component_2>(h);

	expect_equal(container.get(h).get<component_1>().value, 10);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 0, 1, 0, 0);
}

test_case(ecs_detach_one)
{
	container container;

	auto h = container.create(component_1{10}, component_2{20});

	container.detach<component_2>(h);

	expect_equal(container.get(h).get<component_1>().value, 10);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 0, 1, 0, 0);
}

test_case(ecs_detach_two)
{
	container container;

	auto h = container.create(component_1{10}, component_2{20});

	container.detach<component_1>(h);
	container.detach<component_2>(h);

	expect(container.get(h).get_if<component_1>() == nullptr);
	expect(container.get(h).get_if<component_2>() == nullptr);

	check_impl(container, 1, 0, 0, 0);
}

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

	int count0 = 0;
	int count1 = 0;
	int count2 = 0;
	int count12 = 0;

	for (int i = 0; i < 1024 * 1024; ++i)
	{
		auto h = container.create();

		bool has1 = random::uniform<bool>(rng);
		bool has2 = random::uniform<bool>(rng);

		if (has1 && has2)
		{
			container.attach(h, component_1{i}, component_2{2 * i});
			++count12;
		}
		else if (has1 && !has2)
		{
			container.attach(h, component_1{i});
			++count1;
		}
		else if (!has1 && has2)
		{
			container.attach(h, component_2{2 * i});
			++count2;
		}
		else
		{
			++count0;
		}
	}

	check_impl(container, count0, count1, count2, count12);
}

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

	int count0 = 0;
	int count1 = 0;
	int count2 = 0;
	int count12 = 0;

	for (int i = 0; i < 1024 * 1024; ++i)
	{
		auto h = container.create(component_1{i}, component_2{2 * i});

		bool has1 = random::uniform<bool>(rng);
		bool has2 = random::uniform<bool>(rng);

		if (has1 && has2)
		{
			++count12;
		}
		else if (has1 && !has2)
		{
			container.detach<component_2>(h);
			++count1;
		}
		else if (!has1 && has2)
		{
			container.detach<component_1>(h);
			++count2;
		}
		else
		{
			container.detach<component_1, component_2>(h);
			++count0;
		}
	}

	check_impl(container, count0, count1, count2, count12);
}
