/*Cose da fare

- Personaggio
  - Sonno (Sleep)
  - Spavento (Fear)
  - Fame (Hunger)
  - Vita (Health)
  - State : StateType
    * Sleepy
    * Fearful
    * Hungry
    * Base

- Meccanica di gioco (stati, modificatori, plus, minus...)

Implementation detail:
- Tag:
  Player
  -> FirstPersonController
  -> check if clicked object is IInteractible
     -> .Interact(this)
  -> .Update()
	 -> apply current state modifiers
	    -> Health() -> no change unless .Hurt
		-> Sleep() -> increase over time
		-> Hunger() -> increase over time, modified by geometric mean of previous K samples of speed,
			apply random
		-> Fear() -> increase if num enemies > threshold, decrease over time, apply random
	 -> compute primary state
		   -> Scared if Fear > .5
		   -> Hungry if Hunger > .5
		   -> Sleepy if Sleep > .5
		   -> Base if Others
     -> EnsureEnemies() -> enable-disable enemies with a trigger
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class Player : MonoBehaviour
{
		public Material daySky;
		public Material mediumSky;
		public Material nightSky;
		
		public GameObject deathScreen;
		public GameObject gameOverText;
		public GameObject deathReasonText;
		
		public AudioClip success;
		public AudioClip[] heartBeats;
		public AudioClip stomach;
		public AudioClip death;
		public AudioClip victory;
		public AudioClip hitSound;
		public AudioClip eatSound;
		public AudioClip snore;
		
		public AudioSource shortSoundSource;
		public AudioSource heartBeatSource;

		public float Sleep {
				get { return sleep; }
		}

		public float Fear {
				get{ return fear; }
		}

		public float Hunger {
				get{ return hunger; }
		}

		public float Health {
				get{ return health; }
		}

		public int Goals {
				get;
				set;
		}

		public float sleep, hunger, health, fear;
		public bool Scared { get; set; }
		public bool Hungry { get; set; }
		public bool Tired { get; set; }
		public States MostImportantState { get; set; }

		DayStates dayState;

		float lastTiredAnimation;
		float lastHungerSound;

		private bool dead;
		private float deadTime;
		private float lastInteractionTime;
		private float hitOverlayTime;
		
		public GameObject hitOverlay;
		public GameObject introText;
		public float introStart;

		public enum States
		{
				Base,
				Sleepy,
				Scared,
				Hungry,
		}
		private enum DayStates
		{
				Day,
				Afternoon,
				Night,
		}
		public enum DeathReason
		{
				Hunger,
				Sleep,
				Health,
		}
		
		
		public void Hurt ()
		{
				health -= 0.28f;
				if (health <= 0f)
						DIE (DeathReason.Health);
				if (fear < .5f)
						fear = .501f;
						
				PlayClip (hitSound);
				
				hitOverlay.SetActive (true);
				hitOverlay.guiTexture.pixelInset = new Rect (-256, -256, 512, 512);
				hitOverlayTime = Time.time;
		}

		void Start ()
		{
				sleep = hunger = fear = 0f;
				health = 1f;
				lastPos = this.transform.position;
				
				getAnimation ().Play ("Still");
		
				Screen.showCursor = false;
				
				ActivateDay ();
				
				introText.guiText.text = "Teleporting statues?\n\nI really want to take pictures of them!";
				introStart = Time.time;
		}

		public void WIN ()
		{
				if (dead)
						return;
			
				Debug.Log ("Guess who won the game? It's you!");
				
				Finish ("You won!", "Don't get too used to it", victory);
		}
	
		private void DIE (DeathReason rs)
		{
				if (dead)
						return;
			
				Debug.Log ("YOU ARE DEAD");
				getAnimation ().Play ("DeadlyTired");

				switch (rs) {
				case DeathReason.Health:
			//looks like you don't have any more blood in your veins. Too bad, you cant play with no blood.
						Finish ("You died", "Pro tip of the day: claws hurt", death, 4f);
						break;
				case DeathReason.Hunger:
				// Kids, never disappoint your granma and always eat your meal
						Finish ("You died", "Guess who forgot to eat?", death, 4f);
						break;
				case DeathReason.Sleep:
						Finish ("You died", "At least you were sleeping...", death, 4f);
						break;
				}
		}
		
		private void Finish (string primaryText, string secondaryText, AudioClip audio, float delay = 0f)
		{
				deadTime = Time.time + delay;
				dead = true;
				
				PlayClip (audio);
				
				deathReasonText.guiText.text = secondaryText;
				gameOverText.guiText.text = primaryText;
		
				var everything = (MonoBehaviour[])GameObject.FindObjectsOfType (typeof(MonoBehaviour));
				foreach (var e in everything) {
						if (!(e is Player) && !(e.gameObject.tag == "Terrain"))
								e.enabled = false;
				}
		}
	
		private void PlayClip (AudioClip audio)
		{
				if (audio != null)
						shortSoundSource.PlayOneShot (audio);
				else
						shortSoundSource.Stop ();
		}
	
		public void Chase (GameObject chaser)
		{
				fear += 0.01f * Time.deltaTime;
				if (fear > 1f)
						fear = 1f;
		}
	
		public void Eat ()
		{
				hunger = 0f;
				
				PlayClip (eatSound);
		}

		public void Unscare ()
		{
				fear = 0f;
		}

		public void FlashInteraction ()
		{
				GameObject.FindGameObjectWithTag ("StatueLight").light.intensity = 7f;
				lastInteractionTime = Time.time;
				PlayClip (success);
		}

		private DayStates ComputeDayState ()
		{
				if (fear > 0.75f) 
						return DayStates.Night;
				else if (fear > .5f)
						return DayStates.Afternoon;
				else
						return DayStates.Day;
		}
		
		private void GetTiredAnimationDetails (out string name, out float delay)
		{
				if (sleep > 0.7) {
						name = "Very_Tired";
						delay = 5.0f;
				} else {
						name = "Tired";
						delay = 30.0f;
				}
		}
	
		void Update ()
		{
				if (dead) {
						if ((Time.time - deadTime) >= 0f) {
								//Debug.Log ("you are more dead, like, you know, when you die in the inside");
								deathScreen.SetActive (true);
								
								var lights = (Light[])GameObject.FindObjectsOfType (typeof(Light));
								RenderSettings.ambientLight = Color.black;
								RenderSettings.skybox = nightSky;
								foreach (var l in lights) {
										l.active = false;
								}
						}
						return;
				}
				
				if ((Time.time - lastInteractionTime) >= .1f) {
						var go = GameObject.FindGameObjectWithTag ("StatueLight");
						go.light.intensity = 0f;
				}
				if ((Time.time - hitOverlayTime) >= .3f) {
						hitOverlay.SetActive (false);
				}
				if ((Time.time - introStart) >= 3.0f) {
						introText.SetActive (false);
				}
				
				if (Input.GetKey (KeyCode.K))
						DIE ((DeathReason)Mathf.FloorToInt (UnityEngine.Random.value * 3));
				if (Input.GetKey (KeyCode.Q))
						FlashInteraction ();

				float StateController = Mathf.Max (new float[]{ sleep, hunger, fear });
				if (sleep >= 1f)
						DIE (DeathReason.Sleep);
				else if (hunger >= 1f)
						DIE (DeathReason.Hunger);
		
				var oldHungry = Hungry;
				//var oldScared = Scared;
				var oldTired = Tired;
				Hungry = Hunger > .5f;
				Scared = Fear > .5f;
				Tired = Sleep > .5f;
		
				States oldstate = MostImportantState;
				if (StateController >= 0.5f) {
						if (Hunger > Fear) {
								if (Hunger > Sleep) {
										MostImportantState = States.Hungry;
								} else {
										MostImportantState = States.Sleepy;
								}
						} else {
								if (Fear > Sleep) {
										MostImportantState = States.Scared;
								} else {
										MostImportantState = States.Sleepy;
								}
						}
				} else {
						MostImportantState = States.Base;
				}

				if (MostImportantState != oldstate) {
						GetComponent<SpawnController> ().PlayerStateChanged (this);
				}
				
				DayStates oldDayState = dayState;
				dayState = ComputeDayState ();
				if (dayState != oldDayState) {
						switch (dayState) {
						case DayStates.Day:
								ActivateDay ();
								heartBeatSource.Stop ();
								break;
						case DayStates.Afternoon:
								ActivateAfternoon ();
								heartBeatSource.clip = heartBeats [0];
								heartBeatSource.Play ();
								break;
						case DayStates.Night:
								ActivateNight ();
								heartBeatSource.clip = heartBeats [1];
								heartBeatSource.Play ();
								break;
						}
				}
		
				if (Tired) {
						var ani = getAnimation ();
						string name;
						float delay;
						
						GetTiredAnimationDetails (out name, out delay);
						
						if ((Time.time - lastTiredAnimation) > delay ||
								(oldTired != Tired)) {
								ani.Play (name);
								lastTiredAnimation = Time.time;
						}
				}
				if (Hungry) {	
						if ((Time.time - lastHungerSound) > 5f ||
								(oldHungry != Hungry)) {
								PlayClip (stomach);
								lastHungerSound = Time.time;
						}
				}
				if (oldHungry != Hungry)
						gameObject.GetComponent<LollipopController> ().SetEnabled (Hungry);
		
				gameObject.SendMessage ("SetSleepy", Tired);

				/*update hunger and self regen*/
				if (Time.time - lastSample > .5f) {
						Vector3 delta = (lastPos - this.transform.position);
						delta.y = 0;
						LastSpeeds.Add (delta.magnitude / (Time.time - lastSample));
						if (LastSpeeds.Count > 5)
								LastSpeeds.RemoveAt (0);
						lastPos = this.transform.position;
						lastSample = Time.time;
						hunger += CalculateNewHunger ();
						if (health <= 1f) {
								health += 0.001f;
								if (health > 1f)
										health = 1f;
						}
				}
				
				/*Sleepyness*/
				sleep += 0.0028f * Time.deltaTime;
				/*Gets Player Action*/

				if (Input.GetKeyDown (KeyCode.Mouse0)) {
						RaycastHit hitnfo;
						if (Physics.Raycast (this.transform.position, Camera.main.transform.forward, out hitnfo, 20f)) {
								Debug.Log ("Mouse down");
						
								GameObject go = hitnfo.collider.gameObject;
								Debug.Log ("Raycasted: " + go);
								if (go && (go.tag == "Interactible" || go.tag == "Animal")) {
										var script = go.GetComponent<IInteractible> ();
					
										if (script == null)
												throw new Exception ("What the fuck?");
										script.Interact (this);
								}
						}
				}
		
				UpdateSun ();
		}
	
		private void UpdateSun ()
		{
				var sun = GameObject.FindGameObjectWithTag ("Sun");
				if (dayState != DayStates.Night) {
						sun.light.color = new Color (1.0f, (health * health), (health * health));
				} else {
						sun.light.color = new Color ((1.0f - health) * (1.0f - health), 0f, 0f);
				}
		}
		public void DoSleep ()
		{
				getAnimation ().Play ("ALittleTired");
				PlayClip (snore);
				sleep = 0f;
		}

		List<float> LastSpeeds = new List<float> ();
		float lastSample = 0f;
		Vector3 lastPos = Vector3.zero;

		private float CalculateNewHunger ()
		{
				float toreturn = 1;
				foreach (float val in LastSpeeds) {
						toreturn *= val;
				}
				return (float)System.Math.Pow (toreturn, 1f / LastSpeeds.Count) * 0.0005f + ((health < 1f) ? (0.0009f) : (0f));
		}

		private Animation getAnimation ()
		{
				Transform cameraTransform = Camera.main.transform;
				Transform childTransform = cameraTransform.GetChild (0);
				GameObject aniGo = childTransform.gameObject;
				return aniGo.animation;
		}

		private void ActivateNight ()
		{
				RenderSettings.skybox = nightSky;
				getAnimation ().Play ("ALittleTired");
				RenderSettings.fog = true;
				RenderSettings.fogMode = FogMode.Exponential;
				RenderSettings.fogDensity = 0.05f;
				
				var sun = (Light)((GameObject)GameObject.FindGameObjectWithTag ("Sun")).GetComponent ("Light");
				sun.intensity = 1.0f;
				UpdateSun ();
		}

		private void ActivateAfternoon ()
		{
				getAnimation ().Play ("ALittleTired");
				RenderSettings.skybox = mediumSky;
				RenderSettings.fog = true;
				RenderSettings.fogMode = FogMode.Linear;
				RenderSettings.fogDensity = 0.5f;
				var sun = (Light)((GameObject)GameObject.FindGameObjectWithTag ("Sun")).GetComponent ("Light");
				sun.intensity = 0.3f;
				UpdateSun ();
		}

		private void ActivateDay ()
		{
				getAnimation ().Play ("ALittleTired");
				RenderSettings.skybox = daySky;
				RenderSettings.fog = true;
				RenderSettings.fogMode = FogMode.Linear;
				RenderSettings.fogDensity = 0.35f;
				var sun = (Light)((GameObject)GameObject.FindGameObjectWithTag ("Sun")).GetComponent ("Light");
				sun.enabled = true;
				sun.intensity = 0.6f;
				UpdateSun ();
		}
}
