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

/* Game Manager Class
 * --------------------------------
 * The Game manager is meant to be a one-stop shop for game logic and gamestate control.
 * It allows physics and other systems manage their various parts, but the Game Manager actuates all other game rules.
 * 
 * Subconcepts:
 * - The Player
 * 	- The player is a class that represents a game object. The player's position is interrogated by the Game Manager to determine where to spawn obstacles.
 * - The Field
 * 	- The field, simply, is the play field. The player will be allowed to maneuver within the Field.
 * 	- The dimensions of the field are important for the particular purpose of knowing where, in relation to the player, to spawn new obstacles.
 */

public class GameManager : MonoBehaviour {
	#region Static Storage Variables
	// For Persistence Across Scenes
	#endregion
	#region Field Generation Parameters
	[SerializeField]
	private GameObject[] playerPrefabs;
	[SerializeField]
	private float chunkSize;
	[SerializeField]
	private float gameRadius;
	[SerializeField]
	private float fogDistance;
	#endregion
	#region Controls
	[SerializeField]
	private string buttonNameForward;
	[SerializeField]
	private string buttonNameBackward;
	[SerializeField]
	private string buttonNameLeft;
	[SerializeField]
	private string buttonNameRight;
	[SerializeField]
	private string buttonNameRotateLeft;
	[SerializeField]
	private string buttonNameRotateRight;
	[SerializeField]
	private string buttonNameRollMode;
	[SerializeField]
	private string buttonNameEscape;
	#endregion
	#region Other Variables
	[SerializeField]
	float secondsToHoldEscape = 2;
	[SerializeField]
	float secondsEscapeHeld	= 0;
	[SerializeField]
	GUIStyle escapeLabelStyle;
	[SerializeField]
	BoxCollider[] boundaries;
	#endregion
	#region Init
	void Start()
	{
		if (PersistData.get) {
			int selectedPlayerIndex = PersistData.get.selectedPlayerIndex;
			bool startupFromMainMenu = PersistData.get.wasPlayerSelectedFromMenu;

			if (startupFromMainMenu && selectedPlayerIndex != -1) {
				Instantiate (playerPrefabs [selectedPlayerIndex], Vector3.zero, Quaternion.identity);
				startupFromMainMenu = false;
			}
		}
		Player.position = Vector3.zero;
		if (Player.Instance)
			InfinityManager.Add (Player.Instance.transform);

		foreach (BoxCollider go in boundaries) {
			go.transform.position = go.transform.position.normalized * gameRadius * 0.6f;

			go.transform.rotation = Quaternion.LookRotation(transform.position - go.transform.position, Vector3.up);
			go.transform.localScale = new Vector3(gameRadius,fogDistance * 2, 1);
			go.transform.position -= Vector3.up * fogDistance * 0.5f;
			go.transform.position += Vector3.one;
		}
	}
	#endregion
	#region Per-Frame Updates
	void Update()
	{
		UpdateControls ();
	}

	void UpdateControls()
	{
		Player.UpdateControls(Input.GetButton (buttonNameForward),
		                      Input.GetButton (buttonNameBackward),
		                      Input.GetButton (buttonNameLeft),
		                      Input.GetButton (buttonNameRight),
		                      Input.GetButton (buttonNameRotateLeft),
		                      Input.GetButton (buttonNameRotateRight),
		                      Input.GetButton (buttonNameRollMode));

		if (Input.GetButton (buttonNameEscape)) {
			secondsEscapeHeld += Time.deltaTime;
		} else {
			secondsEscapeHeld = 0;
		}

		if (secondsEscapeHeld >= secondsToHoldEscape) {
			foreach (Camera c in Camera.allCameras)
			{
				if (c != Camera.main)
					c.enabled = false;
			}
			if (PersistData.get) {
				PersistData.get.selectedPlayerIndex = 0;
			}

			SplashScreen.AddSummonAction(Core.GoToMainMenu);
			SplashScreen.AddDismissAction(MainMenuManager.Enable);
			SplashScreen.SetDismissTrigger(SplashScreen.DismissTrigger.DoneFadeIn);
			SplashScreen.Summon();
		}
	}
	#endregion
	#region Fixed-Rate Updates
	void FixedUpdate()
	{
		TrackPlayer();
		UpdateField();
	}

