module tests.basic;

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

import std.array : staticArray;

struct CInt
{
    mixin ECS.Component;

    alias value this;

    int value = 1;
}

struct CFloat
{
    mixin ECS.Component;

    alias value this;

    float value = 2.0;
}

struct CDouble
{
    mixin ECS.Component;

    alias value this;

    double value = 3.0;
}

struct CLong
{
    mixin ECS.Component;

    alias value this;

    long value = 10;
}

struct CShort
{
    mixin ECS.Component;

    alias value this;

    short value = 12;
}

struct CUnregistered
{
    mixin ECS.Component;

    alias value this;

    short value = 12;
}

struct LongAddSystem
{
    mixin ECS.System;

    struct EntitiesData
    {
        int length;

        CLong[] long_;
    }

    void onUpdate(EntitiesData data)
    {
        updates_count++;
        foreach(i;0..data.length)
        {
            data.long_[i] += 1;
        }
    }

    int updates_count = 0;
}

struct EmptySystem
{
    mixin ECS.System!16;

    struct EntitiesData
    {
        int thread_id;
    }

    void onUpdate(EntitiesData data)
    {
        count++;
    }

    int count = 0;
}

void beforeEveryTest()
{
    gEM.initialize(0);

    gEM.beginRegister();

    gEM.registerComponent!CInt;
    gEM.registerComponent!CFloat;
    gEM.registerComponent!CDouble;
    gEM.registerComponent!CLong;
    gEM.registerComponent!CShort;

    gEM.endRegister();
}

void afterEveryTest()
{
    gEM.destroy();
}

@("AddEntity")
unittest
{
    ushort[2] ids = [CInt.component_id, CFloat.component_id];
    EntityTemplate* tmpl_ = gEM.allocateTemplate(ids);
    assert(tmpl_.info.components.length == 2);
    assert(tmpl_.getComponent!CInt);
    assert(tmpl_.getComponent!CFloat);
    assert(!tmpl_.getComponent!CLong);
    assert(!tmpl_.getComponent!CUnregistered);
    assert(*tmpl_.getComponent!CInt == 1);
    assert(*tmpl_.getComponent!CFloat == 2.0);

    Entity* entity = gEM.addEntity(tmpl_);
    assert(entity.getComponent!CInt);
    assert(entity.getComponent!CFloat);
    assert(*entity.getComponent!CInt == 1);
    assert(*entity.getComponent!CFloat == 2.0);
    *entity.getComponent!CInt = 2;

    Entity* entity2 = gEM.addEntityCopy(entity.id);
    assert(entity2.getComponent!CInt);
    assert(entity2.getComponent!CFloat);
    assert(*entity2.getComponent!CInt == 2);
    assert(*entity2.getComponent!CFloat == 2.0);
}

