namespace Eden.CoreEditor
{
	// using Eden.CommonEditor;
	using Eden.Core;
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Reflection;
	using UnityEditor;
	using UnityEngine;

	public static class ScriptableObjectCreator
	{
		private static readonly List<string> INCLUDED_ASSEMBLIES = new List<string>()
		{
			"Eden."
		};

		private static readonly List<string> EXCLUDED_UNITY_ASSEMBLIES = new List<string>()
		{
			"Unity"
		};

		private static readonly List<Type> EXCLUDED_BASE_TYPES = new List<Type>()
		{
			typeof(Editor),
			typeof(EditorWindow),
			typeof(StateMachineBehaviour),
		};

		private static Dictionary<object, string> _allScriptableObjectsTypes = new Dictionary<object, string>();

		[MenuItem("Assets/Create/ScriptableObject", priority = 3)]
		public static void CreateScriptableObject(MenuCommand command)
		{
			RefreshCreatableScriptableObjects();

			SearchableListWindow.ShowSingle(_allScriptableObjectsTypes, "Scriptable Object Creator", OnSearchableListClosed, alphabeticalSort: true);
		}

		private static void RefreshCreatableScriptableObjects()
		{
			_allScriptableObjectsTypes.Clear();

			// Compute instantiable types
			List<Assembly> assemblies = SelectInterestingAssemblies();

			for (int i = 0; i < assemblies.Count; i++)
			{
				string assemblyName = Path.GetFileNameWithoutExtension(assemblies[i].ManifestModule.Name);
				string niceAssemblyName = ObjectNames.NicifyVariableName(assemblyName);

				Type[] subClasses = GetCreatableSubclasses(typeof(ScriptableObject), assemblies[i]);
				foreach (System.Type subClass in subClasses)
				{
					_allScriptableObjectsTypes.Add(subClass, string.Format("{0} ({1})", ObjectNames.NicifyVariableName(subClass.Name), niceAssemblyName));
				}
			}
		}

		private static void OnSearchableListClosed(SearchResult result, object selectedObject)
		{
			if (result == SearchResult.Validate)
			{
				ScriptableObjectInstantiator.Create((Type)selectedObject);
			}
		}

		private static List<Assembly> SelectInterestingAssemblies()
		{
			List<Assembly> interestingAssemblies = new List<Assembly>();

			Assembly[] allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
			UnityEditor.Compilation.Assembly[] unityAssemblies = UnityEditor.Compilation.CompilationPipeline.GetAssemblies();
			HashSet<string> unityAssembliesNames = new HashSet<string>();

			foreach (UnityEditor.Compilation.Assembly unityAssembly in unityAssemblies)
			{
				unityAssembliesNames.Add(unityAssembly.name);
			}

			foreach (Assembly assembly in allAssemblies)
			{
				string assemblyName = Path.GetFileNameWithoutExtension(assembly.ManifestModule.Name);

				if (unityAssembliesNames.Contains(assemblyName))
				{
					// This is a unity assembly. Check we do not have to exclude it
					if (EXCLUDED_UNITY_ASSEMBLIES.Exists(match => assemblyName.Contains(match)) == false)
					{
						interestingAssemblies.Add(assembly);
					}
				}
				else if (INCLUDED_ASSEMBLIES.Exists(match => assemblyName.Contains(match)) == true)
				{
					// Any other "included" assembly is good to go
					interestingAssemblies.Add(assembly);
				}
			}

			return interestingAssemblies;
		}

		public static System.Type[] GetCreatableSubclasses(System.Type baseClass, Assembly assembly)
		{
			System.Type[] types = assembly.GetTypes();
			List<System.Type> inheritedTypes = new List<System.Type>();
			foreach (System.Type type in types)
			{
				if (type.IsSubclassOf(baseClass) && type.IsAbstract == false && EXCLUDED_BASE_TYPES.Exists(excludedType => type.IsSubclassOf(excludedType)) == false)
				{
					inheritedTypes.Add(type);
				}
			}
			return inheritedTypes.ToArray();
		}
	}
}