/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                       *
 *   TestCaseExtension Library, Copyright 2015 Bryan Chadwick            *
 *                                                                       *
 *   FILE: .\TestExtensionUtil.cs                                        *
 *                                                                       *
 *   This file is part of TestCaseExtension.                             *
 *                                                                       *
 *   TestCaseExtension is free software: you can redistribute it and/or  *
 *   modify it under the terms of the GNU General Public License         *
 *   as published by the Free Software Foundation, either version        *
 *   3 of the License, or (at your option) any later version.            *
 *                                                                       *
 *   TestCaseExtension is distributed in the hope that it will be useful,*
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
 *   GNU General Public License for more details.                        *
 *                                                                       *
 *   You should have received a copy of the GNU General Public License   *
 *   along with TestCaseExtension.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *                                                                       *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestCaseExtension.Internal
{
    /// <summary>Useful utility/extension methods for Reflection and Enumerables</summary>
    public static class TestExtensionUtil
    {
        /// <summary>Get the type with the given typeName (full path)</summary>
        public static Type GetType(string typeName)
        {
            Type t = Type.GetType(typeName);
            if (t != null) return t;

            var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetType(typeName) != null);
            if (assembly != null)
            {
                return assembly.GetType(typeName);
            }
            return null;
        }

        /// <summary>Retrieve the static Members of this Type with the given memberName</summary>
        public static MemberInfo[] GetStaticMembers(this Type type, string memberName)
        {
            return type.GetMember(memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        }

        /// <summary>Retrieve the value of the staic member (Field, Property, or no-argument Method)</summary>
        public static T ValueOf<T>(this Type type, MemberInfo info)
        {
            switch (info.MemberType)
            {
                case MemberTypes.Property: return (T)(info as PropertyInfo).GetValue(type);
                case MemberTypes.Field: return (T)(info as FieldInfo).GetValue(type);
                case MemberTypes.Method: return (T)(info as MethodInfo).Invoke(type, null);
                default: throw new TestCaseSourceException("Cannot get Value of member {0} of type {1}",
                    info.Name, info.MemberType);
            }
        }

        /// <summary>Get the Type of the MemberInfo (Field, Property, Method, or Event)</summary>
        public static Type GetMemberType(this MemberInfo info)
        {
            switch (info.MemberType)
            {
                case MemberTypes.Property: return (info as PropertyInfo).PropertyType;
                case MemberTypes.Field: return (info as FieldInfo).FieldType;
                case MemberTypes.Method: return (info as MethodInfo).ReturnType;
                case MemberTypes.Event: return (info as EventInfo).EventHandlerType;
                default: throw new TestCaseSourceException("Cannot get Type of member {0} (MemberType = {1}",
                    info.Name, info.MemberType);
            }
        }

        /// <summary>Is this Method marked with an Ignore or Explicit attribute?</summary>
        public static bool IsExplicitOrIgnored(this MethodInfo method)
        {
            return GetAttributes<ExplicitAttribute>(method).Any() || GetAttributes<IgnoreAttribute>(method).Any();
        }

        /// <summary>Get the Attributes of the given type on thie Method</summary>
        public static IList<T> GetAttributes<T>(this MethodInfo method) where T : Attribute
        {
            return method.GetCustomAttributes<T>().ToList();
        }

        /// <summary>Get the inner-most Exception within this one</summary>
        public static Exception DeepestException(this Exception e)
        {
            if (e == null || e.InnerException == null)
                return e;
            return DeepestException(e.InnerException);
        }

        /// <summary>Concatenate string versions of the elements in this IEnumerable separated by
        ///        the given string separator</summary>
        public static string StringJoin<T>(this IEnumerable<T> elements, string separator)
        {
            return elements.StringJoin(t => t.ToString(), separator);
        }

        /// <summary>Concatenate the elements in this IEnumerable converted by the given conversion
        ///        function, separated by the given string separator</summary>
        public static string StringJoin<T>(this IEnumerable<T> elements, Func<T, string> conversion, string separator)
        {
            return string.Join(separator, elements.Select(t => t == null ? "null" : conversion(t)));
        }

        /// <summary>Convert this Type into a string that includes generic type-parameters.
        ///        E.g., "KeyValuePair<Double, String>" rather than "System.Collections.Generic.KeyValuePair`2"</summary>
        public static string GenericTypeString(this Type t)
        {
            string simple = t.SimpleName();
            if (!t.IsGenericType)
                return simple;
            return simple + "<" + t.GenericTypeArguments.StringJoin(u => u.GenericTypeString(), ", ") + ">";
        }

        /// <summary>Returns the Name of the type without the Namspace prefix</summary>
        public static string SimpleName(this Type t)
        {
            string name = t.Name;
            int trimStart = name.LastIndexOf('.') + 1;
            int end = name.LastIndexOf('`');
            if (end < trimStart)
                end = name.Length;
            return name.Substring(trimStart, end - trimStart);
        }
    }
}