//allocate templates
@("AllocateTemplates")
unittest
{
    //basic template allocation
    ushort[2] ids = [CInt.component_id, CFloat.component_id];
    EntityTemplate* tmpl_ = gEM.allocateTemplate(ids);
    EntityTemplate* tmpl_d = gEM.allocateTemplate([CFloat.component_id, CInt.component_id, CFloat.component_id].staticArray);
    assert(tmpl_d.info == tmpl_.info);
    assert(tmpl_.info.components.length == 2);
    assert(tmpl_.getComponent!CInt);
    assert(tmpl_.getComponent!CFloat);
    assert(*tmpl_.getComponent!CInt == 1);
    assert(*tmpl_.getComponent!CFloat == 2.0);
    *tmpl_.getComponent!CInt = 4;
    *tmpl_.getComponent!CFloat = 5.0;

    //allocate template from template with additional component
    ushort[1] ids2 = [CDouble.component_id];
    EntityTemplate* tmpl_2 = gEM.allocateTemplate(tmpl_, ids2);
    assert(tmpl_2.info.components.length == 3);
    assert(tmpl_2.getComponent!CInt);
    assert(tmpl_2.getComponent!CFloat);
    assert(tmpl_2.getComponent!CDouble);
    assert(*tmpl_2.getComponent!CInt == 4);
    assert(*tmpl_2.getComponent!CFloat == 5.0);
    assert(*tmpl_2.getComponent!CDouble == 3.0);

    Entity* entity = gEM.addEntity(tmpl_);
    gEM.addComponents(entity.id, CDouble(8.0));

    //apply entity changes
    gEM.commit();

    //allocate template as entity copy
    EntityTemplate* tmpl_3 = gEM.allocateTemplate(entity.id);
    assert(tmpl_3.info.components.length == 3);
    assert(tmpl_3.getComponent!CInt);
    assert(tmpl_3.getComponent!CFloat);
    assert(tmpl_3.getComponent!CDouble);
    assert(*tmpl_3.getComponent!CInt == 4);
    assert(*tmpl_3.getComponent!CFloat == 5.0);
    assert(*tmpl_3.getComponent!CDouble == 8.0);

    //allocate template with entity data but default values
    EntityTemplate* tmpl_4 = gEM.allocateTemplate(entity.id, true);
    assert(tmpl_4.info.components.length == 3);
    assert(tmpl_4.getComponent!CInt);
    assert(tmpl_4.getComponent!CFloat);
    assert(tmpl_4.getComponent!CDouble);
    assert(*tmpl_4.getComponent!CInt == 1);
    assert(*tmpl_4.getComponent!CFloat == 2.0);
    assert(*tmpl_4.getComponent!CDouble == 3.0);

    //allocate template from template with two additional component
    ushort[2] ids3 = [CDouble.component_id, CLong.component_id];
    EntityTemplate* tmpl_5 = gEM.allocateTemplate(tmpl_, ids3);
    assert(tmpl_5.info.components.length == 4);
    assert(tmpl_5.getComponent!CInt);
    assert(tmpl_5.getComponent!CFloat);
    assert(tmpl_5.getComponent!CDouble);
    assert(tmpl_5.getComponent!CLong);
    assert(*tmpl_5.getComponent!CInt == 4);
    assert(*tmpl_5.getComponent!CFloat == 5.0);
    assert(*tmpl_5.getComponent!CDouble == 3.0);
    assert(*tmpl_5.getComponent!CLong == 10);

    //allocate template from template without one component
    ushort[1] rem_ids = [CFloat.component_id];
    EntityTemplate* tmpl_6 = gEM.allocateTemplate(tmpl_, null, rem_ids);
    assert(tmpl_6.info.components.length == 1);
    assert(tmpl_6.getComponent!CInt);
    assert(*tmpl_6.getComponent!CInt == 4);

    //allocate template from template without one component and two additional
    EntityTemplate* tmpl_7 = gEM.allocateTemplate(tmpl_, ids3, rem_ids);
    assert(tmpl_7.info.components.length == 3);
    assert(tmpl_7.getComponent!CInt);
    assert(tmpl_7.getComponent!CDouble);
    assert(tmpl_7.getComponent!CLong);
    assert(*tmpl_7.getComponent!CInt == 4);
    assert(*tmpl_7.getComponent!CDouble == 3.0);
    assert(*tmpl_7.getComponent!CLong == 10);

    gEM.freeTemplate(tmpl_d);
    gEM.freeTemplate(tmpl_);
    gEM.freeTemplate(tmpl_2);
    gEM.freeTemplate(tmpl_3);
    gEM.freeTemplate(tmpl_4);
    gEM.freeTemplate(tmpl_5);
    gEM.freeTemplate(tmpl_6);
    gEM.freeTemplate(tmpl_7);
}

@("UnsortedComponentIDs")
unittest
{
    //basic template allocation
    ushort[2] ids = [CFloat.component_id, CInt.component_id];
    ushort[2] ids2 = [CInt.component_id, CFloat.component_id];
    EntityTemplate* tmpl_ = gEM.allocateTemplate(ids);
    EntityTemplate* tmpl_2 = gEM.allocateTemplate(ids2);
    assert(tmpl_.info.components.length == 2);
    assert(tmpl_.getComponent!CInt);
    assert(tmpl_.getComponent!CFloat);
    assert(*tmpl_.getComponent!CInt == 1);
    assert(*tmpl_.getComponent!CFloat == 2.0);
    assert(tmpl_.info == tmpl_2.info);
}

@("MultiRegister")
unittest
{
    gEM.beginRegister();

    gEM.endRegister();

    gEM.beginRegister();

    gEM.registerComponent!CLong;
    gEM.registerComponent!CShort;

    gEM.endRegister();
}

