using NUnit.Framework;
using Stylet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StyletUnitTests
{
    [TestFixture]
    public class EventAggregatorTests
    {
        public class M1 { }
        public class M2 : M1 { }

        public class C1 : IHandle<M1>
        {
            public M1 ReceivedMessage;
            public int ReceivedMessageCount;
            public void Handle(M1 message) { this.ReceivedMessage = message; this.ReceivedMessageCount++; }
        }

        public class C2 : IHandle<M2>, IHandle<M1>
        {
            public M2 ReceivedM2;
            public M1 ReceivedM1;
            public void Handle(M2 message) { this.ReceivedM2 = message; }
            public void Handle(M1 message) { this.ReceivedM1 = message; }
        }

        public class C3 : IHandle<M1>
        {
            public void Handle(M1 message) { throw new Exception("Should not be called. Ever"); }
        }

        private EventAggregator ea;

        [SetUp]
        public void SetUp()
        {
            this.ea = new EventAggregator();
        }

        [Test]
        public void SubscribesAndDeliversExactMessage()
        {
            var target = new C1();
            this.ea.Subscribe(target);

            var message = new M1();
            this.ea.Publish(message);

            Assert.AreEqual(message, target.ReceivedMessage);
        }

        [Test]
        public void DeliversToAllHandlersIncludingDerived()
        {
            var target = new C2();
            this.ea.Subscribe(target);

            var message = new M2();
            this.ea.Publish(message);

            Assert.AreEqual(message, target.ReceivedM1);
            Assert.AreEqual(message, target.ReceivedM2);
        }

        [Test]
        public void UnsubscribeUnsubscribes()
        {
            var target = new C1();
            this.ea.Subscribe(target);
            this.ea.Unsubscribe(target);

            var message = new M1();
            this.ea.Publish(message);

            Assert.IsNull(target.ReceivedMessage);
        }

        [Test]
        public void TargetReferenceIsWeak()
        {
            var target = new C3();
            var weaktarget = new WeakReference(target);
            this.ea.Subscribe(target);

            // Ugly, but it's the only way to test a WeakReference...
            target = null;
            GC.Collect();

            Assert.DoesNotThrow(() => this.ea.Publish(new M1()));
            Assert.IsNull(weaktarget.Target);
        }

        [Test]
        public void SubscribingTwiceDoesNothing()
        {
            var target = new C1();
            this.ea.Subscribe(target);
            this.ea.Subscribe(target);

            var message = new M1();
            this.ea.Publish(message);

            Assert.AreEqual(1, target.ReceivedMessageCount);
        }

        [Test]
        public void PublishOnUIThreadPublishedOnUIThread()
        {
            var target = new C1();
            this.ea.Subscribe(target);

            var message = new M1();
            this.ea.PublishOnUIThread(message);

            Assert.AreEqual(message, target.ReceivedMessage);
        }

        [Test]
        public void TargetReceivesMessagesOnTopicsItIsSubscribedTo()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1", "C2");

            var message = new M1();
            this.ea.Publish(message, "C1");
            this.ea.Publish(message, "C2");

            Assert.AreEqual(2, target.ReceivedMessageCount);
        }

        [Test]
        public void TargetDoesNotReceiveMessagesOnTopicsItIsNotSubscribedTo()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1");

            var message = new M1();
            this.ea.Publish(message, "C2");

            Assert.AreEqual(0, target.ReceivedMessageCount);
        }

        [Test]
        public void SubscribesToDefaultChannelByDefault()
        {
            var target = new C1();
            this.ea.Subscribe(target);

            var message = new M1();
            this.ea.Publish(message, EventAggregator.DefaultChannel);

            Assert.AreEqual(message, target.ReceivedMessage);
        }

        [Test]
        public void DoesNotSubscribeToDefaultChannelIfAChannelIsGiven()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1");

            var message = new M1();
            this.ea.Publish(message);

            Assert.AreEqual(0, target.ReceivedMessageCount);
        }

        [Test]
        public void DoesNotPublishToDefaultChannelIfAChannelIsGiven()
        {
            var target = new C1();
            this.ea.Subscribe(target);

            var message = new M1();
            this.ea.Publish(message, "C1");

            Assert.AreEqual(0, target.ReceivedMessageCount);
        }

        [Test]
        public void AdditionalChannelsCanBeSubscribedTo()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1");
            this.ea.Subscribe(target, "C2");

            var message = new M1();
            this.ea.Publish(message, "C1");
            this.ea.Publish(message, "C2");

            Assert.AreEqual(2, target.ReceivedMessageCount);
        }

        [Test]
        public void IndividualChannelsCanBeUnsubscribedFrom()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1", "C2");
            this.ea.Unsubscribe(target, "C1");

            var message = new M1();
            this.ea.Publish(message, "C1");
            Assert.AreEqual(0, target.ReceivedMessageCount);

            this.ea.Publish(message, "C2");
            Assert.AreEqual(1, target.ReceivedMessageCount);
        }

        [Test]
        public void UnsubscribeUnsubscribesFromEverythingIfNoChannelsGiven()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1", "C2");
            this.ea.Unsubscribe(target);

            var message = new M1();
            this.ea.Publish(message, "C1", "C2");

            Assert.AreEqual(0, target.ReceivedMessageCount);
        }

        [Test]
        public void MessagePublishedToMultipleChannelsGetsDeliveredOnce()
        {
            var target = new C1();
            this.ea.Subscribe(target, "C1", "C2");

            var message = new M1();
            this.ea.Publish(message, "C1", "C2");

            Assert.AreEqual(1, target.ReceivedMessageCount);
        }
    }
}
