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

// Util class with coroutines that can be paused.
public static class Wait
{
	public static IEnumerator CoroutineWaitForTime( float time )
	{		
		while ( time > 0.0f )
		{
			yield return new WaitForEndOfFrame();
			if ( SystemTime.Paused == false )
			{
				time -= Time.deltaTime;
			}
		}
		yield break;
	}
	
	public static IEnumerator CoroutineWaitForFixedUpdate( )
	{
		yield return new WaitForFixedUpdate();
		while ( SystemTime.Paused )
		{
			yield return new WaitForFixedUpdate();
		}
		yield break;
	}
	
	public static IEnumerator CoroutineWaitForEndOfFrame( )
	{
		yield return new WaitForEndOfFrame();
		while ( SystemTime.Paused )
		{
			yield return new WaitForEndOfFrame();
		}
		yield break;
	}
	
	public static IEnumerator CoroutineWaitForTimeNoPause( float time ) { yield return new WaitForSeconds(time); }	
	public static IEnumerator CoroutineWaitForFixedUpdateNoPause( ) { yield return new WaitForFixedUpdate(); }	
	public static IEnumerator CoroutineWaitForEndOfFrameNoPause( ) { yield return new WaitForEndOfFrame(); }

	public static Coroutine ForTime( float time )
	{
		return SystemTime.Get.StartCoroutine(CoroutineWaitForTime(time));
	}	
	
	public static Coroutine ForFixedUpdate()
	{
		return SystemTime.Get.StartCoroutine(CoroutineWaitForFixedUpdate());
	}	
	
	public static Coroutine ForEndOfFrame()
	{
		return SystemTime.Get.StartCoroutine(CoroutineWaitForEndOfFrame());
	}
	
	public static Coroutine WaitForTime( this MonoBehaviour self, float time )
	{
		if ( self.gameObject.layer == (int)eLayers.eGUI )
		    return self.StartCoroutine(CoroutineWaitForTimeNoPause(time));
		return self.StartCoroutine(CoroutineWaitForTime(time));
	}	
	
	public static Coroutine WaitForFixedUpdate(this MonoBehaviour self)
	{
		if ( self.gameObject.layer == (int)eLayers.eGUI )
			return self.StartCoroutine(CoroutineWaitForFixedUpdateNoPause());
		return self.StartCoroutine(CoroutineWaitForFixedUpdate());
	}	
	
	public static Coroutine WaitForEndOfFrame(this MonoBehaviour self)
	{
		if ( self.gameObject.layer == (int)eLayers.eGUI )
			return self.StartCoroutine(CoroutineWaitForEndOfFrameNoPause());
		return self.StartCoroutine(CoroutineWaitForEndOfFrame());
	}



}


public static class ExtentionsPause
{
	public static bool Paused( this GameObject obj )
	{			
		if ( Time.timeScale <= 0 )
			return true;
		
		if ( obj.layer == (int)eLayers.eGUI )
			return false;
		
		return SystemTime.Paused;
	}


}


public class SystemTime : Singleton<SystemTime> 
{	
	class tTimeScaleMultiplier
	{
		public float m_mult = 1;
		public float m_time = 0;
	}
	
	Dictionary< string, tTimeScaleMultiplier > m_timeScaleMultipliers = new Dictionary< string, tTimeScaleMultiplier >();			
	float m_timeSinceLastFrame = 0.0f;
	float m_timeFixedOriginal = 0.1f;
	
	// int m_activeMenuId = -1;
	bool m_gamePaused = false;	
	int m_pauseRefCount = 0;
	
	List<MonoBehaviour> m_pausedComponents = new List<MonoBehaviour>();
	List<Rigidbody> m_pausedBodies = new List<Rigidbody>();
	List<Vector3> m_pausedVelocities = new List<Vector3>();
	// List<exSpriteAnimation> m_pausedExAnimations = new List<exSpriteAnimation>();
	// List<Animation> m_pausedAnimations = new List<Animation>();
	
	public static bool Paused { get { if ( m_instance ) return m_instance.m_gamePaused; return false; } }
	
	public static void PauseGame()
	{			
		Instance.m_gamePaused = true;
		Instance.m_pauseRefCount++;
		if ( Debug.isDebugBuild) Debug.Log("Paused: "+Instance.m_pauseRefCount);
		
		GameObject[] allObjects = FindObjectsOfType(typeof(GameObject))  as GameObject[];
		GameObject obj = null;
		bool tmpWasRendererEnabled = false;
		MonoBehaviour[] tmpComponents = null;
		int tmpComponentCount = 0;
		MonoBehaviour tmpComponent = null;
			
		//exSpriteAnimation exAnimComponent = null;
		int count = allObjects.Length;
		for ( int i = 0; i < count; ++i )
		{
			obj = allObjects[i];
		   	if (obj.activeInHierarchy && obj.layer != (int)eLayers.eGUI && obj.layer != (int)eLayers.eNoPause)
			{
				if ( obj.GetComponent<Rigidbody>() != null)
				{
					if ( obj.GetComponent<Rigidbody>().isKinematic == false )
					{
						m_instance.m_pausedVelocities.Add( obj.GetComponent<Rigidbody>().velocity );
						m_instance.m_pausedBodies.Add( obj.GetComponent<Rigidbody>() );
						obj.GetComponent<Rigidbody>().isKinematic = true;
					}				
				}				
				
				tmpWasRendererEnabled = ( obj.GetComponent<Renderer>() != null ) ? tmpWasRendererEnabled = obj.GetComponent<Renderer>().enabled : false;
				
				tmpComponents = obj.GetComponents<MonoBehaviour>();				
				tmpComponentCount = tmpComponents.Length;
				for ( int j = 0; j < tmpComponentCount; ++j )
				{
					tmpComponent = tmpComponents[j];					
					if ( tmpComponent.enabled )
					{
						m_instance.m_pausedComponents.Add(tmpComponent);
						tmpComponent.enabled = false;
					}
				}
				// MeshRenderer renderer = obj.GetComponent<MeshRenderer>();
				if ( tmpWasRendererEnabled )
					obj.GetComponent<Renderer>().enabled = true;
			}
		}
		
	}
	
