using UnityEngine;
using System.Collections;
using System.Reflection;
using System.Collections.Generic;

public delegate void OnUIFinishLoading();

public class BaseUI : MonoBehaviour
{
	public const string pathToUIPrefabs = "UI/";

	#region Static part
	static Dictionary<System.Type, BaseUI> registeredUI = new Dictionary<System.Type, BaseUI>();
	static List<BaseUI> displayedUIStack = new List<BaseUI>();

	public static bool isInitialComponentRegistered = false;
	public static DontDublicateCanvas instanceForCoroutine;
	private static Transform mainCanvas;
	public static Transform MainCanvas
	{
		get
		{
			if(mainCanvas == null)
			{
				Canvas findedCanvas = GameObject.FindObjectOfType<Canvas>();
				if(findedCanvas != null)
				{
					mainCanvas = findedCanvas.transform;
				}
				else
				{
					GameObject loadedCanvas = Instantiate(Resources.Load(pathToUIPrefabs + "Canvas")) as GameObject;
					mainCanvas = loadedCanvas.transform;
				}
				instanceForCoroutine = mainCanvas.GetComponent<DontDublicateCanvas>();
			}
			return mainCanvas;
		}

		set
		{
			mainCanvas = value;
			instanceForCoroutine = mainCanvas.GetComponent<DontDublicateCanvas>();
		}
	}

	public bool IsShowed
	{
		get {
			if (displayedUIStack != null)
			{
				foreach ( var ui in displayedUIStack)
				{
					if (this == ui)
					{
						return true;
					}
				}
			}
			return false;
		}
	}

	/// <summary>
	/// Отображает заданное меню на экран.
	/// </summary>
	/// <param name="needLevel">Уровень интерфейса, на котором необходимо отобразить меню</param>
	/// <param name="showingDelegate">Делегат, который может быть вызван когда меню закончит процедуру отрисовки, случае если меню это предусматривает.
	/// Например если меню имеет сложную структуру появления
	/// и необходимо продолжить дальнейшее выполнение программы только после того как меню полностью появится, то можно использовать этот делегат.</param>
	/// <typeparam name="T">Тип отображаемого меню, унаследованный от BaseUI</typeparam>
	public static void Show<T>(int needLevel = 0, OnUIFinishLoading showingDelegate = null) where T : BaseUI
	{
		if (registeredUI.ContainsKey (typeof(T)))
		{
			if (needLevel < 0 || needLevel > displayedUIStack.Count)
			{
				needLevel = displayedUIStack.Count;
			}

			int oldUILevel = displayedUIStack.IndexOf(registeredUI [typeof(T)]);
			if(oldUILevel == needLevel)
			{
				return;
			}

			if(oldUILevel != -1)
			{
				displayedUIStack[oldUILevel].transform.SetSiblingIndex(needLevel);

				BaseUI swapUI = displayedUIStack[oldUILevel];

				displayedUIStack.RemoveAt(oldUILevel);
				displayedUIStack.Insert(Mathf.Min(needLevel, displayedUIStack.Count), swapUI);
				
				HideDisplayedUI(needLevel + 1);
			}
			else
			{
				HideDisplayedUI(needLevel);
				displayedUIStack.Insert(needLevel, registeredUI [typeof(T)]);
				displayedUIStack[needLevel].transform.SetSiblingIndex(needLevel);
				displayedUIStack[needLevel].gameObject.SetActive(true);
				displayedUIStack[needLevel].OnShow();
				displayedUIStack[needLevel].Show(showingDelegate);
			}
		}
		else
		{
			Create<T>(needLevel);
			BaseUI.Show<T>(needLevel, showingDelegate);
		}
	}

	public static void ShowModal<T>(OnUIFinishLoading showingDelegate = null) where T : BaseUI
	{
		Show<T>(-1, showingDelegate);
	}
	
