// ***********************************************************************
// Copyright (c) 2007-2012 Charlie Poole
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ***********************************************************************

using System;
using System.Collections;
#if CLR_2_0 || CLR_4_0
using System.Collections.Generic;
#endif
using System.Reflection;

namespace NUnit.Framework.Internal
{
	/// <summary>
	/// Helper methods for inspecting a type by reflection. 
	/// 
	/// Many of these methods take ICustomAttributeProvider as an 
	/// argument to avoid duplication, even though certain attributes can 
	/// only appear on specific types of members, like MethodInfo or Type.
	/// 
	/// In the case where a type is being examined for the presence of
	/// an attribute, interface or named member, the Reflect methods
	/// operate with the full name of the member being sought. This
	/// removes the necessity of the caller having a reference to the
	/// assembly that defines the item being sought and allows the
	/// NUnit core to inspect assemblies that reference an older
	/// version of the NUnit framework.
	/// </summary>
	public class Reflect
	{
        private static readonly BindingFlags AllMembers = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy;

        // A zero-length Type array - not provided by System.Type for all CLR versions we support.
        private static readonly Type[] EmptyTypes = new Type[0];

        #region Get Methods of a type

        /// <summary>
        /// Examine a fixture type and return an array of methods having a 
        /// particular attribute. The array is order with base methods first.
        /// </summary>
        /// <param name="fixtureType">The type to examine</param>
        /// <param name="attributeType">The attribute Type to look for</param>
        /// <param name="inherit">Specifies whether to search the fixture type inheritance chain</param>
        /// <returns>The array of methods found</returns>
        public static MethodInfo[] GetMethodsWithAttribute(Type fixtureType, Type attributeType, bool inherit)
        {
            MethodInfoList list = new MethodInfoList();

            foreach (MethodInfo method in fixtureType.GetMethods(AllMembers))
            {
                if (method.IsDefined(attributeType, inherit))
                    list.Add(method);
            }

            list.Sort(new BaseTypesFirstComparer());

            return list.ToArray();
        }

#if CLR_2_0 || CLR_4_0
        private class BaseTypesFirstComparer : IComparer<MethodInfo>
        {
            public int Compare(MethodInfo m1, MethodInfo m2)
            {
                if (m1 == null || m2 == null) return 0;

                Type m1Type = m1.DeclaringType;
                Type m2Type = m2.DeclaringType;

                if ( m1Type == m2Type ) return 0;
                if ( m1Type.IsAssignableFrom(m2Type) ) return -1;

                return 1;
            }
        }
#else
        private class BaseTypesFirstComparer : IComparer
        {
            public int Compare(object x, object y)
            {
                MethodInfo m1 = x as MethodInfo;
                MethodInfo m2 = y as MethodInfo;

                if (m1 == null || m2 == null) return 0;

                Type m1Type = m1.DeclaringType;
                Type m2Type = m2.DeclaringType;

                if (m1Type == m2Type) return 0;
                if (m1Type.IsAssignableFrom(m2Type)) return -1;

                return 1;
            }
        }
#endif

        /// <summary>
        /// Examine a fixture type and return true if it has a method with
        /// a particular attribute. 
        /// </summary>
        /// <param name="fixtureType">The type to examine</param>
        /// <param name="attributeType">The attribute Type to look for</param>
        /// <param name="inherit">Specifies whether to search the fixture type inheritance chain</param>
        /// <returns>True if found, otherwise false</returns>
        public static bool HasMethodWithAttribute(Type fixtureType, Type attributeType, bool inherit)
        {
            foreach (MethodInfo method in fixtureType.GetMethods(AllMembers))
            {
                if (method.IsDefined(attributeType, inherit))
                    return true;
            }

            return false;
        }

        #endregion

        #region Invoke Constructors

        /// <summary>
        /// Invoke the default constructor on a Type
        /// </summary>
        /// <param name="type">The Type to be constructed</param>
        /// <returns>An instance of the Type</returns>
        public static object Construct(Type type)
        {
            ConstructorInfo ctor = type.GetConstructor(EmptyTypes);
            if (ctor == null)
                throw new InvalidTestFixtureException(type.FullName + " does not have a default constructor");

