module tests.tests;
/*
import std.experimental.allocator;
import std.experimental.allocator.mallocator;*/

import bubel.ecs.entity;
import bubel.ecs.events;
import bubel.ecs.manager;
import bubel.ecs.system;
import bubel.ecs.attributes;
import bubel.ecs.core;

version (WebAssembly)
{
	extern (C) int printf(scope const char* format, ...) @nogc nothrow @system;

	alias int time_t;
	alias int clockid_t;
	enum CLOCK_REALTIME = 0;

	struct timespec
	{
		time_t tv_sec;
		int tv_nsec;
	}

	extern (C) int clock_gettime(clockid_t, timespec*) @nogc nothrow @system;

	struct Time
	{

		static long getUSecTime()
		{
			time_t time;
			timespec spec;

			clock_gettime(CLOCK_REALTIME, &spec);

			//time = spec.tv_sec;
			return spec.tv_sec * 1000_000 + spec.tv_nsec / 1000; //time / 1000_000;
		}
	}

	extern (C) void _start()
	{
	}
}
else version (Windows)
{
	import core.stdc.stdio : printf;
	import core.sys.windows.windows;

	struct Time
	{
		static long getUSecTime()
		{
			LARGE_INTEGER time, freq;
			QueryPerformanceFrequency(&freq);
			QueryPerformanceCounter(&time);
			return time.QuadPart / (freq.QuadPart / 1000_000);
		}
	}
}
else version (Posix)
{
	import core.stdc.stdio : printf;
	import core.sys.posix.time;

	struct Time
	{
		static long getUSecTime()
		{
			time_t time;
			timespec spec;

			clock_gettime(CLOCK_REALTIME, &spec);

			//time = spec.tv_sec;
			return spec.tv_sec * 1000_000 + spec.tv_nsec / 1000; //time / 1000_000;
		}
	}
}

struct TestEvent
{
	mixin ECS.Event; //__gshared ushort event_id;
	int a;
}

struct TestEvent2
{
	mixin ECS.Event; //__gshared ushort event_id;
	float a;
}

static struct TestComp
{
	mixin ECS.Component; //__gshared ushort component_id;
	int a = 1;
	ulong b = 2;

	static void serializeComponent(SerializeVector output)
	{

	}

	static void deserializeComponent(ubyte[] data)
	{

	}
}

static struct TestComp2
{
	mixin ECS.Component; //__gshared ushort component_id;
	int b = 3;
	int a = 4;

	static void serializeComponent(ref TestComp comp, SerializeVector output)
	{

	}

	static void deserializeComponent(ref TestComp comp, ubyte[] data)
	{

	}
}

static struct TestComp3
{
	mixin ECS.Component; //__gshared ushort component_id;
	uint gg = 5; //good game
	uint bg = 6; //bad game

	void serializeComponent(SerializeVector output)
	{

	}

	void deserializeComponent(ubyte[] data)
	{

	}
}

static struct TestComp4
{
	mixin ECS.Component; //__gshared ushort component_id;
	uint gg = 7; //good game
	uint bg = 8; //bad game
	ulong a = 9;
	ulong b = 10;
	ulong c = 11;
	ulong g = 12;

	static void serializeComponent(ref TestComp comp, SerializeVector output)
	{

	}

	static void deserializeComponent(ref TestComp comp, ubyte[] data)
	{

	}
}

static struct TestComp5
{
	mixin ECS.Component; //__gshared ushort component_id;
	uint gg = 7; //good game
	uint bg = 8; //bad game
	ulong a = 9;
	ulong b = 10;
	ulong c = 11;
	ulong g = 12;

	static void serializeComponent(ref TestComp comp, SerializeVector output)
	{

	}

	static void deserializeComponent(ref TestComp comp, ubyte[] data)
	{

	}
}

struct ChangeTestSystem
{
	mixin ECS.System!16; //__gshared ushort system_id;

	void onCreate()
	{
		//writeln("On Change Test System create.");
		printf("On Change Test System create.\n");
	}

	void onCreate(int i)
	{
		//writeln("On Change Test System create.");
		printf("On Change Test System create.\n");
	}

	void onDestroy()
	{
		//writeln("On Change Test System destroy.");
		printf("On Change Test System destroy.\n");
	}

	void onAddEntity(EntitiesData data)
	{
		//printf("Entity added! ID: ");
		foreach (i; 0 .. data.length)
			printf("Entity added! ID: %u\n", cast(uint) data.entites[i].id.id);
		////writeln("Entity added! ID: ", data.entites[i].id);
	}