@("EmptySystem")
unittest
{
    gEM.beginRegister();

    gEM.registerSystem!EmptySystem(0);

    gEM.endRegister();

    EmptySystem* system = gEM.getSystem!EmptySystem;
    assert(system !is null);
    assert(system.count == 0);

    System* ecs_system = gEM.getSystem(EmptySystem.system_id);
    assert(ecs_system !is null);
    assert(ecs_system.id == EmptySystem.system_id);
    assert(ecs_system.name == "EmptySystem");

    gEM.begin();

    gEM.update();

    gEM.end();

    assert(system.count == 1);
}

@("SystemCallbacks")
unittest
{
    struct TestSystem
    {
        mixin ECS.System!16;

        mixin ECS.ExcludedComponents!(CShort);

        struct EntitiesData
        {
            int length;
            CLong[] long_;
            @optional CInt[] int_;
        }

        void onCreate()
        {
            create++;
        }

        void onDestroy()
        {
            if(destroy)(*destroy)++;
        }

        void onEnable()
        {
            enable++;
        }

        void onDisable()
        {
            disable++;
        }

        bool onBegin()
        {
            begin++;
            update = 0;
            return pass;
        }

        void onEnd()
        {
            end++;
        }

        void onUpdate(EntitiesData data)
        {
            update++;
        }

        int create = 0;
        int* destroy;
        int update = 0;
        int begin = 0;
        int end = 0;
        int enable = 0;
        int disable = 0;
        bool pass = true;
    }

    gEM.beginRegister();

    gEM.registerSystem!TestSystem(0);

    gEM.endRegister();

    TestSystem* system = gEM.getSystem!TestSystem;
    int destroy = 0;
    system.destroy = &destroy;

    gEM.beginRegister();

    gEM.registerSystem!TestSystem(0);

    gEM.endRegister();

    system = gEM.getSystem!TestSystem;
    system.destroy = &destroy;
    assert(system !is null);
    assert(system.create == 1);
    assert(system.begin == 0);
    assert(system.end == 0);
    assert(system.enable == 1);
    assert(system.disable == 0);
    //FIXME: currently destroy is only called with Manager.destory which is bug, but there is no workaround for this by now
    //assert(destroy == 1);

    System* ecs_system = gEM.getSystem(system.system_id);

    ecs_system.enable();
    assert(system.enable == 1);
    ecs_system.disable();
    ecs_system.disable();
    ecs_system.enable();
    assert(system.enable == 2);
    assert(system.disable == 1);


    ushort[2] ids = [CLong.component_id,CFloat.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);
    gEM.addEntity(tmpl);

    gEM.begin();
    assert(system.begin == 1);

    gEM.update();
    assert(system.update == 1);

    gEM.end();
    assert(system.end == 1);

    ushort[2] ids2 = [CLong.component_id, CInt.component_id];
    EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2);
    scope (exit) gEM.freeTemplate(tmpl2);
    gEM.addEntity(tmpl2);
    gEM.addEntity(tmpl2);

    gEM.begin();
    assert(system.begin == 2);

    gEM.update();
    assert(system.update == 2);//system is updated number of entity blocks times

    gEM.end();
    assert(system.end == 2);

    ushort[2] ids3 = [CLong.component_id, CShort.component_id];
    EntityTemplate* tmpl3 = gEM.allocateTemplate(ids3);
    scope (exit) gEM.freeTemplate(tmpl3);
    gEM.addEntity(tmpl3);

    //entity with excluded component shouldn't be updated
    gEM.begin();
    assert(system.begin == 3);

    gEM.update();
    assert(system.update == 2);

    gEM.end();
    assert(system.end == 3);

    //system can be disable form update in onBegin() callback, onEnd() callback is called
    system.pass = false;
    gEM.begin();
    assert(system.begin == 4);

    gEM.update();
    assert(system.update == 0);

    gEM.end();
    assert(system.end == 4);
    system.pass = true;

    //disabled system is't called
    ecs_system.disable();
    gEM.begin();
    assert(system.begin == 4);

    gEM.update();
    assert(system.update == 0);

    gEM.end();
    assert(system.end == 4);
    ecs_system.enable();
    system.destroy = null;
}

