using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace Chernobyl.Reflection.Template
{
    /// <summary>
    /// A helper base testing class for testing types that implement the
    /// <see cref="IInstance"/> interface.
    /// 
    /// Note to implementors: the <see cref="INamed"/> instance created by the 
    /// <see cref="NamedTests.CreateNamed"/> method must be named "test_name"
    /// so that the <see cref="NamedTests.NameIsExpected"/> test method may pass.
    /// </summary>
    public abstract class InstanceTests : ComponentTests
    {

        [Test, Description("Tests the IInstance to see if it can contain an " +
            "IComponent that is a constructor.")]
        public void InstanceWithConstructorTest()
        {
            IInstance instance = CreateInstanceWithConstructor();
            IMember constructor = instance.ComponentChildren.OfType<IMember>().First();
            Assert.IsNotNull(constructor, "A constructor was not created");
        }

        [Test, Description("Tests the IInstance to see if it can contain an " + 
            "IComponent that is a method.")]
        public void InstanceWithMethodTest()
        {
            IInstance instance = CreateInstanceWithMethod();
            IMember method = instance.ComponentChildren.OfType<IMember>().First();
            Assert.IsNotNull(method, "A method was not created");
        }

        [Test, Description("Tests the IInstance to see if it can contain an " +
            "IComponent that is a property.")]
        public void InstanceWithPropertyTest()
        {
            IInstance instance = CreateInstanceWithProperty();
            IMember property = instance.ComponentChildren.OfType<IMember>().First();
            Assert.IsNotNull(property, "A property was not created");
        }

        [Test, Description("Tests the IInstance to see if it can contain an " +
            "IComponent that is a field.")]
        public void InstanceWithFieldTest()
        {
            IInstance instance = CreateInstanceWithField();
            IMember field = instance.ComponentChildren.OfType<IMember>().First();
            Assert.IsNotNull(field, "A property was not created");
        }

        [Test, Description("Tests the IInstance to see if it can contain an " +
            "IComponent that is a event.")]
        public void InstanceWithEventTest()
        {
            IInstance instance = CreateInstanceWithEvent();
            IMember eventMember = instance.ComponentChildren.OfType<IMember>().First();
            Assert.IsNotNull(eventMember, "An eventMember was not created");
        }

        [Test, Description("Tests to see if the instance return by " + 
            "CreateLinkedInstance() is equal to the instance created by " +
            "CreateLinkedToInstance().")]
        public void LinkedInstanceTest()
        {
            IInstance linkedInstance = CreateLinkedInstance();
            IInstance actualLinkedToInstance = CreateLinkedToInstance();

            Assert.AreEqual(actualLinkedToInstance.TheInstance, linkedInstance.TheInstance);
        }

        [Test, Description("Tests to see if the instance return by " +
            "CreateTypedInstance() is an instance of type " + 
            "KeyValuePair<Int32, ArrayList>.")]
        public void TypedInstanceTest()
        {
            IInstance instance = CreateTypedInstance();
            Assert.AreEqual(typeof(Dictionary<Int32, Nullable<Single>>), instance.Type);
        }

        /// <summary>
        /// Creates an <see cref="IInstance"/> that contains an
        /// <see cref="IComponent"/> that has a constructor. This method should 
        /// not return the same <see cref="IInstance"/> more than once.
        /// </summary>
        /// <returns>The IInstance to be tested.</returns>
        protected abstract IInstance CreateInstanceWithConstructor();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that contains an
        /// <see cref="IComponent"/> that has a method. This method should not
        /// return the same <see cref="IInstance"/> more than once.
        /// </summary>
        /// <returns>The IInstance to be tested.</returns>
        protected abstract IInstance CreateInstanceWithMethod();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that contains an
        /// <see cref="IComponent"/> that has a property. This method should not
        /// return the same <see cref="IInstance"/> more than once.
        /// </summary>
        /// <returns>The IInstance to be tested.</returns>
        protected abstract IInstance CreateInstanceWithProperty();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that contains an
        /// <see cref="IComponent"/> that has a field. This method should not
        /// return the same <see cref="IInstance"/> more than once.
        /// </summary>
        /// <returns>The IInstance to be tested.</returns>
        protected abstract IInstance CreateInstanceWithField();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that contains an
        /// <see cref="IComponent"/> that has a event. This method should not
        /// return the same <see cref="IInstance"/> more than once.
        /// </summary>
        /// <returns>The IInstance to be tested.</returns>
        protected abstract IInstance CreateInstanceWithEvent();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that is to be tested to ensure it
        /// properly links to the <see cref="IInstance"/> created by 
        /// <see cref="CreateLinkedToInstance"/>. This method will always create
        /// a new <see cref="IInstance"/> and never return the same instance 
        /// twice. The <see cref="IInstance"/> created should have a name that 
        /// is different from the <see cref="IInstance"/> created by 
        /// <see cref="CreateLinkedToInstance"/>.
        /// </summary>
        /// <returns>The instance that is to be tested.</returns>
        protected abstract IInstance CreateLinkedInstance();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that is to be tested to ensure 
        /// it properly links from the <see cref="IInstance"/> created by 
        /// <see cref="CreateLinkedInstance"/>. This method will always create 
        /// a new <see cref="IInstance"/> and never return the same instance 
        /// twice.
        /// </summary>
        /// <returns>The instance that is to be tested.</returns>
        protected abstract IInstance CreateLinkedToInstance();

        /// <summary>
        /// Creates an <see cref="IInstance"/> that is to be tested to ensure 
        /// it is of type <see cref="Dictionary{TKey,TValue}"/> where the 
        /// key type is of <see cref="int"/> and the value type is of 
        /// <see cref="Nullable{T}"/>. The <see cref="Nullable{T}"/>'s type
        /// parameter should be of type <see cref="Single"/>. This method will 
        /// always create a new <see cref="IInstance"/> and never return the 
        /// same instance twice.
        /// </summary>
        /// <returns>The instance that is to be tested.</returns>
        protected abstract IInstance CreateTypedInstance();

        /// <summary>
        /// The name that is expected by the
        /// <see cref="NamedTests.NameIsExpected()"/> test method. If the value
        /// returned is an empty string, then the test is ignored.
        /// </summary>
        protected override string ExpectedName
        {
            get { return "test_name"; }
        }
    }

    /// <summary>
    /// A type used to test some functionality of <see cref="IInstance"/>s.
    /// </summary>
    public class TestType
    {
        public TestType()
        { }

        public TestType(string y) : this(0.0f, y)
        { }

        public TestType(float x, string y)
        {
            X = x;
            Y = y;
        }

        public event EventHandler SomeEvent;

        public void SomeEventHandler(object sender, EventArgs e)
        {
            X = 0;
        }

        public float X;

        public void SetChild(TestType child)
        {
            Child = child;
        }

        public TestType Child
        {
            get { return ChildField; }
            set { ChildField = value; }
        }

        public TestType ChildField;

        public string Y { get; set; }

        public int SomeProperty { get; set; }

        public void SomeMethod(float x)
        {
            X = x;

            if (SomeEvent != null)
                SomeEvent(this, EventArgs.Empty);
        }
    }
}