// This code is part of the Fungus library (https://github.com/snozbot/fungus)
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)

using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;

namespace Fungus.EditorUtils
{
    public static class EditorExtensions
    {
        /// <summary>
        /// FindDerivedTypesFromAssembly allows a user to query all of types derived from a
        /// particular Type at runtime.
        /// Example usage:
        ///     foreach (System.Type st in EditorUtility.FindDerivedTypesFromAssembly(System.Reflection.Assembly.GetAssembly(typeof(BaseTimelineEvent)), typeof(BaseTimelineEvent), true))
        /// </summary>
        /// <param name="assembly">The assembly to search in</param>
        /// <param name="baseType">The base Type from which all returned Types derive</param>
        /// <param name="classOnly">If true, only class Types will be returned</param>
        /// <returns></returns>
        public static System.Type[] FindDerivedTypesFromAssembly(this System.Reflection.Assembly assembly, System.Type baseType, bool classOnly = true)
        {
            if (assembly == null)
                Debug.LogError("Assembly must be defined");
            if (baseType == null)
                Debug.LogError("Base type must be defined");

            // Iterate through all available types in the assembly
            var types = assembly.GetTypes().Where(type =>
                {
                    if (classOnly && !type.IsClass)
                        return false;
                    
                    if (baseType.IsInterface)
                    {
                        var it = type.GetInterface(baseType.FullName);

                        if (it != null)
                            return true;
                    }
                    else if (type.IsSubclassOf(baseType))
                    {
                        return true;
                    }

                    return false;
                }
            );

            return types.ToArray();
        }

        /// <summary>
        /// A convenient method for calling the above, but for ALL assemblies.
        /// Example usage:
        ///     List<System.Type> subTypes = EditorUtility.FindDerivedTypes(typeof(BaseTimelineEvent)).ToList();
        /// </summary>
        /// <param name="baseType"></param>
        /// <param name="classOnly"></param>
        /// <returns></returns>
        public static System.Type[] FindDerivedTypes(System.Type baseType, bool classOnly = true)
        {
#if UNITY_2019_2_OR_NEWER
            var results = TypeCache.GetTypesDerivedFrom(baseType).ToArray();
            if (classOnly)
                return results.Where(x => x.IsClass).ToArray();
            else
                return results.ToArray();
#else
            System.Type[] typeArray = new System.Type[0];
            var retval = typeArray.Concat(typeArray);
            foreach (var assem in System.AppDomain.CurrentDomain.GetAssemblies())
            {
                retval = retval.Concat(FindDerivedTypesFromAssembly(assem, baseType, classOnly));
            }
            return retval.ToArray();
#endif
        }

        /// <summary>
        /// Find and return all Unity.Objects that have the target interface
        /// </summary>
        /// <typeparam name="T">Intended to be an interface but will work for any</typeparam>
        /// <returns></returns>
        public static List<T> FindObjectsOfInterface<T>()
        {
            return Object.FindObjectsOfType<Object>().OfType<T>().ToList();
        }
    }
}