using System;
using System.Collections.Generic;
using System.Reflection;
using Chernobyl.Event;
using NUnit.Framework;

namespace Chernobyl.Creation
{
    /// <summary>
    /// Tests for implementations of the <see cref="IFactory{T}"/> type where
    /// type parameter <typeparamref name="T"/> is the type of the instances
    /// generated by the <see cref="IFactory{T}"/> being tested.
    /// </summary>
    /// <typeparam name="T">The type of instances generated by the
    /// <see cref="IFactory{T}"/> being tested.</typeparam>
    public abstract class FactoryTests<T>
    {
        [Test, Description("A test that ensures the " +
            "IFactory<T>.Create(CreationCompletedEventHandler<T>) method " +
            "creates the instance properly.")]
        public void CreateSuccessTest()
        {
            IFactory<T> factory = CreateSuccessfulFactory();
            var result = factory.Create();

            Assert.True(GetComparer().Compare(GetProperlyCreatedItem(), result) == 0,
                "The Factory<T> did not create the item that was expected.");
        }

        [Test, Description("A test that ensures the " +
            "IFactory<T>.Create(CreationCompletedEventHandler<T>) method sets " +
            "the CreationEventArgs<T>.Error properly and throws a " +
            "TargetInvocationException when it has failed to create the instance.")]
        public void CreateExceptionTest()
        {
            IFactory<T> factory = CreateFailureFactory();
            if (factory == null)
                Assert.Ignore("This test cannot be run because the IFactory<T> " +
                    "being tested cannot fail.");
            else
            {
                Assert.Throws<Exception>(() => factory.Create());
            }
        }

        [Test, Description("A test to ensure the " +
            "IFactory<T>.Created event is raised and it's " +
            "CreationEventArgs<T>.Error is correct when the " +
            "IFactory<T>.Create(CreationCompletedEventHandler<T>) method " +
            "is invoked.")]
        public void CreatedEventFailureTest()
        {
            IFactory<T> factory = CreateFailureFactory();
            if (factory == null)
                Assert.Ignore("This test cannot be run because the IFactory<T> " +
                              "being tested cannot fail.");
            else
            {
                factory.Created += (sender, e) =>
                {
                    Assert.AreNotEqual(null, e.Error,
                                        "The CreationEventArgs<T>.Error " +
                                        "was not equal to the value expected.");
                    Assert.Throws<TargetInvocationException>(
                        () => Console.WriteLine("EventFactoryTests.CreatedEventFailureTest: " +
                                      e.CreatedInstance));
                    Assert.Pass("EventFactory<T>.Created event was raised.");
                };

                factory.Create();

                // We should not get to this point.
                Assert.Fail("EventFactory<T>.Created event was not raised.");
            }
        }

        /// <summary>
        /// Generates an <see cref="IFactory{T}"/> instance that can successfully
        /// generate instances of type <typeparamref name="T"/>.
        /// </summary>
        /// <returns>The <see cref="IFactory{T}"/> type to test.</returns>
        protected abstract IFactory<T> CreateSuccessfulFactory();

        /// <summary>
        /// Generates an <see cref="IFactory{T}"/> instance that fails to
        /// generate instances of type <typeparamref name="T"/> or returns null
        /// if the <see cref="IFactory{T}"/> does not fail to create instances.
        /// </summary>
        /// <returns>The <see cref="IFactory{T}"/> type to test or null if
        /// <see cref="IFactory{T}"/> that can fail cannot be generated.</returns>
        protected abstract IFactory<T> CreateFailureFactory();

        /// <summary>
        /// Returns an instance of type <typeparamref name="T"/> that is
        /// "properly created" for the <see cref="IFactory{T}"/> being tested.
        /// This instance will be compared against for some of the tests.
        /// </summary>
        /// <returns>The instance to compare the creation results of the
        /// <see cref="IFactory{T}"/> against.</returns>
        protected abstract T GetProperlyCreatedItem();

        /// <summary>
        /// The <see cref="IComparer{T}"/> used to compare two
        /// <typeparamref name="T"/> types. By default,
        /// <see cref="Comparer{T}.Default"/> is returned.
        /// </summary>
        /// <returns>The <see cref="IComparer{T}"/> to use when making
        /// comparisons.</returns>
        protected virtual IComparer<T> GetComparer()
        {
            return Comparer<T>.Default;
        }
    }
}