	void onRemoveEntity(EntitiesData data)
	{
		////writeln("Entity removed! ID: ", data.entites[0].id);
		printf("Entity removed! ID: %u\n", cast(uint) data.entites[0].id.id);
	}

	void onChangeEntity(EntitiesData data)
	{
		////writeln("Entity changed! ID: ", data.entites[0].id);
		printf("Entity changed! ID: %u\n", cast(uint) data.entites[0].id.id);
	}

	bool onBegin()
	{
		////writeln("On Test System begin.");
		return true;
	}

	void onEnd()
	{
		////writeln("On Test System end.");
	}

	void initialize(ref Entity entity, ref TestComp comp)
	{

	}

	static struct EntitiesData
	{
		size_t length;
		const(Entity)[] entites;
		TestComp4[] test4;
		@optional TestComp5[] test5;
	}

	void onUpdate(EntitiesData data)
	{
		foreach (i; 0 .. data.length)
		{

		}
	}
}

struct TestSystem
{
	mixin ECS.System!16; //__gshared ushort system_id;

	void onCreate()
	{
		//writeln("On Test System create.");
		printf("On Change Test System create.\n");
	}

	void onDestroy()
	{
		//writeln("On Test System destroy.");
		printf("On Change Test System destroy.\n");
	}

	void onAddEntity(EntitiesData data)
	{
		//foreach(i;0..data.length)
		////writeln("Entity added ID: ",data.entites[i].id.id);
	}
	/*
	void onRemoveEntity(EntitiesData data)
	{
		////writeln("Entity destroyed ID: ",data.entites[0].id);
	}*/

	bool onBegin()
	{
		////writeln("On Test System begin.");
		return true;
	}

	void onEnd()
	{
		////writeln("On Test System end.");
	}

	void initialize(ref Entity entity, ref TestComp comp)
	{

	}

	static struct EntitiesData
	{
		size_t length;
		const(Entity)[] entites;
		TestComp[] test;
		TestComp2[] test2;
		@readonly @optional const(TestComp3)[] test3;
		//@excluded TestComp4[] test4;
	}

	void onUpdate(ref Entity entity, ref TestComp test, ref TestComp2 test2) //, TestComp3* test3) //ref TestComp comp)
	{
		//assert(cast(size_t)&test % TestComp.alignof == 0);
		//assert(cast(size_t)&test2 % TestComp2.alignof == 0);

		test.a += 1000;
		test.b += 2000;
		test2.b += 2;
		test2.a = 8;
	}

	void onUpdate(EntitiesData data)
	{
		foreach (i; 0 .. data.length)
		{
			data.test[i].a += 1000;
			data.test[i].b += 2000;
			data.test2[i].b += 2;
			data.test2[i].a = 8;
		}
	}

	void handleEvent(TestEvent event, ref TestComp test, ref TestComp2 test2, TestComp3* test3)
	{

	}
}

struct TestSystemWithHighPriority
{
	mixin ECS.System!16; //__gshared ushort system_id;

	static struct EntitiesData
	{
		TestComp[] test;
	}

	void initialize(ref Entity entity, ref TestComp comp)
	{
		int o = 1;
	}

	void onUpdate(EntitiesData data)
	{

	}
}

struct Sys1
{
	mixin ECS.System;

	struct EntitiesData
	{
		TestComp[] comp;
	}

	void onAddEntity(EntitiesData data)
	{

	}
}

struct Sys2
{
	mixin ECS.System;

	struct EntitiesData
	{
		TestComp[] comp;
	}

	void onAddEntity(EntitiesData data)
	{

	}
}

struct Sys3
{
	mixin ECS.System;

	struct EntitiesData
	{
		TestComp[] comp;
	}

	void onAddEntity(EntitiesData data)
	{

	}

	void onUpdate(EntitiesData data)
	{

	}
}

struct EmptyEventSystem
{
	mixin ECS.System;

	bool handled = false;

	struct EntitiesData
	{
		uint thread_id;
	}

	void handleEvent(Entity* entity, TestEvent event)
	{
		if (!handled)
		{
			printf("EmptyEventSystem.handleEvent() called!\n");
			handled = true;
		}
		assert(0, "this shouldn't be called!");
	}
}

struct EventSystem
{
	mixin ECS.System;

	bool handled = false;

	struct EntitiesData
	{
		uint thread_id;
		TestComp[] comp;
	}

	void handleEvent(Entity* entity, TestEvent event)
	{
		if (!handled)
		{
			printf("EventSystem.handleEvent() called!\n");
			handled = true;
		}
	}

