using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace MoreMountains.Tools
{
	/// <summary>
	/// Add this attribute to a class and its Execution Order will be changed to the value specified in parameters
	/// Usage : [ExecutionOrder(66)]
	/// </summary>
	public class MMExecutionOrderAttribute : Attribute
	{
		#if UNITY_EDITOR
		/// the execution order you want for the class this attribute is applied to 
		public int ExecutionOrder = 0;

		protected static Dictionary<MonoScript, MMExecutionOrderAttribute> _monoScripts;
		protected static Type _executionOrderAttributeType;
		protected static Assembly _typeAssembly;
		protected static Type[] _assemblyTypes;

		/// <summary>
		/// Attribute method
		/// </summary>
		/// <param name="newExecutionOrder"></param>
		public MMExecutionOrderAttribute(int newExecutionOrder)
		{
			ExecutionOrder = newExecutionOrder;
		}


		/// <summary>
		/// When Unity loads, modifies the execution orders of monos with an ExecutionOrder attribute, if needed
		/// </summary>
		[InitializeOnLoadMethod]        
		protected static void ModifyExecutionOrder()
		{
			Initialization();

			FindExecutionOrderAttributes();
            
			if (ExecutionOrderHasChanged())
			{
				UpdateExecutionOrders();
			}
		}

		/// <summary>
		/// Initialization method
		/// </summary>
		protected static void Initialization()
		{
			_monoScripts = new Dictionary<MonoScript, MMExecutionOrderAttribute>();
			_executionOrderAttributeType = typeof(MMExecutionOrderAttribute);
			_typeAssembly = _executionOrderAttributeType.Assembly;
			_assemblyTypes = _typeAssembly.GetTypes();
		}

		/// <summary>
		/// Goes through all assembly types and stores execution order attributes when found
		/// </summary>
		protected static void FindExecutionOrderAttributes()
		{
			foreach (Type assemblyType in _assemblyTypes)
			{
				if (!HasExecutionOrderAttribute(assemblyType))
				{
					continue;
				}

				object[] attributes = assemblyType.GetCustomAttributes(_executionOrderAttributeType, false);
				MMExecutionOrderAttribute attribute = attributes[0] as MMExecutionOrderAttribute;

				string asset = "";
				string[] guids = AssetDatabase.FindAssets(assemblyType.Name + " t:script");

				if (guids.Length != 0)
				{
					foreach (string guid in guids)
					{
						string assetPath = AssetDatabase.GUIDToAssetPath(guid);
						string filename = Path.GetFileNameWithoutExtension(assetPath);
						if (filename == assemblyType.Name)
						{
							asset = guid;
							break;
						}
					}
				}
				else
				{
					Debug.LogError("MMTools' ExecutionOrderAttribute : Can't change "+ assemblyType.Name + "'s execution order");
					return;
				}

				MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(AssetDatabase.GUIDToAssetPath(asset));
				_monoScripts.Add(monoScript, attribute);
			}
		}

		/// <summary>
		/// Returns true if the class in parameters has the ExecutionOrder attribute, false otherwise
		/// </summary>
		/// <param name="assemblyType"></param>
		/// <returns></returns>
		protected static bool HasExecutionOrderAttribute(Type assemblyType)
		{
			object[] attributes = assemblyType.GetCustomAttributes(_executionOrderAttributeType, false);
			return (attributes.Length == 1);
		}

		/// <summary>
		/// Returns true if the execution order has changed since last time 
		/// </summary>
		/// <returns></returns>
		protected static bool ExecutionOrderHasChanged()
		{
			bool executionOrderHasChanged = false;
			foreach (KeyValuePair<MonoScript, MMExecutionOrderAttribute> monoScript in _monoScripts)
			{
				if (monoScript.Key != null)
				{
					if (MonoImporter.GetExecutionOrder(monoScript.Key) != monoScript.Value.ExecutionOrder)
					{
						executionOrderHasChanged = true;
						break;
					}
				}                    
			}
			return executionOrderHasChanged;
		}

		/// <summary>
		/// Updates the execution orders for all pairs found by FindExecutionOrderAttributes()
		/// </summary>
		protected static void UpdateExecutionOrders()
		{
			foreach (KeyValuePair<MonoScript, MMExecutionOrderAttribute> monoScript in _monoScripts)
			{
				if (monoScript.Key != null)
				{
					if (MonoImporter.GetExecutionOrder(monoScript.Key) != monoScript.Value.ExecutionOrder)
					{
						MonoImporter.SetExecutionOrder(monoScript.Key, monoScript.Value.ExecutionOrder);
					}
				}
			}
		}

		#endif
	}
}