            return ctor.Invoke(null);
        }

        /// <summary>
        /// Invoke a constructor on a Type with arguments
        /// </summary>
        /// <param name="type">The Type to be constructed</param>
        /// <param name="arguments">Arguments to the constructor</param>
        /// <returns>An instance of the Type</returns>
        public static object Construct(Type type, object[] arguments)
        {
            if (arguments == null) return Construct(type);

            Type[] argTypes = GetTypeArray(arguments);
            ConstructorInfo ctor = type.GetConstructor(argTypes);
            if (ctor == null)
                throw new InvalidTestFixtureException(type.FullName + " does not have a suitable constructor");

            return ctor.Invoke(arguments);
        }

        /// <summary>
        /// Returns an array of types from an array of objects.
        /// Used because the compact framework doesn't support
        /// Type.GetTypeArray()
        /// </summary>
        /// <param name="objects">An array of objects</param>
        /// <returns>An array of Types</returns>
        private static Type[] GetTypeArray(object[] objects)
        {
            Type[] types = new Type[objects.Length];
            int index = 0;
            foreach (object o in objects)
                types[index++] = o.GetType();
            return types;
        }

        #endregion

        #region Invoke Methods

        /// <summary>
		/// Invoke a parameterless method returning void on an object.
		/// </summary>
		/// <param name="method">A MethodInfo for the method to be invoked</param>
		/// <param name="fixture">The object on which to invoke the method</param>
		public static object InvokeMethod( MethodInfo method, object fixture ) 
		{
			return InvokeMethod( method, fixture, null );
		}

		/// <summary>
		/// Invoke a method, converting any TargetInvocationException to an NUnitException.
		/// </summary>
		/// <param name="method">A MethodInfo for the method to be invoked</param>
		/// <param name="fixture">The object on which to invoke the method</param>
        /// <param name="args">The argument list for the method</param>
        /// <returns>The return value from the invoked method</returns>
		public static object InvokeMethod( MethodInfo method, object fixture, params object[] args )
		{
			if(method != null)
			{
                if (fixture == null && !method.IsStatic)
                    Console.WriteLine("Trying to call {0} without an instance", method.Name);
				try
				{
					if (LocationProperty != null)
						Environment.CurrentDirectory = System.IO.Path.GetDirectoryName ((string)LocationProperty.GetValue (method.DeclaringType.Assembly, null));

					object result = null;
					if (GuiUnit.TestRunner.MainLoop == null) {
						result = method.Invoke (fixture, args);
					} else {
						var invokeHelper = new GuiUnit.InvokerHelper {
							Context = TestExecutionContext.CurrentContext,
							Func = () => method.Invoke( fixture, args )
						};

						GuiUnit.TestRunner.MainLoop.InvokeOnMainLoop (invokeHelper);
						invokeHelper.Waiter.WaitOne ();
						if (invokeHelper.ex != null)
							Rethrow (invokeHelper.ex);
						result = invokeHelper.Result;
					}

					if (result is System.Threading.Tasks.Task)
						((System.Threading.Tasks.Task) result).Wait ();
					return result;
				}
				catch(Exception e)
				{
					Rethrow (e);
                }
			}

		    return null;
		}

		private static readonly PropertyInfo LocationProperty;

		static Reflect()
		{
			Type assemblyType = typeof (Assembly);
			LocationProperty = assemblyType.GetProperty ("Location");
		}

		static void Rethrow (Exception e)
		{
			string Rethrown = "Rethrown";
			if (e is NUnitException && e.Message == Rethrown)
				throw e;

			if (e is TargetInvocationException || e is AggregateException)
				throw new NUnitException(Rethrown, e.InnerException);
			else
				throw new NUnitException(Rethrown, e);
		}
		#endregion

        #region Private Constructor for static-only class

        private Reflect() { }

		#endregion

#if CLR_2_0 || CLR_4_0
        class MethodInfoList : List<MethodInfo> { }
#else
        class MethodInfoList : ArrayList
        {
            public new MethodInfo[] ToArray()
            {
                return (MethodInfo[])base.ToArray(typeof(MethodInfo));
            }
        }
#endif
	}
}