@("CustomPass")
unittest
{
    gEM.beginRegister();

    gEM.registerPass("custom");
    gEM.registerSystem!LongAddSystem(-1,"custom");

    gEM.endRegister();

    LongAddSystem* system = gEM.getSystem!LongAddSystem;
    assert(system !is null);
    assert(system.updates_count == 0);

    System* ecs_system = gEM.getSystem(LongAddSystem.system_id);
    assert(ecs_system !is null);
    assert(ecs_system.id == LongAddSystem.system_id);
    assert(ecs_system.priority == -1);
    assert(ecs_system.name == "LongAddSystem");

    ushort[1] ids = [CLong.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);
    gEM.addEntity(tmpl);

    gEM.begin();

    gEM.update();
    assert(system.updates_count == 0);
    gEM.update("custom");
    assert(system.updates_count == 1);

    gEM.end();
}

@("SystemEntityCallbacks")
unittest
{
    static int add_order = 0;
    static int rem_order = 0;
    static int change_order = 0;
    struct TestSystem
    {
        mixin ECS.System!16;

        mixin ECS.ExcludedComponents!(CShort);

        struct EntitiesData
        {
            int length;
            CLong[] long_;
            @optional CInt[] int_;
        }

        void onAddEntity(EntitiesData data)
        {
            add++;
            assert(add_order == 1);  
            add_order++; 
        }
        
        void onRemoveEntity(EntitiesData data)
        {
            remove++;
            assert(rem_order == 1);  
            rem_order++; 
        }
        
        void onChangeEntity(EntitiesData data)
        {
            change++;
            assert(change_order == 1);  
            change_order++; 
        }

        void onUpdate(EntitiesData data)
        {
        }

        int add = 0;
        int remove = 0;
        int change = 0;
    }

    struct TestSystem2
    {
        mixin ECS.System!16;

        mixin ECS.ExcludedComponents!(CShort);

        struct EntitiesData
        {
            int length;
            CLong[] long_;
            @optional CInt[] int_;
        }

        void onAddEntity(EntitiesData data)
        {
            assert(add_order == 2);
            add_order = 0;
        }
        
        void onRemoveEntity(EntitiesData data)
        {
            assert(rem_order == 2);  
            rem_order = 0 ;
        }
        
        void onChangeEntity(EntitiesData data)
        {
            assert(change_order == 2);  
            change_order = 0;
        }

        void onUpdate(EntitiesData data)
        {
        }
    }

    struct TestSystem3
    {
        mixin ECS.System!16;

        mixin ECS.ExcludedComponents!(CShort);

        struct EntitiesData
        {
            int length;
            CLong[] long_;
            @optional CInt[] int_;
        }

        void onAddEntity(EntitiesData data)
        {
            assert(add_order == 0);
            add_order++;
        }
        
        void onRemoveEntity(EntitiesData data)
        {
            assert(rem_order == 0);  
            rem_order++; 
        }
        
        void onChangeEntity(EntitiesData data)
        {
            assert(change_order == 0);  
            change_order++; 
        }

        void onUpdate(EntitiesData data)
        {
        }
    }

    gEM.beginRegister();

    gEM.registerSystem!TestSystem3(-1);
    gEM.registerSystem!TestSystem(0);
    gEM.registerSystem!TestSystem2(1);

    gEM.endRegister();

    TestSystem* system = gEM.getSystem!TestSystem;
    assert(system !is null);
    assert(system.add == 0);
    assert(system.remove == 0);
    assert(system.change == 0);

    EntityTemplate* tmpl = gEM.allocateTemplate([CLong.component_id,CFloat.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl);
    EntityID id0 = gEM.addEntity(tmpl).id;
    gEM.commit();
    assert(system.add == 1);

    EntityTemplate* tmpl2 = gEM.allocateTemplate([CLong.component_id, CInt.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl2);
    EntityID id1 = gEM.addEntity(tmpl2).id;
    gEM.commit();
    assert(system.add == 2);

    EntityTemplate* tmpl3 = gEM.allocateTemplate([CLong.component_id, CShort.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl3);
    EntityID id2 = gEM.addEntity(tmpl3).id;
    gEM.commit();
    assert(system.add == 2);

    gEM.beginRegister();
    gEM.endRegister();

    gEM.removeComponents(id0, [CFloat.component_id].staticArray);
    gEM.commit();
    assert(system.add == 2);
    assert(system.remove == 0);
    assert(system.change == 0);

    gEM.removeComponents(id1, [CInt.component_id].staticArray);
    gEM.commit();
    assert(system.add == 2);
    assert(system.remove == 0);
    assert(system.change == 1);

    gEM.removeComponents(id2, [CShort.component_id].staticArray);
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 0);
    assert(system.change == 1);

    gEM.addComponents(id2, CShort(1));
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 1);
    assert(system.change == 1);

    gEM.removeEntity(id0);
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 2);
    assert(system.change == 1);

    gEM.addComponents(id1, CInt(1));
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 2);
    assert(system.change == 2);

    gEM.addComponents(id0, CFloat(1));
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 2);
    assert(system.change == 2);

    gEM.removeEntity(id1);
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 3);
    assert(system.change == 2);

    gEM.removeEntity(id2);
    gEM.commit();
    assert(system.add == 3);
    assert(system.remove == 3);
    assert(system.change == 2);
}

