using NUnit.Framework;

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

namespace Tests
{
    public class WorldBuilderTest
    {
        public class EngineCycleSimple
        {
            struct AMessage : IMessage { }
            struct BMessage : IMessage { }

            [Receives(typeof(AMessage))]
            [Sends(typeof(BMessage))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    BMessage message;
                    this.SendMessage(message);
                }
            }

            [Receives(typeof(BMessage))]
            [Sends(typeof(AMessage))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    AMessage message;
                    this.SendMessage(message);
                }
            }

            [Test]
            public void EngineCycle()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());

                Assert.Throws<EngineCycleException>(() => worldBuilder.Build());
            }
        }

        public class EngineCycleComplex
        {
            struct AMessage : IMessage { }
            struct BMessage : IMessage { }
            struct CMessage : IMessage { }
            struct DMessage : IMessage { }

            [Receives(typeof(AMessage))]
            [Sends(typeof(BMessage))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    BMessage message;
                    this.SendMessage(message);
                }
            }

            [Receives(typeof(BMessage))]
            [Sends(typeof(CMessage))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    CMessage message;
                    this.SendMessage(message);
                }
            }

            [Receives(typeof(CMessage))]
            [Sends(typeof(DMessage))]
            class CEngine : Engine
            {
                public override void Update(double dt)
                {
                    DMessage message;
                    this.SendMessage(message);
                }
            }

            [Receives(typeof(DMessage))]
            [Sends(typeof(AMessage))]
            class DEngine : Engine
            {
                public override void Update(double dt)
                {
                    AMessage message;
                    this.SendMessage(message);
                }
            }

            [Test]
            public void EngineCycle()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());
                worldBuilder.AddEngine(new CEngine());
                worldBuilder.AddEngine(new DEngine());

                Assert.Throws<EngineCycleException>(() => worldBuilder.Build());
            }
        }

        public class MultipleEngineWriteConflict
        {
            struct AComponent : IComponent { }

            [Writes(typeof(AComponent))]
            class AEngine : Engine
            {
                public override void Update(double dt) { }
            }

            [Writes(typeof(AComponent))]
            class BEngine : Engine
            {
                public override void Update(double dt) { }
            }

            [Test]
            public void EngineWriteConflictException()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());

                Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
            }
        }

        public class MultipleEngineWriteWithPriority
        {
            struct SetMessage : IMessage
            {
                public Entity entity;
            }

            struct AComponent : IComponent
            {
                public int myInt;
            }

            [Receives(typeof(SetMessage))]
            [Writes(typeof(AComponent), 0)]
            [WritesPending(typeof(AComponent))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var setMessage in ReadMessages<SetMessage>())
                    {
                        SetComponent(setMessage.entity, new AComponent { myInt = 0 });
                    }
                }
            }

            [Receives(typeof(SetMessage))]
            [Writes(typeof(AComponent), 1)]
            [WritesPending(typeof(AComponent))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var setMessage in ReadMessages<SetMessage>())
                    {
                        SetComponent(setMessage.entity, new AComponent { myInt = 1 });
                    }
                }
            }

            static AComponent resultComponent;

            [ReadsPending(typeof(AComponent))]
            class ReadComponentEngine : Engine
            {
                public override void Update(double dt)
                {
                    resultComponent = ReadComponent<AComponent>();
                }
            }

            [Test]
            public void LowerPriorityWrites()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SendMessage(new SetMessage { entity = entity });

                var world = worldBuilder.Build();

                world.Update(0.01);

                Assert.That(resultComponent.myInt, Is.EqualTo(0));
            }
        }

        public class DefaultWritePriority
        {
            struct SetMessage : IMessage
            {
                public Entity entity;
            }

            struct AComponent : IComponent
            {
                public int myInt;
            }

            [Receives(typeof(SetMessage))]
            [Writes(typeof(AComponent))]
            [WritesPending(typeof(AComponent))]
            [Encompass.DefaultWritePriority(4)]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var setMessage in ReadMessages<SetMessage>())
                    {
                        SetComponent(setMessage.entity, new AComponent { myInt = 5 });
                    }
                }
            }


            [Receives(typeof(SetMessage))]
            [Writes(typeof(AComponent), 3)]
            [WritesPending(typeof(AComponent))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var setMessage in ReadMessages<SetMessage>())
                    {
                        SetComponent(setMessage.entity, new AComponent { myInt = 1 });
                    }
                }
            }

            [Receives(typeof(SetMessage))]
            [Writes(typeof(AComponent), 2)]
            [WritesPending(typeof(AComponent))]
            class CEngine : Engine
            {
                public override void Update(double dt)
                {
                    foreach (var setMessage in ReadMessages<SetMessage>())
                    {
                        SetComponent(setMessage.entity, new AComponent { myInt = 3 });
                    }
                }
            }

            static AComponent resultComponent;

            [ReadsPending(typeof(AComponent))]
            class ReadComponentEngine : Engine
            {
                public override void Update(double dt)
                {
                    resultComponent = ReadComponent<AComponent>();
                }
            }

            [Test]
            public void LowerPriorityWrites()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());
                worldBuilder.AddEngine(new CEngine());
                worldBuilder.AddEngine(new ReadComponentEngine());

                var entity = worldBuilder.CreateEntity();
                worldBuilder.SendMessage(new SetMessage { entity = entity });

                var world = worldBuilder.Build();

                world.Update(0.01);

                Assert.That(resultComponent.myInt, Is.EqualTo(3));
            }
        }

        public class EngineMessageSelfCycle
        {
            struct AMessage : IMessage { }

            [Receives(typeof(AMessage))]
            [Sends(typeof(AMessage))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

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

                Assert.Throws<EngineSelfCycleException>(() => worldBuilder.AddEngine(new AEngine()), "Engine both sends and receives Message AMessage");
            }
        }

        public class IllegalReadType
        {
            struct ANonMessage { }

            [Reads(typeof(ANonMessage))]
            class MyEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

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

                Assert.Throws<IllegalReadTypeException>(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component");
            }
        }

        public class IllegalWriteType
        {
            struct ANonMessage { }

            [Sends(typeof(ANonMessage))]
            class MyEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

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

                Assert.Throws<IllegalSendTypeException>(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component");
            }
        }

        public class PriorityConflict
        {
            [Writes(typeof(MockComponent), 2)]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

            [Writes(typeof(MockComponent), 2)]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

            [Test]
            public void PriorityConflictTest()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());

                Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
            }
        }

        public class EngineWriteConflict
        {
            [Writes(typeof(MockComponent))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

            [Writes(typeof(MockComponent), 2)]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {

                }
            }

            [Test]
            public void EngineWriteConflictPriorityAndNoPriorityTest()
            {
                var worldBuilder = new WorldBuilder();
                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());

                Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
            }
        }

        public class LegalEngines
        {
            static List<Engine> order = new List<Engine>();

            struct AComponent : IComponent { }
            struct BComponent : IComponent { }

            struct AMessage : IMessage { }
            struct BMessage : IMessage { }
            struct CMessage : IMessage { }
            struct DMessage : IMessage { }

            [Sends(typeof(AMessage))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

            [Sends(typeof(BMessage))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

            [Receives(typeof(AMessage), typeof(BMessage))]
            [Sends(typeof(DMessage))]
            class CEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

            [Receives(typeof(DMessage))]
            class DEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

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

                worldBuilder.AddEngine(new AEngine());
                worldBuilder.AddEngine(new BEngine());
                worldBuilder.AddEngine(new CEngine());
                worldBuilder.AddEngine(new DEngine());

                Assert.DoesNotThrow(() => worldBuilder.Build());

                worldBuilder = new WorldBuilder();

                var engineA = worldBuilder.AddEngine(new AEngine());
                var engineB = worldBuilder.AddEngine(new BEngine());
                var engineC = worldBuilder.AddEngine(new CEngine());
                var engineD = worldBuilder.AddEngine(new DEngine());

                var world = worldBuilder.Build();

                world.Update(0.01f);

                Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineC)));
                Assert.That(order.IndexOf(engineB), Is.LessThan(order.IndexOf(engineC)));
                Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD)));
            }

            static List<AMessage> resultMessages = new List<AMessage>();

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

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

                worldBuilder.SendMessage(new AMessage { }, 0.5);

                var world = worldBuilder.Build();

                resultMessages.Should().BeEmpty();

                world.Update(0.25);

                resultMessages.Should().BeEmpty();

                world.Update(0.25);

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

        public class MultipleMessagesBetweenEngines
        {
            static List<Engine> order = new List<Engine>();

            struct AMessage : IMessage { }
            struct BMessage : IMessage { }

            [Sends(typeof(AMessage), typeof(BMessage))]
            class AEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

            [Receives(typeof(AMessage), typeof(BMessage))]
            class BEngine : Engine
            {
                public override void Update(double dt)
                {
                    order.Add(this);
                }
            }

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

                var engineA = worldBuilder.AddEngine(new AEngine());
                var engineB = worldBuilder.AddEngine(new BEngine());

                Assert.DoesNotThrow(() => worldBuilder.Build());

                var world = worldBuilder.Build();

                world.Update(0.01f);

                Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineB)));
            }
        }
    }
}