	static void HideDisplayedUI(int fromLevel = 0)
	{
		for (int i = displayedUIStack.Count - 1; i >= fromLevel; i--)
		{
			if (!displayedUIStack[i].isNeedFreezeUIInDisplayStack)
			{
				BaseUI hidingMenu = displayedUIStack[i];
				displayedUIStack.RemoveAt(i);
				hidingMenu.OnHide();
				hidingMenu.Hide(() => 
				{
					hidingMenu.gameObject.SetActive(false);
					hidingMenu.transform.SetAsLastSibling();
				});
			}
		}
	}

	/// <summary>
	/// Создает заданное меню на заданном уровне интерфейса. При этом меню автоматически не отображается.
	/// </summary>
	/// <param name="interfaceLevel">Уровень интерфейса, на котором будет создано меню.</param>
	/// <typeparam name="T">Тип отображаемого меню, унаследованный от BaseUI</typeparam>
	public static T Create<T>(int interfaceLevel = 0) where T : BaseUI
	{
		if (registeredUI.ContainsKey (typeof(T)))
		{
			return (T)registeredUI[typeof(T)];
		}

		GameObject UIprefab = Resources.Load<GameObject> (pathToUIPrefabs + typeof(T).Name);
		return (T)InitializeUIGameObject<T> (UIprefab, interfaceLevel);
	}

	static T InitializeUIGameObject<T>(GameObject UIprefab, int interfaceLevel) where T : BaseUI
	{
		if(!isInitialComponentRegistered)
		{
			RegisterAllChildUI();
		}
		if (registeredUI.ContainsKey (typeof(T)))
		{
			return (T)registeredUI[typeof(T)];
		}

		GameObject interfaceObject;
		T interfaceComponent;
		if(UIprefab == null)
		{
			interfaceObject = new GameObject();
			interfaceComponent = interfaceObject.AddComponent<T>();
		}
		else
		{
			interfaceObject = Instantiate<GameObject>(UIprefab);
			interfaceComponent = interfaceObject.GetComponent<T>();
		}
		
		interfaceObject.name = typeof(T).Name;
		interfaceObject.GetComponent<RectTransform>().SetParent (MainCanvas, false);

		registeredUI.Add (typeof(T), interfaceComponent);
		interfaceComponent.Init ();
		interfaceObject.SetActive (false);

		return (T)interfaceComponent;
	}

	public static void Hide<T> (OnUIFinishLoading onFinishHiding = null) where T : BaseUI
	{
		if(registeredUI.ContainsKey(typeof(T)))
		{
			for (int i = 0; i < displayedUIStack.Count; i++)
			{
				if(displayedUIStack[i] == registeredUI[typeof(T)])
				{
					displayedUIStack[i].Hide(() => 
					{
						displayedUIStack[i].gameObject.SetActive(false);
						displayedUIStack[i].OnHide();
						displayedUIStack[i].transform.SetAsLastSibling();
						displayedUIStack.RemoveAt(i);
						if(onFinishHiding != null)
						{
							onFinishHiding();
						}
					});
					break;
				}
			}
		}
	}

	// TODO - refactor it -  create 2 functions - with and without scene load
	public static void AsyncShowWithLoaderUI<T, LoaderUI>(int interfaceLevel = 0, OnUIFinishLoading loadingDelegate = null, string sceneName = "")
		where T : BaseUI
		where LoaderUI : BaseUI
	{
		BaseUI.Show<LoaderUI> (interfaceLevel + 1, () =>
		{
			registeredUI[typeof(LoaderUI)].isNeedFreezeUIInDisplayStack = true;
			registeredUI[typeof(LoaderUI)].isNeedDestroyUIBetweenScenes = false;
			AsyncLoad<T> (interfaceLevel, () =>
			{
				BaseUI.Show<T> (interfaceLevel);
				registeredUI[typeof(LoaderUI)].isNeedFreezeUIInDisplayStack = false;
				registeredUI[typeof(LoaderUI)].isNeedDestroyUIBetweenScenes = true;
				Hide<LoaderUI>();
				if(loadingDelegate != null)
				{
					loadingDelegate ();
				}
			}, sceneName);
		});
	}