@("TemplateCoverage")
unittest
{
    struct TestSystem 
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
            Entity[] entity;
            @readonly CLong[] long_;
            @optional CInt[] int_;
            @readonly CFloat[] float_;
        }

        mixin ECS.ExcludedComponents!(CUnregistered);
    
        void onUpdate(EntitiesData data)
        {

        }
    }

    gEM.beginRegister();

    gEM.registerComponent!CUnregistered;

    gEM.registerSystem!TestSystem(0);

    gEM.endRegister();
}

@("UnregisteredSystem")
unittest
{
    struct TestSystem 
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
            Entity[] entity;
            @readonly CLong[] long_;
            @optional CInt[] int_;
            @readonly CFloat[] float_;
        }
    
        void onUpdate(EntitiesData data)
        {

        }
    }

    assert(gEM.getSystem!TestSystem is null);
    assert(gEM.getSystem(TestSystem.system_id) is null);
}

@("MultithreadedUpdate")
unittest
{
    struct TestSystem 
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
            Entity[] entity;
            @readonly CLong[] long_;
            @optional CInt[] int_;
            @readonly CFloat[] float_;
        }
    
        void onUpdate(EntitiesData data)
        {
            update++;
            entities += data.length;
        }

        int update = 0;
        int entities = 0;
    }

    struct TestEmptySystem 
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
        }
    
        void onUpdate(EntitiesData data)
        {
            update++;
        }

        int update = 0;
    }

    void dispatch(EntityManager.JobGroup grp)
    {
        foreach(job; grp.jobs)
        {
            job.execute();
        }
    }

    uint getID()
    {
        return 0;
    }

    gEM.setMultithreadingCallbacks(&dispatch, &getID);

    gEM.beginRegister();

    gEM.registerPass("custom");
    gEM.registerSystem!TestSystem(-1,"custom");
    gEM.registerSystem!TestEmptySystem(1,"custom");

    gEM.endRegister();

    TestSystem* system = gEM.getSystem!TestSystem;
    TestEmptySystem* empty_system = gEM.getSystem!TestEmptySystem;

    ushort[2] ids = [CLong.component_id,CFloat.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);
    EntityTemplate* tmpl2 = gEM.allocateTemplate([CLong.component_id,CInt.component_id,CShort.component_id,CFloat.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl2);

    gEM.begin();

    gEM.updateMT("custom");
    
    gEM.end();

    assert(system.update == 0);
    assert(system.entities == 0);
    assert(empty_system.update == 1);

    gEM.addEntity(tmpl);

    gEM.begin();

    gEM.updateMT("custom");
    
    gEM.end();

    assert(system.update == 1);
    assert(system.entities == 1);
    assert(empty_system.update == 2);
    system.entities = 0;

    foreach(i;0..2000)gEM.addEntity(tmpl);

    gEM.begin();

    gEM.updateMT("custom");
    
    gEM.end();

    assert(system.update > 2);
    assert(system.entities == 2001);
    assert(empty_system.update == 3);
    system.entities = 0;

    foreach(i;0..10000)gEM.addEntity(tmpl);

    gEM.begin();

    gEM.updateMT("custom");
    
    gEM.end();

    assert(system.entities == 12001);
}

unittest
{
    assert(gEM.pageSize == 32768);
    assert(gEM.pagesInBlock == 128);
}

@("AddRemoveEntities")
unittest
{
    ushort[3] ids = [CLong.component_id,CFloat.component_id,CShort.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);

    EntityID[5000] entities;

    foreach(i;0..4)
    {
        foreach(j;0..5000)
        {
            entities[j] = gEM.addEntity(tmpl).id;
        }
        gEM.commit();
        foreach(j;0..5000)
        {
            gEM.removeEntity(entities[j]);
        }
        gEM.commit();
    }
}