	/*void onUpdate(EntitiesData)
	{

	}*/
}

struct EmptySystem
{
	mixin ECS.System;

	struct EntitiesData
	{
		uint thread_id;
	}

	void onUpdate(EntitiesData data)
	{
		printf("EmptySystem.onUpdate() - this should be called once per update\n");
	}
}

import std.meta;

struct TestSystem2
{
	mixin ECS.System!16; //__gshared ushort system_id;

	/*enum ExcludedComponents
	{
		TestComp,
		TestComp4
	}*/

	//alias ExcludedComponents = AliasSeq!("TestComp", "TestComp4");
	/*
	string ExcludedComponents2;*/

	static struct EntitiesData
	{
		short length;
		const(Entity)[] entity;
		TestComp3[] test;
		//@excluded TestComp[] testt;
	}

	static struct EventInput
	{
		Entity* entity;
		TestComp3* test;
		//TestComp* tt;
	}

	void handleEvent(Entity* entity, TestEvent event)
	{
		TestComp3* test = entity.getComponent!TestComp3;
		test.bg = event.a;
		TestEvent2 event2;
		event2.a = event.a + 8;
		gEM.sendEvent(entity.id, event2);
	}

	void handleEvent(Entity* entity, TestEvent2 event)
	{
		TestComp3* test = entity.getComponent!TestComp3;
		test.gg = cast(uint) event.a;
	}

	void onEnable()
	{

		//writeln("TestSystem2 enabled");
		printf("TestSystem2 enabled\n");
	}

	void onDisable()
	{

		//writeln("TestSystem2 disabled");
		printf("TestSystem2 disabled\n");
	}

	void initialize(ref Entity entity, ref TestComp comp)
	{

	}

	void onUpdate(EntitiesData data)
	{
		foreach (i; 0 .. data.test.length)
		{
			data.test[i].gg += 14;
			TestEvent event;
			event.a = data.test[i].gg + 4;
			gEM.sendEvent(data.entity[i].id, event); //*/
			/*TestEvent2 event2;
			event2.a = data.test[i].gg + 8;
			gEM.sendEvent(data.entity[i].id, event2);//*/
			//gEM.sendEvent!(TestEvent)(data.entity[i].id, event);
			//gEM.sendSelfEvent!(TestEvent)(data.entity[i].id, TestEvent());
		}
	}

	void lateUpdate(ref EntitiesData data)
	{
		foreach (i; 0 .. data.test.length)
		{
			data.test[i].gg -= 1;
			//gEM.sendSelfEvent!(TestEvent)(data.entity[i].id, TestEvent());
		}
	}

}

struct ExternalUpdateCallTest
{
	int print_count = 3;

	void update(TestSystem2.EntitiesData data)
	{
		if (print_count > 0)
		{
			print_count--;
			printf("ExternalUpdateCallTest %u %u\n", data.test[0].gg, cast(uint) data.length);
		}
	}
}