	public static void AsyncLoad<T>(int interfaceLevel = 0, OnUIFinishLoading loadingDelegate = null, string sceneName = "") where T : BaseUI
	{
		instanceForCoroutine.StartCoroutine (LoadingUIEnumerator<T>(interfaceLevel, loadingDelegate, sceneName));
	}

	public static IEnumerator LoadingUIEnumerator<T>(int interfaceLevel = 0, OnUIFinishLoading loadingDelegate = null, string sceneName = "") where T : BaseUI
	{
		if(sceneName != "" && Application.loadedLevelName != sceneName)
		{
			List<System.Type> deleteUI = new List<System.Type>();
			foreach(var currentUI in registeredUI)
			{
				if(currentUI.Value.isNeedDestroyUIBetweenScenes)
				{
					deleteUI.Add(currentUI.Key);
				}
			}

			foreach(var deleteKey in deleteUI)
			{
				if(displayedUIStack.Contains(registeredUI[deleteKey]))
				{
					displayedUIStack.Remove(registeredUI[deleteKey]);
				}
				DestroyImmediate(registeredUI[deleteKey].gameObject);
				registeredUI.Remove(deleteKey);
			}

			AsyncOperation async = Application.LoadLevelAsync(sceneName);
			yield return async;
		}

		if (registeredUI.ContainsKey (typeof(T)))
		{
			if (loadingDelegate != null)
			{
				loadingDelegate();
			}
			yield break;
		}

		ResourceRequest UIrequest = Resources.LoadAsync (pathToUIPrefabs + typeof(T).Name);
		yield return UIrequest;
		
		InitializeUIGameObject<T> ((GameObject)UIrequest.asset, interfaceLevel);
		BaseUI.Show<T> (interfaceLevel);

		if (loadingDelegate != null)
		{
			loadingDelegate ();
		}
	}

	public static void UpdateInterface()
	{
		if (registeredUI != null)
		{
			foreach(BaseUI i in registeredUI.Values)
			{
				i.UpdateState();
			}
		}
	}

	public static void RegisterAllChildUI()
	{
		BaseUI[] findedComponents = MainCanvas.GetComponentsInChildren<BaseUI> ();
		for(int i = 0; i < findedComponents.Length; i++)
		{
			if(!registeredUI.ContainsKey( findedComponents[i].GetType() ))
			{
				registeredUI.Add(findedComponents[i].GetType(), findedComponents[i]);
				displayedUIStack.Insert(Mathf.Min(findedComponents[i].transform.GetSiblingIndex(), displayedUIStack.Count), findedComponents[i]);
			}
		}
		isInitialComponentRegistered = true;
	}

	#endregion

	#region Instance part
	private bool isNeedDestroyUIBetweenScenes = true;
	private bool isNeedFreezeUIInDisplayStack = false;

	protected virtual void Awake()
	{
	}

	void Start()
	{
		if(!registeredUI.ContainsKey(this.GetType()))
		{
			RegisterAllChildUI();
		}
	}

	protected virtual void Init()
	{
	}

	protected virtual void Show(OnUIFinishLoading onFinishShowing = null)
	{
		if(onFinishShowing != null)
		{
			onFinishShowing();
		}
	}

	protected virtual void Hide(OnUIFinishLoading onFinishHiding = null)
	{
		if(onFinishHiding != null)
		{
			onFinishHiding();
		}
	}

	protected virtual void OnShow()
	{
	}

	protected virtual void OnHide()
	{
	}

	protected virtual void OnDestroy()
	{
		registeredUI.Remove (this.GetType());
	}
	
	public virtual void UpdateState()
	{	
	}
	#endregion
}