@("ChangeEntityComponents")
unittest
{
    gEM.beginRegister();

    gEM.registerComponent!CUnregistered;

    gEM.endRegister();

    ushort[1] ids = [CLong.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);

    EntityID id = gEM.addEntity(tmpl).id;
    gEM.commit();
    Entity* entity = gEM.getEntity(id);
    assert(entity.id == id);
    assert(entity.getComponent!CLong !is null);
    assert(entity.getComponent!CFloat is null);
    assert(entity.getComponent!CUnregistered is null);
    assert(entity.getComponent!CShort is null);
    assert(entity.getComponent!CInt is null);
    assert(*entity.getComponent!CLong == 10);

    gEM.addComponents(id, CShort(15), CFloat(13));
    gEM.commit();

    entity = gEM.getEntity(id);
    assert(entity.id == id);
    assert(entity.getComponent!CLong !is null);
    assert(entity.getComponent!CFloat !is null);
    assert(entity.getComponent!CUnregistered is null);
    assert(entity.getComponent!CShort !is null);
    assert(entity.getComponent!CInt is null);
    assert(*entity.getComponent!CLong == 10);
    assert(*entity.getComponent!CShort == 15);
    assert(*entity.getComponent!CFloat == 13);

    ushort[3] ids2 = [CFloat.component_id, CLong.component_id, CUnregistered.component_id];
    gEM.removeComponents(id, ids2);
    gEM.commit();

    entity = gEM.getEntity(id);
    assert(entity.id == id);
    assert(entity.getComponent!CLong is null);
    assert(entity.getComponent!CFloat is null);
    assert(entity.getComponent!CUnregistered is null);
    assert(entity.getComponent!CShort !is null);
    assert(entity.getComponent!CInt is null);
    assert(*entity.getComponent!CShort == 15);

    gEM.removeComponents(id, ids2);
    gEM.addComponents(id, CShort(11), CLong(2)); //wrong order of components 
    gEM.commit();

    entity = gEM.getEntity(id);
    assert(entity.id == id);
    assert(entity.getComponent!CLong !is null);
    assert(entity.getComponent!CFloat is null);
    assert(entity.getComponent!CUnregistered is null);
    assert(entity.getComponent!CShort !is null);
    assert(entity.getComponent!CInt is null);
    assert(*entity.getComponent!CLong == 2);
    assert(*entity.getComponent!CShort == 15);

    gEM.removeEntity(id);

    entity = gEM.getEntity(id);
    assert(entity !is null);
    assert(entity.id == id);

    gEM.commit();
    entity = gEM.getEntity(id);
    assert(entity is null);
}

