using NUnit.Framework;

using Encompass;
using System.Collections.Generic;

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

            [Reads(typeof(AMessage))]
            [Emits(typeof(BMessage))]
            class AEngine : Engine
            {
                public override void Update(float dt)
                {
                    BMessage message;
                    this.EmitMessage(message);
                }
            }

            [Reads(typeof(BMessage))]
            [Emits(typeof(AMessage))]
            class BEngine : Engine
            {
                public override void Update(float dt)
                {
                    AMessage message;
                    this.EmitMessage(message);
                }
            }

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

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

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

            [Reads(typeof(AMessage))]
            [Emits(typeof(BMessage))]
            class AEngine : Engine
            {
                public override void Update(float dt)
                {
                    BMessage message;
                    this.EmitMessage(message);
                }
            }

            [Reads(typeof(BMessage))]
            [Emits(typeof(CMessage))]
            class BEngine : Engine
            {
                public override void Update(float dt)
                {
                    CMessage message;
                    this.EmitMessage(message);
                }
            }

            [Reads(typeof(CMessage))]
            [Emits(typeof(DMessage))]
            class CEngine : Engine
            {
                public override void Update(float dt)
                {
                    DMessage message;
                    this.EmitMessage(message);
                }
            }

            [Reads(typeof(DMessage))]
            [Emits(typeof(AMessage))]
            class DEngine : Engine
            {
                public override void Update(float dt)
                {
                    AMessage message;
                    this.EmitMessage(message);
                }
            }

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

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

        public class MutationConflict
        {
            struct AComponent : IComponent { }

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

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

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

                Assert.Throws<EngineMutationConflictException>(() => 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 { }

            [Mutates(typeof(AComponent))]
            [Emits(typeof(AMessage))]
            class AEngine : Engine
            {
                public override void Update(float dt)
                {
                    order.Add(this);
                }
            }

            [Mutates(typeof(BComponent))]
            [Emits(typeof(BMessage))]
            class BEngine : Engine
            {
                public override void Update(float dt)
                {
                    order.Add(this);
                }
            }

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

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

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

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

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

                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)));
            }
        }
    }
}