using NUnit.Framework;
using FluentAssertions;

using Encompass;

using System;
using System.Linq;
using System.Collections.Generic;

namespace Tests
{
    public class EngineTest
    {
        static List<KeyValuePair<Guid, MockComponent>> resultComponents;
        static MockComponent resultComponent;

        static List<MockMessage> resultMessages;

        public class ReadComponentsTestEngine : Engine
        {
            public override void Update(float dt)
            {
                resultComponents = this.ReadComponents<MockComponent>().ToList();
            }
        }

        public class ReadComponentTestEngine : Engine
        {
            public override void Update(float dt)
            {
                resultComponent = this.ReadComponent<MockComponent>().Value;
            }
        }

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

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 0;
            mockComponent.myString = "hello";

            MockComponent mockComponentB;
            mockComponentB.myInt = 1;
            mockComponentB.myString = "howdy";

            var componentAID = entity.AddComponent(mockComponent);
            var componentBID = entity.AddComponent(mockComponentB);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            var resultComponentValues = resultComponents.Select((kv) => kv.Value);
            resultComponentValues.Should().Contain(mockComponent);
            resultComponentValues.Should().Contain(mockComponentB);
        }

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

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 0;
            mockComponent.myString = "hello";

            entity.AddComponent(mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            Assert.AreEqual(mockComponent, resultComponent);
        }

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

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 0;
            mockComponent.myString = "hello";

            MockComponent mockComponentB;
            mockComponentB.myInt = 1;
            mockComponentB.myString = "howdy";

            entity.AddComponent(mockComponent);
            entity.AddComponent(mockComponentB);

            var world = worldBuilder.Build();

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

        [Mutates(typeof(MockComponent))]
        public class UpdateComponentTestEngine : Engine
        {
            public override void Update(float dt)
            {
                (var componentID, var component) = this.ReadComponent<MockComponent>();

                component.myInt = 420;
                component.myString = "blaze it";
                this.UpdateComponent(componentID, component);

                resultComponent = this.ReadComponent<MockComponent>().Value;
            }
        }

        [Test]
        public void UpdateComponent()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine<UpdateComponentTestEngine>();

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 0;
            mockComponent.myString = "hello";

            entity.AddComponent(mockComponent);

            var world = worldBuilder.Build();

            world.Update(0.01f);

            Assert.AreEqual(420, resultComponent.myInt);
            Assert.AreEqual("blaze it", resultComponent.myString);
        }

        public class UndeclaredUpdateComponentTestEngine : Engine
        {
            public override void Update(float dt)
            {
                (var componentID, var component) = this.ReadComponent<MockComponent>();

                component.myInt = 420;
                component.myString = "blaze it";
                this.UpdateComponent(componentID, component);

                component = this.ReadComponent<MockComponent>().Value;
            }
        }

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

            var entity = worldBuilder.CreateEntity();

            MockComponent mockComponent;
            mockComponent.myInt = 0;
            mockComponent.myString = "hello";

            entity.AddComponent(mockComponent);

            var world = worldBuilder.Build();

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

        struct MockMessage : IMessage
        {
            public string myString;
        }

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

                this.EmitMessage(message);
            }
        }

        [Reads(typeof(MockMessage))]
        public class MessageReadEngine : Engine
        {
            public override void Update(float dt)
            {
                resultMessages = this.ReadMessages<MockMessage>().ToList();
            }
        }

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

            var world = worldBuilder.Build();

            world.Update(0.01f);

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

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

                this.EmitMessage(message);
            }
        }

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

            var world = worldBuilder.Build();

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

        static bool someTest;

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

                this.EmitMessage(message);
            }
        }

        [Reads(typeof(MockMessage))]
        class SomeTestEngine : Engine
        {
            public override void Update(float dt)
            {
                someTest = this.Some<MockMessage>();
            }
        }

        [Test]
        public void Some()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine<EmitMockMessageEngine>();
            worldBuilder.AddEngine<SomeTestEngine>();

            var world = worldBuilder.Build();

            world.Update(0.01f);

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

        class UndeclaredSomeEngine : Engine
        {
            public override void Update(float dt)
            {
                someTest = this.Some<MockMessage>();
            }
        }

        [Test]
        public void IllegalSome()
        {
            var worldBuilder = new WorldBuilder();
            worldBuilder.AddEngine<EmitMockMessageEngine>();
            worldBuilder.AddEngine<UndeclaredSomeEngine>();

            var world = worldBuilder.Build();

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