@("EventCallbacks")
unittest
{
    struct ETest
    {
        mixin ECS.Event;
    }

    struct ETest2
    {
        mixin ECS.Event;
        
        void onDestroy()
        {
            destory++;
        }
        
        int super_liczba = 0;
        static int destory = 0;
    }

    struct TestSystem 
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
            Entity[] entity;
            @readonly CLong[] long_;
            @optional CInt[] int_;
        }
    
        void onUpdate(EntitiesData data)
        {

        }

        void handleEvent(Entity* entity, ETest event)
        {
            CLong* long_ = entity.getComponent!CLong;
            CInt* int_ = entity.getComponent!CInt;
            *long_ += 16;
            if(int_)*int_ += 6;
        }

        void handleEvent(Entity* entity, ETest2 event)
        {
            CLong* long_ = entity.getComponent!CLong;
            CInt* int_ = entity.getComponent!CInt;
            *long_ += event.super_liczba * 2;
            if(int_)*int_ += event.super_liczba * 4;
        }
    }

    struct TestSystem2
    {
        mixin ECS.System;

        struct EntitiesData
        {
            int length;
            Entity[] entity;
            CShort[] short_;
            @optional CInt[] int_;
        }
    
        void handleEvent(Entity* entity, ETest event)
        {
            CShort* short_ = entity.getComponent!CShort;
            CInt* int_ = entity.getComponent!CInt;
            *short_ += 8;
            if(int_)*int_ += 2;
        }

        void handleEvent(Entity* entity, ETest2 event)
        {
            CShort* short_ = entity.getComponent!CShort;
            CInt* int_ = entity.getComponent!CInt;
            *short_ += event.super_liczba;
            if(int_)*int_ *= event.super_liczba;
        }
    }

    gEM.beginRegister();

    gEM.registerEvent!ETest;
    gEM.registerEvent!ETest2;

    gEM.registerEvent!ETest;
    gEM.registerEvent!ETest2;

    gEM.registerSystem!TestSystem2(1);
    gEM.registerSystem!TestSystem(0);

    gEM.endRegister();

    ushort[1] ids = [CLong.component_id];
    EntityTemplate* tmpl = gEM.allocateTemplate(ids);
    scope (exit) gEM.freeTemplate(tmpl);
    ushort[1] ids2 = [CShort.component_id];
    EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2);
    scope (exit) gEM.freeTemplate(tmpl2);

    Entity* entity = gEM.addEntity(tmpl);
    EntityID id = entity.id;
    assert(*entity.getComponent!CLong == 10);
    Entity* entity2 = gEM.addEntity(tmpl2);
    EntityID id2 = entity2.id;
    assert(*entity2.getComponent!CShort == 12);

    gEM.sendEvent(id,ETest());
    gEM.sendEvent(id,ETest2(id,10));
    gEM.sendEvent(id2,ETest());
    gEM.sendEvent(id2,ETest2(id2,12));
    gEM.commit();
    assert(ETest2.destory == 2);

    entity = gEM.getEntity(id);
    entity2 = gEM.getEntity(id2);
    assert(*entity.getComponent!CLong == 46);
    assert(*entity2.getComponent!CShort == 32);

    gEM.addComponents(id, CInt(2), CShort(1));
    gEM.sendEvent(id,ETest());
    gEM.sendEvent(id,ETest2(id,2));
    gEM.commit();
    assert(ETest2.destory == 3);

    entity = gEM.getEntity(id);
    assert(*entity.getComponent!CLong == 66);
    assert(*entity.getComponent!CInt == 36);

    //test for multiple event blocks
    long result = *entity.getComponent!CLong;
    foreach(i;0..10000)
    {
        gEM.sendEvent(id,ETest());
        gEM.sendEvent(id,ETest2(id,4));
        result += 16;
        result += 8;
    }
    gEM.commit();
    assert(ETest2.destory == 10003);
    entity = gEM.getEntity(id);
    assert(*entity.getComponent!CLong == result);

    //cover funcion to clearEvents before destroying manager
    gEM.sendEvent(id,ETest());
}

@("EntitiesFunction")
unittest
{
    struct TestSystem
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    void func1(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += entities.int_[i] / 2;
        }
    }

    void func2(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += 8;
        }
    }

    gEM.beginRegister();

    gEM.registerSystem!TestSystem(1);

    gEM.endRegister();

    EntityTemplate* tmpl = gEM.allocateTemplate([CInt.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl);
    EntityID id1 = gEM.addEntity(tmpl).id;

    EntityTemplate* tmpl2 = gEM.allocateTemplate([CInt.component_id, CLong.component_id].staticArray);
    scope (exit) gEM.freeTemplate(tmpl2);
    EntityID id2 = gEM.addEntity(tmpl2).id;

    gEM.begin();

    Entity* entity1 = gEM.getEntity(id1);
    Entity* entity2 = gEM.getEntity(id2);
    assert(*entity1.getComponent!CInt == 1);
    assert(*entity2.getComponent!CInt == 1);

    gEM.callEntitiesFunction!TestSystem(&func2);
    assert(*entity1.getComponent!CInt == 9);
    assert(*entity2.getComponent!CInt == 9);

    gEM.callEntitiesFunction!TestSystem(&func1);
    assert(*entity1.getComponent!CInt == 13);
    assert(*entity2.getComponent!CInt == 13);

    gEM.end();
}