	public static void UnPauseGame()
	{
		Instance.m_pauseRefCount--;
		if ( Instance.m_pauseRefCount < 0 )
			Debug.LogError("Pause ref count mismatch");
		Debug.Log("UnPaused: "+Instance.m_pauseRefCount);
		
		if ( Instance.m_pauseRefCount == 0 )
		{
			
			MonoBehaviour behaviour = null;
			int count = m_instance.m_pausedComponents.Count;
			for ( int i = 0; i < count; ++i )
			{
				behaviour = m_instance.m_pausedComponents[i];
				if ( behaviour != null )
					behaviour.enabled = true;
			}
			m_instance.m_pausedComponents.Clear();
			
			Rigidbody body = null;
			count = m_instance.m_pausedBodies.Count;
			for ( int i = 0; i < count; ++i )
			{
				body = m_instance.m_pausedBodies[i];
				if ( body != null )
				{
					body.isKinematic = false;
					body.velocity = m_instance.m_pausedVelocities[i];
					body.WakeUp();
				}
			}
			m_instance.m_pausedBodies.Clear();
			m_instance.m_pausedVelocities.Clear();
			
			/*
			exSpriteAnimation exAnimComponent = null;
			count = m_instance.m_pausedExAnimations.Count;
			for ( int i = 0; i < count; ++i )
			{
				exAnimComponent = m_instance.m_pausedExAnimations[i];
				if ( exAnimComponent != null )
					exAnimComponent.Resume();
			}
			m_instance.m_pausedExAnimations.Clear();
			
			Animation animation = null;
			count = m_instance.m_pausedAnimations.Count;
			for ( int i = 0; i < count; ++i )
			{
				animation = m_instance.m_pausedAnimations[i];
				if ( animation != null )
					animation.Play();
			}
			m_instance.m_pausedAnimations.Clear();
			*/
			Instance.m_gamePaused = false;	
		}
		
	}
	
	
	// Time meddling functions
	public static void SlowMoPause( string source, float time )
	{
		tTimeScaleMultiplier multiplier = new tTimeScaleMultiplier();
		multiplier.m_time = time;
		multiplier.m_mult = 0.001f;
		Instance.m_timeScaleMultipliers[source] = multiplier;
	}

	public static void SlowMoBegin( string source, float scale, float time )
	{		
		//if ( Instance.m_timeScaleMultipliers.ContainsKey(source)
		Instance.m_timeScaleMultipliers[source] = new tTimeScaleMultiplier{ m_mult = scale, m_time = time };
	}
	
	public static void SlowMoBegin( string source, float scale )
	{		
		Instance.m_timeScaleMultipliers[source] = new tTimeScaleMultiplier{ m_mult = scale, m_time = -1.0f };
	}	
	
	public static void SlowMoEnd( string source )
	{
		Instance.m_timeScaleMultipliers.Remove(source);
	}
	
	
	public static void SlowMoClear()
	{
		Instance.ClearTimeMultipliers();
	}
	
	// Can call every update and returns true on updates where the time has elapsed
	public static bool TimePassed( float period )
	{
		return  ( Time.timeSinceLevelLoad % period) < ((Time.timeSinceLevelLoad - Time.deltaTime) % period);
	}
	
	
	// Use this for initialization
	void Awake () 
	{
		SetSingleton();
		Object.DontDestroyOnLoad(this);	
		m_timeFixedOriginal = Time.fixedDeltaTime;
	
	}
	
	// Update is called once per frame
	void Update () 
	{
		UpdateTimeMultipliers();	
	
	}
	
	
	void ClearTimeMultipliers()
	{
		m_timeScaleMultipliers.Clear();		
		Time.timeScale = 1.0f;
		Time.fixedDeltaTime = m_timeFixedOriginal * 1.0f;
	}
	
	void UpdateTimeMultipliers()
	{
		float timeMultiplier = 1.0f;		
		
		if ( m_gamePaused == false )
		{
			
			float realTimeDelta = Time.realtimeSinceStartup - m_timeSinceLastFrame;
			m_timeSinceLastFrame = Time.realtimeSinceStartup;
							
			var keys = new List<string>(m_timeScaleMultipliers.Keys);
			foreach (string key in keys)
			{
				float timeRemaining = m_timeScaleMultipliers[key].m_time;
				if ( timeRemaining > 0 )
				{
					timeRemaining -= realTimeDelta;
					if ( timeRemaining <= 0 )
					{
						m_timeScaleMultipliers.Remove(key);
					}
					else
					{
						m_timeScaleMultipliers[key].m_time = timeRemaining;
						timeMultiplier = Mathf.Min(timeMultiplier, m_timeScaleMultipliers[key].m_mult);
					}
				}
				else 
				{
					timeMultiplier = Mathf.Min(timeMultiplier, m_timeScaleMultipliers[key].m_mult);
				}
			}
			
			/*  Time mult smoothing (disabled for now) /
			if ( timeMultiplier > 0.0f )
			{
				timeMultiplier = Time.timeScale + ( (timeMultiplier - Time.timeScale) * realTimeDelta * 5.0f );
			}
			/**/
		}
		
		Time.timeScale = timeMultiplier;
		Time.fixedDeltaTime = m_timeFixedOriginal * timeMultiplier;
	}
	
}