	static float player_distanceFallen = 0;
	static Vector3 player_previousPosition;
	void TrackPlayer()
	{
		/*
		Vector3 _player_currentPosition = Player.position;
		float distanceFallenThisCycle = Vector3.Distance (player_previousPosition, _player_currentPosition);
		player_distanceFallen += distanceFallenThisCycle;
		player_previousPosition = _player_currentPosition;

		obstaclesToSpawn += obstaclesToSpawnPerUnitFallen * distanceFallenThisCycle;
		*/

		Vector3 playerVelocity = Player.velocity;
		float distanceFallenThisCycle = Mathf.Abs(playerVelocity.y * Time.fixedDeltaTime);
		player_distanceFallen += distanceFallenThisCycle;
		
		obstaclesToSpawn += obstaclesToSpawnPerUnitFallen * distanceFallenThisCycle;

		transform.position = new Vector3(transform.position.x,Player.position.y,transform.position.z);
	}

	[SerializeField]
	private GameObject[] spawnerTemplateList;
	[SerializeField]
	private List<GameObject> spawnedObjects = new List<GameObject>();
	[SerializeField]
	private float objectTrimThreshold;
	[SerializeField]
	private float obstaclesToSpawnPerUnitFallen = 0.5f; // 0.5f = 1 obstacle for every two unity units fallen.
	[SerializeField]
	private float obstaclesToSpawn				= 0;
	void UpdateField()
	{
		// Trim Objects that the player has passed.
		List<GameObject> _spawnedObjects = new List<GameObject>(spawnedObjects.Count);
		foreach (GameObject go in spawnedObjects) 
		{
			if ((go.transform.position.y - Player.position.y) > objectTrimThreshold)
			{
				// Obstacle is well above the player. Destroy it.
				InfinityManager.Remove(go.transform);
				Destroy(go);
				continue;
			}
			_spawnedObjects.Add(go);
		}
		spawnedObjects = _spawnedObjects;

		//This is lazy. Spawn one object per fixed frame, max.
		if (obstaclesToSpawn >= 1.0f) 
		{
			// Sanity Check. Can we actually sample from the template list?
			if (spawnerTemplateList.Length == 0)
			{
				return;
			}
			// Cool, lets select something then.
			GameObject spawnTemplate = spawnerTemplateList[Random.Range(0,spawnerTemplateList.Length)];
			// Sanity Check. Did we select something null?
			if (spawnTemplate)
			{
				Vector2 spawnPosition2D = Random.insideUnitCircle;
				Vector3 spawnPosition = Vector3.one * gameRadius * 0.5f;
				//spawnPosition.x *= Mathf.Ceil(Random.Range(gameRadius * -0.5f,gameRadius * 0.5f));
				spawnPosition.x *= spawnPosition2D.x;
				spawnPosition.z *= spawnPosition2D.y;
				spawnPosition.x = Mathf.Ceil(spawnPosition.x);
				spawnPosition.z = Mathf.Ceil(spawnPosition.z);
				spawnPosition.y = Mathf.Ceil(Player.position.y - fogDistance);
				//spawnPosition.z *= Mathf.Ceil(Random.Range(gameRadius * -0.5f,gameRadius * 0.5f));
				Quaternion rotation = Quaternion.identity;
				GameObject go = (GameObject)GameObject.Instantiate(spawnTemplate,spawnPosition,rotation);
				spawnedObjects.Add(go);
				InfinityManager.Add(go.transform);
			}
			obstaclesToSpawn -= 1.0f;
		}
	}
	#endregion
	#region GUI
	void OnGUI()
	{
		if (secondsEscapeHeld > 0 && secondsEscapeHeld < secondsToHoldEscape) {
			string message = "Quitting in " + (secondsToHoldEscape - secondsEscapeHeld).ToString("F1") + " seconds.";

			GUI.Label(new Rect(
				0,0,
				Screen.width,
				Screen.height),
			          message,
			          escapeLabelStyle);
		}
	}
	#endregion
	#region Static Functions
	#endregion
}