@("SystemDependencies")
unittest
{
    struct TestSystem
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            @readonly CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem2
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem3
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            @readonly CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem4
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            CInt[] int_;
            CLong[] long_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem5
    {
        mixin ECS.System;

        struct EntitiesData
        {
            uint length;
            @readonly CLong[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    void func1(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += entities.int_[i] / 2;
        }
    }

    void func2(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += 8;
        }
    }

    gEM.beginRegister();

    gEM.registerSystem!TestSystem(0);
    gEM.registerSystem!TestSystem2(1);
    gEM.registerSystem!TestSystem3(2);
    gEM.registerSystem!TestSystem4(3);
    gEM.registerSystem!TestSystem5(4);

    gEM.endRegister();

    const (EntityManager.UpdatePass)* pass = gEM.getPass("update");
    assert(pass != null);
    assert(pass.system_callers.length == 5);
    assert(pass.system_callers[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[1].system_id == TestSystem2.system_id);
    assert(pass.system_callers[2].system_id == TestSystem3.system_id);
    assert(pass.system_callers[3].system_id == TestSystem4.system_id);
    assert(pass.system_callers[4].system_id == TestSystem5.system_id);
    assert(pass.system_callers[0].dependencies.length == 0);
    assert(pass.system_callers[1].dependencies.length == 1);
    assert(pass.system_callers[2].dependencies.length == 1);
    assert(pass.system_callers[3].dependencies.length == 3);
    assert(pass.system_callers[4].dependencies.length == 1);
    assert(pass.system_callers[1].dependencies[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[2].dependencies[0].system_id == TestSystem2.system_id);
    assert(pass.system_callers[3].dependencies[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[3].dependencies[1].system_id == TestSystem2.system_id);
    assert(pass.system_callers[3].dependencies[2].system_id == TestSystem3.system_id);
    assert(pass.system_callers[4].dependencies[0].system_id == TestSystem4.system_id);
}

@("ExternalSystemDependencies")
unittest
{
    enum TestDependency = "TestDepencency";
    
    struct TestSystem
    {
        mixin ECS.System;

        mixin ECS.ReadOnlyDependencies!(TestDependency);

        struct EntitiesData
        {
            uint length;
            @readonly CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem2
    {
        mixin ECS.System;

        mixin ECS.WritableDependencies!(TestDependency);

        struct EntitiesData
        {
            uint length;
            @readonly CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem3
    {
        mixin ECS.System;
        
        mixin ECS.ReadOnlyDependencies!(TestDependency);

        struct EntitiesData
        {
            uint thread_id;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem4
    {
        mixin ECS.System;

        mixin ECS.WritableDependencies!(TestDependency);

        struct EntitiesData
        {
            uint length;
            @readonly CInt[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    struct TestSystem5
    {
        mixin ECS.System;

        mixin ECS.ReadOnlyDependencies!(TestDependency);

        struct EntitiesData
        {
            uint length;
            @readonly CLong[] int_;
        }

        void onUpdate(EntitiesData entities)
        {
            
        }
    }

    void func1(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += entities.int_[i] / 2;
        }
    }

    void func2(TestSystem.EntitiesData entities)
    {
        foreach(i;0 .. entities.length)
        {
            entities.int_[i] += 8;
        }
    }

    gEM.beginRegister();

    gEM.registerDependency(TestDependency);

    gEM.registerSystem!TestSystem(0);
    gEM.registerSystem!TestSystem2(1);
    gEM.registerSystem!TestSystem3(2);
    gEM.registerSystem!TestSystem4(3);
    gEM.registerSystem!TestSystem5(4);

    gEM.endRegister();

    const (EntityManager.UpdatePass)* pass = gEM.getPass("update");
    assert(pass != null);
    assert(pass.system_callers.length == 5);
    assert(pass.system_callers[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[1].system_id == TestSystem2.system_id);
    assert(pass.system_callers[2].system_id == TestSystem3.system_id);
    assert(pass.system_callers[3].system_id == TestSystem4.system_id);
    assert(pass.system_callers[4].system_id == TestSystem5.system_id);
    assert(pass.system_callers[0].dependencies.length == 0);
    assert(pass.system_callers[1].dependencies.length == 1);
    assert(pass.system_callers[2].dependencies.length == 1);
    assert(pass.system_callers[3].dependencies.length == 3);
    assert(pass.system_callers[4].dependencies.length == 2);
    assert(pass.system_callers[1].dependencies[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[2].dependencies[0].system_id == TestSystem2.system_id);
    assert(pass.system_callers[3].dependencies[0].system_id == TestSystem.system_id);
    assert(pass.system_callers[3].dependencies[1].system_id == TestSystem2.system_id);
    assert(pass.system_callers[3].dependencies[2].system_id == TestSystem3.system_id);
    assert(pass.system_callers[4].dependencies[0].system_id == TestSystem2.system_id);
    assert(pass.system_callers[4].dependencies[1].system_id == TestSystem4.system_id);
}