version (unittest)
{
	void main()
	{

	}
}
else:
	extern (C) int main()
{

	void dispatch(EntityManager.JobGroup jobs) nothrow @nogc
	{
		foreach (job; jobs.jobs)
		{
			////writeln(job);
			job.execute();
		}
	}

	uint getID() nothrow @nogc
	{
		return 0;
	}

	void writeEntityComponents(Entity* entity)
	{

		printf("EntityID(%u, %u)", cast(uint) entity.id.id, cast(uint) entity.id.counter);
		//write(entity.id);
		TestComp* test_comp = entity.getComponent!TestComp;
		if (test_comp)
			printf("TestComp(%u, %u)", cast(uint) test_comp.a, cast(uint) test_comp.b); //write(*test_comp);
		TestComp2* test_comp2 = entity.getComponent!TestComp2;
		if (test_comp2)
			printf("TestComp2(%u, %u)", cast(uint) test_comp2.b, cast(uint) test_comp2.a); //write(*test_comp2);
		TestComp3* test_comp3 = entity.getComponent!TestComp3;
		if (test_comp3)
			printf("TestComp3(%u, %u)", cast(uint) test_comp3.gg, cast(uint) test_comp3.bg); //write(*test_comp3);
		TestComp4* test_comp4 = entity.getComponent!TestComp4;
		if (test_comp4)
			printf("TestComp4(%u, %u, %u, %u, %u, %u)", test_comp4.gg, test_comp4.bg,
					cast(uint) test_comp4.a, cast(uint) test_comp4.b,
					cast(uint) test_comp4.c, cast(uint) test_comp4.g); //write(*test_comp4);
		printf("\n");
		//writeln();
		////writeln((cast(uint*) pp)[0 .. 14], " ", pp);
	}

	EntityManager.initialize(1);

	//gEM.setJobDispachFunc(&dispatch);
	gEM.setMultithreadingCallbacks(&dispatch, &getID);
	//assert(gEM !is null);

	gEM.beginRegister();
	gEM.registerPass("fixed");

	//MonoTime time = MonoTime.currTime;
	long time = Time.getUSecTime();

	gEM.registerComponent!TestComp2;
	gEM.registerComponent!TestComp4;
	gEM.registerComponent!TestComp;
	gEM.registerComponent!TestComp3;
	gEM.registerComponent!TestComp5;

	gEM.registerEvent!TestEvent;
	gEM.registerEvent!TestEvent2;

	/*ulong dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Components register: ", dur, " usecs");

	time = MonoTime.currTime;*/

	printf("Components register: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	gEM.registerSystem!TestSystemWithHighPriority(100, "fixed");
	gEM.registerSystem!TestSystem(0);
	gEM.registerSystem!ChangeTestSystem(0);
	gEM.registerSystem!Sys1(10);
	gEM.registerSystem!Sys2(-100);
	gEM.registerSystem!Sys3(-2);
	gEM.registerSystem!EmptySystem(2);
	gEM.registerSystem!EmptyEventSystem(2);
	gEM.registerSystem!EventSystem(2);
	//gEM.registerSystem!TestSystemWithHighPriority(100);
	//gEM.registerSystem!TestSystem2(0);
	gEM.endRegister();

	/*dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Systems register: ", dur, " usecs");

	time = MonoTime.currTime;*/
	printf("Systems register: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	//ushort[3] ids = [TestComp2.component_id, TestComp.component_id, TestComp4.component_id];
	ushort[2] ids = [TestComp2.component_id, TestComp.component_id];
	EntityTemplate* tmpl = gEM.allocateTemplate(ids);

	//ushort[3] ids2 = [TestComp3.component_id, TestComp.component_id, TestComp4.component_id];
	ushort[2] ids2 = [TestComp3.component_id, TestComp.component_id];
	EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2);
	////writeln(tmpl.info.components[]);
	//*cast(EntityID*) tmpl.entity_data.ptr = EntityID(1, 1);

	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Template allocating: ", dur, " usecs");
	printf("Template allocating: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	EntityID entity;

	{
		entity = gEM.addEntity(tmpl).id;
		writeEntityComponents(gEM.getEntity(entity));
		EntityManager.EntitiesBlock* block = EntityManager.instance.getMetaData(
				gEM.getEntity(entity));
		EntityManager.EntityInfo* info = block.type_info;
		//writeln(info.add_listeners);
		//if(info)assert(0);
	}

	//time = MonoTime.currTime;
	time = Time.getUSecTime();

	//foreach(i; 0..1_000_000)gEM.addEntity(tmpl);

	//foreach(i; 0..1_000_000)gEM.removeEntity(gEM.addEntity(tmpl).id);

	import bubel.ecs.std;

	EntityID[] idss = Mallocator.makeArray!EntityID(5000); //[5000]
	//scope(exit)Mallocator.dispose(idss);

	foreach (i; 0 .. 200)
	{
		gEM.begin();
		foreach (j; 0 .. 5_000)
			idss[j] = gEM.addEntity(tmpl).id;
		foreach (j; 0 .. 5_000)
			gEM.removeEntity(idss[j]);
		gEM.end();
	}
	gEM.commit();

	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Entities adding: ", dur, " usecs");
	printf("Entities adding: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	uint blocks = 0;
	foreach (info; &gEM.entities_infos.byValue)
	{
		EntityManager.EntitiesBlock* block = info.first_block;
		while (block !is null)
		{
			block = block.next_block;
			blocks++;
		}
	}
	//writeln("Entities blocks: ", blocks);
	printf("Entities blocks: %u\n", blocks);

	//foreach(j; 0..1_000)gEM.addEntity(tmpl);

	gEM.beginRegister();
	gEM.registerSystem!TestSystem2(0);
	gEM.endRegister();

	//gEM.generateDependencies();

	//assert(*(cast(EntityID*)(cast(void*)tmpl.info.first_block+24)) == EntityID(1,1));
	//assert(*(cast(EntityID*)(cast(void*)tmpl.info.first_block+48)) == EntityID(1,1));

	EntityID entity2;

	//time = MonoTime.currTime;
	time = Time.getUSecTime();

	EntityID[] entities = Mallocator.makeArray!EntityID(1_000_000);
	foreach (i; 0 .. 500_000)
	{
		entity2 = gEM.addEntity(tmpl).id;
		entities[i * 2] = entity2;
		entities[i * 2 + 1] = gEM.addEntity(tmpl2).id;
	}

	gEM.commit();
	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Entities adding2: ", dur, " usecs");

	//time = MonoTime.currTime;
	printf("Entities adding2: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	foreach (i; 0 .. 1_000_000)
	{
		EntityManager.instance.addComponents(entities[i], TestComp5());
		if ((i & 0x00FFFF) == 0)
			gEM.commit();
	}

	gEM.commit();
	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Components adding: ", dur, " usecs");

	//time = MonoTime.currTime;
	printf("Components adding: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	foreach (i; 0 .. 1_000_000)
	{
		EntityManager.instance.removeComponents!TestComp5(entities[i]);
		//if((i & 0x00FFFF) == 0)gEM.commit();
	}

	gEM.commit();
	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Components removing: ", dur, " usecs");
	printf("Components removing: %f usecs\n", cast(float)(Time.getUSecTime() - time));
	time = Time.getUSecTime();

	Mallocator.dispose(entities);

	//time = MonoTime.currTime;
	time = Time.getUSecTime();

	gEM.begin();
	//gEM.updateMT();
	gEM.update();
	gEM.end();

	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Update: ", dur, " usecs");
	printf("Update: %f usecs\n", cast(float)(Time.getUSecTime() - time));

	writeEntityComponents(gEM.getEntity(entity2));

	//time = MonoTime.currTime;
	time = Time.getUSecTime();

	gEM.begin();
	gEM.updateMT();
	//gEM.update();
	gEM.end();

	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Update: ", dur, " usecs");
	printf("Update: %f usecs\n", cast(float)(Time.getUSecTime() - time));

	writeEntityComponents(gEM.getEntity(entity2));

	//time = MonoTime.currTime;
	time = Time.getUSecTime();

	gEM.begin();
	gEM.updateMT();
	//gEM.update();
	gEM.end();

	//dur = (MonoTime.currTime - time).total!"usecs";
	//writeln("Update: ", dur, " usecs");
	printf("Update: %f usecs\n", cast(float)(Time.getUSecTime() - time));

	writeEntityComponents(gEM.getEntity(entity2));

	entity = gEM.addEntity(tmpl).id;

	gEM.begin();
	gEM.update();
	gEM.end();

	//Entity* pp;// = gEM.getEntity(entity.id);
	////writeln((cast(uint*) pp)[0 .. 14], " ", pp);
	writeEntityComponents(gEM.getEntity(entity));

	//writeln("Entity, its copy, and template, and default filled tempalte");
	gEM.addEntity(tmpl);
	writeEntityComponents(gEM.getEntity(entity));
	writeEntityComponents(gEM.addEntityCopy(entity));
	EntityTemplate* copy_tempalte = gEM.allocateTemplate(entity);
	writeEntityComponents(gEM.addEntity(copy_tempalte));
	EntityTemplate* copy_default_tempalte = gEM.allocateTemplate(entity, true);
	writeEntityComponents(gEM.addEntity(copy_default_tempalte));

	gEM.addComponents(entity, TestComp4());
	gEM.addComponents(entity, TestComp3());

	gEM.begin();
	gEM.update();
	gEM.end();

	writeEntityComponents(gEM.getEntity(entity));

	gEM.removeComponents!(TestComp)(entity);
	gEM.addComponents(entity, TestComp());
	gEM.addComponents(entity, TestComp5());

	gEM.begin();
	gEM.update();
	gEM.update("fixed");
	gEM.end();

	gEM.removeComponents!(TestComp4)(entity);

	gEM.commit();

	System* sys = EntityManager.instance.getSystem(TestSystem2.system_id);

	ExternalUpdateCallTest external_update_test;

	EntityManager.instance.callEntitiesFunction!TestSystem2(&external_update_test.update);

	printf("pre end\n");

	writeEntityComponents(gEM.getEntity(entity));
	//import std.stdio;
	////writeln((cast(uint*)tmpl.info.first_block)[0..48]);
	gEM.freeTemplate(tmpl);
	gEM.freeTemplate(tmpl2);
	gEM.freeTemplate(copy_tempalte);
	gEM.freeTemplate(copy_default_tempalte);
	EntityManager.destroy();

	Mallocator.dispose(idss);

	printf("end\n"); //*/

	return 0;
}
