using NUnit.Framework;
using FluentAssertions;

using Encompass;

using System;
using System.Linq;
using System.Collections.Generic;
using Encompass.Exceptions;

namespace Tests
{
    struct MockComponent : IComponent
    {
        public int myInt;
    }

    public class EngineTest
    {
        static MockComponent[] resultComponents;
        static MockComponent resultComponent;

        static MockMessage[] resultMessages;

        [Reads(typeof(MockComponent))]
        public class ReadComponentsTestEngine : Engine
        {
            public override void Update(double dt)
            {
                resultComponents = ReadComponents<MockComponent>().ToArray();
            }
        }

        static List<(MockComponent, Entity)> resultComponentsIncludingEntity = new List<(MockComponent, Entity)>();
        static (MockComponent, Entity) resultComponentIncludingEntity;

        [Reads(typeof(MockComponent))]
        public class ReadComponentsIncludingEntityEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    ref readonly var mockComponent = ref GetComponent<MockComponent>(entity);
                    resultComponentsIncludingEntity.Add((mockComponent, entity));
                }
            }
        }

        [Reads(typeof(MockComponent))]
        public class ReadComponentTestEngine : Engine
        {
            public override void Update(double dt)
            {
                resultComponent = ReadComponent<MockComponent>();
            }
        }

        [Reads(typeof(MockComponent))]
        public class ReadComponentIncludingEntityEngine : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var entity = ref ReadEntity<MockComponent>();
                ref readonly var mockComponent = ref GetComponent<MockComponent>(entity);
                resultComponentIncludingEntity = (mockComponent, entity);
            }
        }

        [Test]
        public void ReadComponents()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentsTestEngine());

            var entity = worldBuilder.CreateEntity();
            var entityB = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 2;

            MockComponent mockComponentB;
            mockComponentB.myInt = 1;

            worldBuilder.SetComponent(entity, mockComponent);
            worldBuilder.SetComponent(entityB, mockComponentB);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            resultComponents.Should().Contain(mockComponent);
            resultComponents.Should().Contain(mockComponentB);
        }

        [Test]
        public void ReadComponentsIncludingEntity()
        {
            resultComponentsIncludingEntity.Clear();

            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentsIncludingEntityEngine());

            var entity = worldBuilder.CreateEntity();
            var entityB = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 2;

            MockComponent mockComponentB;
            mockComponentB.myInt = 1;

            worldBuilder.SetComponent(entity, mockComponent);
            worldBuilder.SetComponent(entityB, mockComponentB);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            var resultComponentValues = resultComponentsIncludingEntity.Select((kv) => kv.Item1);
            resultComponentValues.Should().Contain(mockComponent);
            resultComponentValues.Should().Contain(mockComponentB);

            var resultEntities = resultComponentsIncludingEntity.Select((kv) => kv.Item2);
            resultEntities.Should().Contain(entity);
            resultEntities.Should().Contain(entityB);

            resultComponentsIncludingEntity.Should().Contain((mockComponent, entity));
            resultComponentsIncludingEntity.Should().Contain((mockComponentB, entityB));
        }

        [Test]
        public void ReadComponent()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentTestEngine());

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 3;

            worldBuilder.SetComponent(entity, mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            Assert.AreEqual(mockComponent, resultComponent);
        }

        [Test]
        public void ReadComponentWhenMultipleComponents()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentTestEngine());

            var entity = worldBuilder.CreateEntity();
            var entityB = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 2;

            MockComponent mockComponentB;
            mockComponentB.myInt = 1;

            worldBuilder.SetComponent(entity, mockComponent);
            worldBuilder.SetComponent(entityB, mockComponentB);

            var world = worldBuilder.Build();

            world.Update(0.01);

            Assert.That(resultComponent, Is.EqualTo(mockComponent).Or.EqualTo(mockComponentB));
        }

        [Test]
        public void ReadComponentWithEntity()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentIncludingEntityEngine());

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 2;

            worldBuilder.SetComponent(entity, mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            (mockComponent, entity).Should().BeEquivalentTo(resultComponentIncludingEntity);
        }

        [Reads(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        public class UpdateComponentTestEngine : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var entity = ref ReadEntity<MockComponent>();
                SetComponent(entity, new MockComponent { myInt = 420 });
            }
        }

        // this test needs to be improved...

        [Test]
        public void UpdateComponent()
        {
            var worldBuilder = new WorldBuilder();

            worldBuilder.AddEngine(new UpdateComponentTestEngine());
            worldBuilder.AddEngine(new ReadComponentTestEngine());

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 3;

            worldBuilder.SetComponent(entity, mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01);
            world.Update(0.01);

            Assert.AreEqual(420, resultComponent.myInt);
        }

        [Reads(typeof(MockComponent))]
        public class UndeclaredUpdateComponentTestEngine : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var entity = ref ReadEntity<MockComponent>();
                SetComponent(entity, new MockComponent { myInt = 420 });
            }
        }

        [Test]
        public void UpdateUndeclaredComponent()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new UndeclaredUpdateComponentTestEngine());

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 3;

            worldBuilder.SetComponent(entity, mockComponent);

            var world = worldBuilder.Build();

            var ex = Assert.Throws<IllegalWriteException>(() => world.Update(0.01f));
            Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent"));
        }

        struct MockMessage : IMessage
        {
            public string myString;
        }

        [Sends(typeof(MockMessage))]
        public class MessageEmitEngine : Engine
        {
            public override void Update(double dt)
            {
                MockMessage message;
                message.myString = "howdy";

                this.SendMessage(message);
            }
        }

        [Receives(typeof(MockMessage))]
        public class MessageReadEngine : Engine
        {
            public override void Update(double dt)
            {
                resultMessages = ReadMessages<MockMessage>().ToArray();
            }
        }

        [Test]
        public void EmitAndReadMessage()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new MessageEmitEngine());
            worldBuilder.AddEngine(new MessageReadEngine());

            var world = worldBuilder.Build();

            world.Update(0.01f);

            Assert.AreEqual(resultMessages.First().myString, "howdy");
        }

        public class UndeclaredMessageEmitEngine : Engine
        {
            public override void Update(double dt)
            {
                MockMessage message;
                message.myString = "howdy";

                this.SendMessage(message);
            }
        }

        static MockMessage[] emptyReadMessagesResult;

        [Receives(typeof(MockMessage))]
        class ReadMessagesWhenNoneExistEngine : Engine
        {
            public override void Update(double dt)
            {
                emptyReadMessagesResult = ReadMessages<MockMessage>().ToArray();
            }
        }

        [Test]
        public void ReadMessagesWhenNoneHaveBeenEmitted()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadMessagesWhenNoneExistEngine());

            var world = worldBuilder.Build();

            world.Update(0.01f);

            emptyReadMessagesResult.Should().BeEmpty();
        }

        [Test]
        public void EmitUndeclaredMessage()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new UndeclaredMessageEmitEngine());

            var world = worldBuilder.Build();

            var ex = Assert.Throws<IllegalSendException>(() => world.Update(0.01f));
            Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage"));
        }

        static bool someTest;

        [Sends(typeof(MockMessage))]
        class EmitMockMessageEngine : Engine
        {
            public override void Update(double dt)
            {
                MockMessage message;
                message.myString = "howdy";

                this.SendMessage(message);
            }
        }

        [Receives(typeof(MockMessage))]
        class SomeMessageTestEngine : Engine
        {
            public override void Update(double dt)
            {
                someTest = this.SomeMessage<MockMessage>();
            }
        }

        [Test]
        public void SomeMessage()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new EmitMockMessageEngine());
            worldBuilder.AddEngine(new SomeMessageTestEngine());

            var world = worldBuilder.Build();

            world.Update(0.01f);

            Assert.That(someTest, Is.True);
        }

        class UndeclaredSomeMessageEngine : Engine
        {
            public override void Update(double dt)
            {
                someTest = this.SomeMessage<MockMessage>();
            }
        }

        [Test]
        public void UndeclaredSomeMessage()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new EmitMockMessageEngine());
            worldBuilder.AddEngine(new UndeclaredSomeMessageEngine());

            var world = worldBuilder.Build();

            Assert.Throws<IllegalReadException>(() => world.Update(0.01f));
        }

        struct EntityMessage : IMessage, IHasEntity
        {
            public EntityMessage(Entity entity, int myInt)
            {
                Entity = entity;
                MyInt = myInt;
            }

            public Entity Entity { get; }
            public int MyInt { get; }
        }

        [Sends(typeof(EntityMessage), typeof(MockMessage))]
        class EntityMessageEmitterEngine : Engine
        {
            private Entity _entity;

            public EntityMessageEmitterEngine(Entity entity)
            {
                _entity = entity;
            }

            public override void Update(double dt)
            {
                SendMessage(new EntityMessage(_entity, 2));
                SendMessage(new EntityMessage(_entity, 4));
                SendMessage(new EntityMessage(_entity, 5));
                SendMessage(new MockMessage());
            }
        }

        static List<EntityMessage> entityMessageResults;

        [Receives(typeof(EntityMessage))]
        class EntityMessageReceiverEngine : Engine
        {
            private Entity _entity;

            public EntityMessageReceiverEngine(Entity entity)
            {
                _entity = entity;
            }

            public override void Update(double dt)
            {
                entityMessageResults = ReadMessagesWithEntity<EntityMessage>(_entity).ToList();
            }
        }

        [Test]
        public void MessagesWithEntity()
        {
            var worldBuilder = new WorldBuilder();

            var entity = worldBuilder.CreateEntity();
            worldBuilder.AddEngine(new EntityMessageEmitterEngine(entity));
            worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity));

            var world = worldBuilder.Build();

            world.Update(0.01);

            entityMessageResults.Should().HaveCount(3);
            entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 2));
            entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 4));
            entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 5));
        }

        [Test]
        public void NoMessagesWithEntity()
        {
            var worldBuilder = new WorldBuilder();

            var entity = worldBuilder.CreateEntity();
            worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity));

            var world = worldBuilder.Build();
            world.Update(0.01);

            entityMessageResults.Should().BeEmpty();
        }

        [Sends(typeof(EntityMessage), typeof(MockMessage))]
        class EntityMessageSingularEmitterEngine : Engine
        {
            private Entity _entity;

            public EntityMessageSingularEmitterEngine(Entity entity)
            {
                _entity = entity;
            }

            public override void Update(double dt)
            {
                SendMessage(new EntityMessage(_entity, 2));
                SendMessage(new MockMessage());
            }
        }

        static EntityMessage entityMessageResult;

        [Receives(typeof(EntityMessage))]
        class SingularMessageWithEntityEngine : Engine
        {
            private Entity _entity;

            public SingularMessageWithEntityEngine(Entity entity)
            {
                _entity = entity;
            }

            public override void Update(double dt)
            {
                entityMessageResult = ReadMessageWithEntity<EntityMessage>(_entity);
            }
        }

        [Test]
        public void MessageWithEntity()
        {
            var worldBuilder = new WorldBuilder();

            var entity = worldBuilder.CreateEntity();

            worldBuilder.AddEngine(new EntityMessageSingularEmitterEngine(entity));
            worldBuilder.AddEngine(new SingularMessageWithEntityEngine(entity));

            var world = worldBuilder.Build();

            world.Update(0.01);

            entityMessageResult.Should().Be(new EntityMessage(entity, 2));
        }

        class SomeComponentTestEngine : Engine
        {
            public override void Update(double dt)
            {
                Assert.IsTrue(SomeComponent<MockComponent>());
            }
        }

        [Test]
        public void SomeComponent()
        {
            var worldBuilder = new WorldBuilder();

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();

            world.Update(0.01);
        }

        static (MockComponent, Entity) pairA;
        static (MockComponent, Entity) pairB;

        [Reads(typeof(MockComponent))]
        class SameValueComponentReadEngine : Engine
        {
            public override void Update(double dt)
            {
                var entities = ReadEntities<MockComponent>();

                pairA = (GetComponent<MockComponent>(entities[0]), entities[0]);
                pairB = (GetComponent<MockComponent>(entities[1]), entities[1]);
            }
        }

        // Tests that components with identical values should be distinguishable by their entities
        [Test]
        public void SameValueComponents()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new SameValueComponentReadEngine());

            MockComponent componentA;
            componentA.myInt = 20;

            MockComponent componentB;
            componentB.myInt = 20;

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, componentA);

            var entityB = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entityB, componentB);

            var world = worldBuilder.Build();
            world.Update(0.01f);

            Assert.That(EngineTest.pairA, Is.Not.EqualTo(EngineTest.pairB));
            Assert.That(EngineTest.pairA.Item1, Is.EqualTo(EngineTest.pairB.Item1));
        }

        [Reads(typeof(MockComponent))]
        class ReadEmptyMockComponentsEngine : Engine
        {
            public override void Update(double dt)
            {
                ReadEntities<MockComponent>().ToArray().Should().BeEmpty();
            }
        }

        [Test]
        public void ReadComponentsOfTypeWhereNoneExist()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadEmptyMockComponentsEngine());

            var world = worldBuilder.Build();
            world.Update(0.01f);
        }

        struct DestroyerComponent : IComponent { }

        [Reads(typeof(DestroyerComponent))]
        class DestroyerEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<DestroyerComponent>())
                {
                    Destroy(entity);
                }
            }
        }

        static List<(MockComponent, Entity)> results = new List<(MockComponent, Entity)>();

        [Reads(typeof(MockComponent))]
        class ReaderEngine : Engine
        {
            public override void Update(double dt)
            {
                results.Clear();

                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    ref readonly var mockComponent = ref GetComponent<MockComponent>(entity);
                    results.Add((mockComponent, entity));
                }
            }
        }

        [Test]
        public void DestroyEntity()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new DestroyerEngine());
            worldBuilder.AddEngine(new ReaderEngine());

            var entity = worldBuilder.CreateEntity();
            var entityB = worldBuilder.CreateEntity();
            var entityC = worldBuilder.CreateEntity();

            DestroyerComponent destroyerComponent;
            MockComponent mockComponent;
            mockComponent.myInt = 2;

            worldBuilder.SetComponent(entity, destroyerComponent);
            worldBuilder.SetComponent(entity, mockComponent);

            worldBuilder.SetComponent(entityB, destroyerComponent);
            worldBuilder.SetComponent(entityB, mockComponent);

            worldBuilder.SetComponent(entityC, mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01);
            world.Update(0.01);

            Assert.That(results, Does.Not.Contain((mockComponent, entity)));
            Assert.That(results, Does.Not.Contain((mockComponent, entityB)));
            Assert.That(results, Does.Contain((mockComponent, entityC)));
        }

        [Receives(typeof(DestroyComponentMessage))]
        class DestroyEntityEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var message in ReadMessages<DestroyComponentMessage>())
                {
                    Destroy(message.entity);
                }
            }
        }

        [Test]
        public void DestroyEntityWithoutID()
        {
            results.Clear();

            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new AddComponentEngine());
            worldBuilder.AddEngine(new DestroyEntityEngine());
            worldBuilder.AddEngine(new ReaderEngine());

            var mockComponent = new MockComponent { };
            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, mockComponent);
            worldBuilder.SendMessage(new DestroyComponentMessage { entity = entity });

            var world = worldBuilder.Build();
            world.Update(0.01);

            Assert.DoesNotThrow(() => world.Update(0.01));
            Assert.That(results, Does.Not.Contain((mockComponent, entity)));
        }

        [Reads(typeof(DestroyerComponent))]
        [Writes(typeof(MockComponent))]
        class DestroyAndAddComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<DestroyerComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                    Destroy(entity);
                }
            }
        }

        [Test]
        public void DestroyEntityWhileRemovingComponent()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new DestroyAndAddComponentEngine());
            worldBuilder.AddEngine(new ReaderEngine());

            var entity = worldBuilder.CreateEntity();

            worldBuilder.SetComponent(entity, new DestroyerComponent());
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();

            Assert.DoesNotThrow(() => world.Update(0.01));
        }

        [Reads(typeof(MockComponent))]
        [WritesImmediate(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        class AddAndRemoveMockComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                    SetComponent(entity, new MockComponent());
                }
            }
        }

        static Entity entityResult;

        [ReadsImmediate(typeof(MockComponent))]
        class GetEntityFromImmediateReadComponents : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var entity = ref ReadEntity<MockComponent>();
            }
        }

        [Test]
        public void GetEntityFromImmediateComponentID()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new AddAndRemoveMockComponentEngine());
            worldBuilder.AddEngine(new GetEntityFromImmediateReadComponents());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();

            Assert.DoesNotThrow(() => world.Update(0.01));
        }

        [Reads(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        class DelayedMessageEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                    SendMessage(new MockMessage { }, 1);
                }
            }
        }

        [Test]
        public void EngineSendMessageDelayed()
        {
            Array.Clear(resultMessages, 0, resultMessages.Length);

            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ActivateTimeDilationEngine());
            worldBuilder.AddEngine(new DelayedMessageEngine());
            worldBuilder.AddEngine(new MessageReadEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var world = worldBuilder.Build();

            world.Update(0.01);

            resultMessages.Should().BeEmpty();

            world.Update(0.5);

            resultMessages.Should().BeEmpty();

            world.Update(0.5);

            resultMessages.Should().BeEmpty();

            world.Update(2);

            resultMessages.Should().NotBeEmpty();
            resultMessages.First().Should().BeOfType<MockMessage>();
        }

        [Reads(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        class DelayedMessageIgnoringTimeDilationEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                    SendMessageIgnoringTimeDilation(new MockMessage { }, 1);
                }
            }
        }

        [Test]
        public void EngineSendMessageDelayedIgnoringTimeDilation()
        {
            Array.Clear(resultMessages, 0, resultMessages.Length);

            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ActivateTimeDilationEngine());
            worldBuilder.AddEngine(new DelayedMessageIgnoringTimeDilationEngine());
            worldBuilder.AddEngine(new MessageReadEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var world = worldBuilder.Build();

            world.Update(0.01);

            resultMessages.Should().BeEmpty();

            world.Update(0.5);

            resultMessages.Should().BeEmpty();

            world.Update(0.5);

            resultMessages.Should().NotBeEmpty();
            resultMessages.First().Should().BeOfType<MockMessage>();
        }

        [Receives(typeof(MockMessage))]
        [WritesImmediate(typeof(MockComponent))]
        [Writes(typeof(MockComponent), 1)]
        class ActivateComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var message in ReadMessages<MockMessage>())
                {
                    var entity = CreateEntity();
                    SetComponent(entity, new MockComponent { });
                }
            }
        }

        [ReadsImmediate(typeof(MockComponent))]
        [Writes(typeof(MockComponent), 0)]
        class RemoveComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (var entity in ReadEntities<MockComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                }
            }
        }

        [Test]
        public void EngineAddAndRemoveComponentSameFrameWithRemovePriority()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ActivateComponentEngine());
            worldBuilder.AddEngine(new RemoveComponentEngine());
            worldBuilder.AddEngine(new ReadComponentsTestEngine());

            worldBuilder.SendMessage(new MockMessage { });

            var world = worldBuilder.Build();

            Assert.DoesNotThrow(() => world.Update(0.01));
            world.Update(0.01); // update again for the read
            resultComponents.Should().BeEmpty();
        }

        struct DestroyComponentMessage : IMessage { public Entity entity; }

        [Reads(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        class AddComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (var entity in ReadEntities<MockComponent>())
                {
                    SetComponent(entity, new MockComponent { });
                }
            }
        }

        static Entity readEntity;

        [Reads(typeof(MockComponent))]
        class ReadEntityByComponentTypeEngine : Engine
        {
            public override void Update(double dt)
            {
                readEntity = ReadEntity<MockComponent>();
            }
        }

        [Test]
        public void GetEntityByComponentType()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadEntityByComponentTypeEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();
            world.Update(0.01);

            entity.Should().BeEquivalentTo(readEntity);
        }

        struct MockComponentB : IComponent
        {
            private int value;

            public MockComponentB(int value)
            {
                this.value = value;
            }
        }

        static MockComponentB getComponentResult;

        [Reads(typeof(MockComponent), typeof(MockComponentB))]
        class GetComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                getComponentResult = GetComponent<MockComponentB>(ReadEntity<MockComponent>());
            }
        }

        [Test]
        public void GetComponent()
        {
            var worldBuilder = new WorldBuilder();

            var entity = worldBuilder.CreateEntity();

            worldBuilder.SetComponent(entity, new MockComponent());
            worldBuilder.SetComponent(entity, new MockComponentB(3));
            worldBuilder.AddEngine(new GetComponentEngine());

            var world = worldBuilder.Build();

            world.Update(0.01);

            getComponentResult.Should().BeEquivalentTo(new MockComponentB(3));
        }

        [Reads(typeof(MockComponent), typeof(MockComponentB))]
        class GetComponentExceptionEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (var entity in ReadEntities<MockComponent>())
                {
                    GetComponent<MockComponentB>(entity);
                }
            }
        }

        [Test]
        public void GetComponentWhenComponentIsNotOnEntity()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new GetComponentExceptionEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();
            Assert.Throws<Encompass.Exceptions.NoComponentOfTypeOnEntityException>(() => world.Update(0.01));
        }

        static Entity[] readEntities;

        [Reads(typeof(MockComponent))]
        class ReadEntitiesWithComponentTypeEngine : Engine
        {
            public override void Update(double dt)
            {
                readEntities = ReadEntities<MockComponent>().ToArray();
            }
        }

        [Test]
        public void ReadEntities()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine());
            worldBuilder.AddEngine(new DestroyAllWithEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var entityB = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entityB, new MockComponent { });

            var world = worldBuilder.Build();
            world.Update(0.01);

            readEntities.Should().Contain(entity);
            readEntities.Should().Contain(entityB);
        }

        [Reads(typeof(MockComponent))]
        class DestroyWithEngine : Engine
        {
            public override void Update(double dt)
            {
                if (SomeComponent<MockComponent>())
                {
                    DestroyWith<MockComponent>();
                }
            }
        }

        [Test]
        public void DestroyWith()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine());
            worldBuilder.AddEngine(new DestroyWithEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var world = worldBuilder.Build();
            world.Update(0.01);
            world.Update(0.01); // update twice so the read happens after destroy

            readEntities.Should().BeEmpty();
        }

        [Reads(typeof(MockComponent))]
        class DestroyAllWithEngine : Engine
        {
            public override void Update(double dt)
            {
                DestroyAllWith<MockComponent>();
            }
        }

        [Test]
        public void DestroyAllWith()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine());
            worldBuilder.AddEngine(new DestroyAllWithEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var entityB = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entityB, new MockComponent { });

            var world = worldBuilder.Build();
            world.Update(0.01);
            world.Update(0.01); // update twice so the read happens after destroy

            readEntities.Should().BeEmpty();
        }

        [Reads(typeof(MockComponent))]
        [Writes(typeof(MockComponent))]
        class RemoveComponentByTypeEngine : Engine
        {
            public override void Update(double dt)
            {
                foreach (ref readonly var entity in ReadEntities<MockComponent>())
                {
                    RemoveComponent<MockComponent>(entity);
                }
            }
        }

        [Test]
        public void RemoveComponentByType()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadComponentsTestEngine());
            worldBuilder.AddEngine(new RemoveComponentByTypeEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var entityB = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent { });

            var world = worldBuilder.Build();

            world.Update(0.01);
            world.Update(0.01);

            resultComponents.Should().BeEmpty();
        }

        static double dilatedDeltaTime;

        class ActivateTimeDilationEngine : Engine
        {
            public override void Update(double dt)
            {
                if (!TimeDilationActive)
                {
                    ActivateTimeDilation(0.2, 1, 1, 1);
                }
                else
                {
                    dilatedDeltaTime = dt;
                }
            }
        }

        [Test]
        public void ActivateTimeDilation()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ActivateTimeDilationEngine());

            var world = worldBuilder.Build();

            world.Update(0.01); // activate time dilation

            world.Update(0.5);

            dilatedDeltaTime.Should().BeApproximately(0.3, 0.01);

            world.Update(0.5);

            dilatedDeltaTime.Should().BeApproximately(0.1, 0.01);

            world.Update(1);

            world.Update(0.5);

            dilatedDeltaTime.Should().BeApproximately(0.3, 0.01);
        }

        class ActivateTimeDilationLowerFactorEngine : Engine
        {
            private bool activated = false;

            public override void Update(double dt)
            {
                if (!activated)
                {
                    ActivateTimeDilation(0.2, 1, 1, 1);
                    activated = true;
                }
            }
        }

        class ActivateTimeDilationHigherFactorEngine : Engine
        {
            private bool activated = false;

            public override void Update(double dt)
            {
                if (!activated)
                {
                    ActivateTimeDilation(0.5, 1, 1, 1);
                    activated = true;
                }
            }
        }

        static bool timeDilationActive;
        class ReadDilatedDeltaTimeEngine : Engine
        {
            public override void Update(double dt)
            {
                dilatedDeltaTime = dt;
                timeDilationActive = TimeDilationActive;
            }
        }

        [Test]
        public void MultipleActivateTimeDilation()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ReadDilatedDeltaTimeEngine());
            worldBuilder.AddEngine(new ActivateTimeDilationLowerFactorEngine());
            worldBuilder.AddEngine(new ActivateTimeDilationHigherFactorEngine());

            var world = worldBuilder.Build();

            world.Update(0.01); // activate time dilation

            world.Update(0.5); // 0.3 and 0.375

            dilatedDeltaTime.Should().BeApproximately(0.3375, 0.01);
            timeDilationActive.Should().BeTrue();

            world.Update(5);

            dilatedDeltaTime.Should().BeApproximately(5, 0.01);
            timeDilationActive.Should().BeFalse();
        }

        static double undilatedDeltaTime;

        [IgnoresTimeDilation]
        class IgnoresTimeDilationEngine : Engine
        {
            public override void Update(double dt)
            {
                undilatedDeltaTime = dt;
            }
        }

        [Test]
        public void IgnoresTimeDilation()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new ActivateTimeDilationEngine());
            worldBuilder.AddEngine(new IgnoresTimeDilationEngine());

            var world = worldBuilder.Build();

            world.Update(0.01); // activate time dilation

            world.Update(0.5);

            undilatedDeltaTime.Should().Be(0.5);
        }

        class AddComponentWithoutPriorityEngine : Engine
        {
            public override void Update(double dt)
            {
                var entity = CreateEntity();
                AddComponent(entity, new MockComponent());

                var entityB = CreateEntity();
                AddComponent(entityB, new MockComponent());
            }
        }

        [Test]
        public void AddComponent()
        {
            var worldBuilder = new WorldBuilder();

            worldBuilder.AddEngine(new AddComponentWithoutPriorityEngine());
            worldBuilder.AddEngine(new ReadComponentsTestEngine());

            var world = worldBuilder.Build();

            world.Update(0.01);
            world.Update(0.01);

            resultComponents.Should().HaveCount(2);

            world.Update(0.01);

            resultComponents.Should().HaveCount(4);
        }

        [Reads(typeof(MockComponent))]
        class AddComponentToPreviouslyExistingEntityEngine : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var entity = ref ReadEntity<MockComponent>();
                AddComponent(entity, new MockComponent());
            }
        }

        [Test]
        public void AddComponentToPreviouslyExistingEntityTest()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine(new AddComponentToPreviouslyExistingEntityEngine());

            var entity = worldBuilder.CreateEntity();
            worldBuilder.SetComponent(entity, new MockComponent());

            var world = worldBuilder.Build();

            Assert.Throws<IllegalWriteException>(() => world.Update(0.01));
        }

        [WritesImmediate(typeof(MockComponentB))]
        class AddImmediateComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                var entity = CreateEntity();
                AddComponent(entity, new MockComponentB(5));
            }
        }

        [ReadsImmediate(typeof(MockComponentB))]
        class ReadImmediateComponentEngine : Engine
        {
            public override void Update(double dt)
            {
                ref readonly var component = ref ReadComponent<MockComponentB>();
                getComponentResult = component;
            }
        }

        [Test]
        public void AddImmediateComponentTest()
        {
            getComponentResult = default(MockComponentB);

            var worldBuilder = new WorldBuilder();

            worldBuilder.AddEngine(new AddImmediateComponentEngine());
            worldBuilder.AddEngine(new ReadImmediateComponentEngine());

            var world = worldBuilder.Build();

            world.Update(0.01);

            getComponentResult.Should().Be(new MockComponentB(5));
        }

        static bool entityExistsResult;

        class EntityExistsEngine : Engine
        {
            private int _id;

            public EntityExistsEngine(int id)
            {
                _id = id;
            }

            public override void Update(double dt)
            {
                entityExistsResult = EntityExists(_id);
            }
        }

        [Test]
        public void PruneEmptyEntities()
        {
            var worldBuilder = new WorldBuilder();
            var entity = worldBuilder.CreateEntity();
            var id = entity.ID;

            var world = worldBuilder.Build();

            world.Update(0.01);

            entityExistsResult.Should().BeFalse();
        }

        public class QueryTests
        {
            struct MockComponentB : IComponent { }
            struct MockComponentC : IComponent { }
            struct MockComponentD : IComponent { }

            [Writes(typeof(MockComponentB))]
            [QueryWith(typeof(MockComponent), typeof(MockComponentB))]
            class EntityQueryWithComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();
                    foreach (var entity in TrackedEntities)
                    {
                        entities.Add(entity);
                        RemoveComponent<MockComponentB>(entity);
                    }
                }
            }

            [Test]
            public void EntitiesWithComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();
                var entityC = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entity, new MockComponent());
                worldBuilder.SetComponent(entity, new MockComponentB());

                worldBuilder.SetComponent(entityB, new MockComponent());
                worldBuilder.SetComponent(entityB, new MockComponentB());

                worldBuilder.SetComponent(entityC, new MockComponentB());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new EntityQueryWithComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.Should().BeEquivalentTo(new Entity[] { entity, entityB });

                world.Update(0.01);

                queriedEntities.Should().BeEmpty();
            }

            [Writes(typeof(MockComponent))]
            [QueryWithout(typeof(MockComponent))]
            class EntityQueryWithoutComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithoutComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();
                    foreach (var entity in TrackedEntities)
                    {
                        entities.Add(entity);
                        SetComponent(entity, new MockComponent());
                    }
                }
            }

            [Test]
            public void EntitiesWithoutComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();
                var entityC = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entity, new MockComponent());
                worldBuilder.SetComponent(entity, new MockComponentB());

                worldBuilder.SetComponent(entityB, new MockComponent());
                worldBuilder.SetComponent(entityB, new MockComponentB());

                worldBuilder.SetComponent(entityC, new MockComponentB());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new EntityQueryWithoutComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityC });

                world.Update(0.01);

                queriedEntities.Should().BeEmpty();
            }

            [QueryWith(typeof(MockComponent), typeof(MockComponentB))]
            [QueryWithout(typeof(MockComponentD))]
            class EntityQueryWithandWithoutComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithandWithoutComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();

                    entities.AddRange(TrackedEntities);
                }
            }

            [Test]
            public void EntitiesWithAndWithoutComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();
                var entityC = worldBuilder.CreateEntity();
                var entityD = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entity, new MockComponent());
                worldBuilder.SetComponent(entity, new MockComponentB());
                worldBuilder.SetComponent(entity, new MockComponentD());

                worldBuilder.SetComponent(entityB, new MockComponent());

                worldBuilder.SetComponent(entityC, new MockComponent());
                worldBuilder.SetComponent(entityC, new MockComponentB());
                worldBuilder.SetComponent(entityC, new MockComponentC());
                worldBuilder.SetComponent(entityC, new MockComponentD());

                worldBuilder.SetComponent(entityD, new MockComponent());
                worldBuilder.SetComponent(entityD, new MockComponentB());
                worldBuilder.SetComponent(entityD, new MockComponentC());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new EntityQueryWithandWithoutComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityD });
            }

            [Reads(typeof(MockComponent))]
            [WritesImmediate(typeof(MockComponentB))]
            [Writes(typeof(MockComponentB), 0)]
            class AddImmediateComponentEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var entity in ReadEntities<MockComponent>())
                    {
                        SetComponent(entity, new MockComponentB());
                    }
                }
            }

            [ReadsImmediate(typeof(MockComponentB))]
            [QueryWith(typeof(MockComponentB))]
            class EntityQueryWithImmediateComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithImmediateComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();
                    entities.AddRange(TrackedEntities);
                }
            }

            [Test]
            public void EntitiesWithImmediateComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entity, new MockComponent());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new AddImmediateComponentEngine());
                worldBuilder.AddEngine(new EntityQueryWithImmediateComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entity });
            }

            [ReadsImmediate(typeof(MockComponentB))]
            [QueryWithout(typeof(MockComponentB))]
            class EntityQueryWithoutImmediateComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithoutImmediateComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();
                    entities.AddRange(TrackedEntities);
                }
            }

            [Test]
            public void EntitiesWithoutImmediateComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entity, new MockComponent());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new AddImmediateComponentEngine());
                worldBuilder.AddEngine(new EntityQueryWithoutImmediateComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB });
            }

            [Reads(typeof(MockComponentC), typeof(MockComponentD))]
            [WritesImmediate(typeof(MockComponent), typeof(MockComponentB))]
            [Writes(typeof(MockComponent), 0)]
            [Writes(typeof(MockComponentB), 0)]
            class ConditionallyAddImmediateComponentsEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var entity in ReadEntities<MockComponentC>())
                    {
                        SetComponent(entity, new MockComponent());
                    }

                    foreach (var entity in ReadEntities<MockComponentD>())
                    {
                        SetComponent(entity, new MockComponent());
                        SetComponent(entity, new MockComponentB());
                    }
                }
            }

            [ReadsImmediate(typeof(MockComponent), typeof(MockComponentB))]
            [QueryWith(typeof(MockComponent))]
            [QueryWithout(typeof(MockComponentB))]
            class EntityQueryWithAndWithoutImmediateComponentsEngine : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithAndWithoutImmediateComponentsEngine(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();

                    entities.AddRange(TrackedEntities);
                }
            }

            [Test]
            public void EntitiesWithAndWithoutImmediateComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();
                var entityC = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entityB, new MockComponentC());
                worldBuilder.SetComponent(entityC, new MockComponentD());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new ConditionallyAddImmediateComponentsEngine());
                worldBuilder.AddEngine(new EntityQueryWithAndWithoutImmediateComponentsEngine(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB });
            }

            [Reads(typeof(MockComponentC))]
            [WritesImmediate(typeof(MockComponentB))]
            [Writes(typeof(MockComponentB), 0)]
            class ConditionallyAddImmediateComponentEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var entity in ReadEntities<MockComponentC>())
                    {
                        SetComponent(entity, new MockComponentB());
                    }
                }
            }

            [ReadsImmediate(typeof(MockComponentB))]
            [QueryWith(typeof(MockComponent), typeof(MockComponentB))]
            class EntityQueryWithImmediateAndNonImmediateComponents : Engine
            {
                private List<Entity> entities;

                public EntityQueryWithImmediateAndNonImmediateComponents(List<Entity> entities)
                {
                    this.entities = entities;
                }

                public override void Update(double dt)
                {
                    entities.Clear();
                    entities.AddRange(TrackedEntities);
                }
            }

            [Test]
            public void EntitiesWithImmediateAndNonImmediateComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                var entityB = worldBuilder.CreateEntity();
                var entityC = worldBuilder.CreateEntity();

                worldBuilder.SetComponent(entityB, new MockComponent());
                worldBuilder.SetComponent(entityB, new MockComponentC());
                worldBuilder.SetComponent(entityC, new MockComponentD());

                var queriedEntities = new List<Entity>();
                worldBuilder.AddEngine(new ConditionallyAddImmediateComponentEngine());
                worldBuilder.AddEngine(new EntityQueryWithImmediateAndNonImmediateComponents(queriedEntities));

                var world = worldBuilder.Build();

                world.Update(0.01);

                queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB });
            }

            [ReadsImmediate(typeof(MockComponentB))]
            class ReadImmediateComponentsEngine : Engine
            {
                private List<MockComponentB> _components;

                public ReadImmediateComponentsEngine(List<MockComponentB> components)
                {
                    _components = components;
                }
                public override void Update(double dt)
                {
                    _components.AddRange(ReadComponents<MockComponentB>().ToArray());
                }
            }

            [Test]
            public void ReadImmediateComponents()
            {
                var worldBuilder = new WorldBuilder();

                var _components = new List<MockComponentB>();

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SetComponent(entity, new MockComponent());

                worldBuilder.AddEngine(new AddImmediateComponentEngine());
                worldBuilder.AddEngine(new ReadImmediateComponentsEngine(_components));

                var world = worldBuilder.Build();
                world.Update(0.01);

                _components.Should().NotBeEmpty();
            }

            [ReadsImmediate(typeof(MockComponentB))]
            [Reads(typeof(MockComponent))]
            class HasAndGetImmediateComponentEngine : Engine
            {
                private List<MockComponentB> _components;

                public HasAndGetImmediateComponentEngine(List<MockComponentB> components)
                {
                    _components = components;
                }

                public override void Update(double dt)
                {
                    foreach (ref readonly var entity in ReadEntities<MockComponent>())
                    {
                        if (HasComponent<MockComponentB>(entity))
                        {
                            _components.Add(GetComponent<MockComponentB>(entity));
                        }
                    }
                }
            }

            [Test]
            public void HasAndGetImmediateComponent()
            {
                var worldBuilder = new WorldBuilder();

                var _components = new List<MockComponentB>();

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SetComponent(entity, new MockComponent());

                worldBuilder.AddEngine(new AddImmediateComponentEngine());
                worldBuilder.AddEngine(new HasAndGetImmediateComponentEngine(_components));

                var world = worldBuilder.Build();
                world.Update(0.01);

                _components.Should().NotBeEmpty();
            }

            struct MockTimerComponent : IComponent
            {
                public double Timer { get; }

                public MockTimerComponent(double time)
                {
                    Timer = time;
                }
            }

            [Reads(typeof(MockTimerComponent))]
            [Writes(typeof(MockTimerComponent))]
            class ReadWhileRemovingComponentsEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (ref readonly var entity in ReadEntities<MockTimerComponent>())
                    {
                        ref readonly var component = ref GetComponent<MockTimerComponent>(entity);

                        if (component.Timer - dt <= 0)
                        {
                            RemoveComponent<MockTimerComponent>(entity);

                        }
                        else
                        {
                            SetComponent<MockTimerComponent>(entity, new MockTimerComponent(component.Timer - dt));
                        }
                    }
                }
            }

            [Test]
            public void ReadWhileRemovingComponents()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SetComponent(entity, new MockTimerComponent(0.5));

                var entityB = worldBuilder.CreateEntity();
                worldBuilder.SetComponent(entityB, new MockTimerComponent(0.4));

                worldBuilder.AddEngine(new ReadWhileRemovingComponentsEngine());

                var world = worldBuilder.Build();
                Assert.DoesNotThrow(() => world.Update(0.2));
                Assert.DoesNotThrow(() => world.Update(0.25));
            }

            [Test]
            public void DestroyedEntitiesAreRemovedFromTracking()
            {
                var worldBuilder = new WorldBuilder();

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SetComponent(entity, new MockComponent());

                worldBuilder.AddEngine(new DestroyWithEngine());
                worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine());

                var world = worldBuilder.Build();

                world.Update(0.01);
                world.Update(0.01);

                readEntities.Should().BeEmpty();
            }
        }
    }
}