using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Mixamo {
	
	public interface TransitionHandler {
		string[] KeyControls();
		bool CanTransitionTo( string guard , string source , string destination );
	}
	
	/// <summary>
	/// A class for holding all the control parameters (weights that control blend trees) in the graph.
	/// This also contains a paramter "state_speed" to control the playback speed of a state.
	/// </summary>
	public class ControlParameters {
		
		Dictionary< string , float> dict = new Dictionary<string, float>();
		
		public ControlParameters(string[] opts,float[] vals) {
			if( opts != null && vals != null ) {
				for( int i=0 ;i < opts.Length;i++ ) {
					dict.Add( opts[i] , vals[i] );
				}
			}
		}
		
		public float this[string s]
		{
		    get { return dict[s]; }
		    set { 
				dict[s] = value;
			}
		}
		
		/// <summary>
		/// Returns a list of all control paramters
		/// </summary>
		public string[] Names {
			get {
				string[] arr = new string[dict.Keys.Count];
				dict.Keys.CopyTo( arr , 0 );
				return arr;
			}
		}
	}
	
	/// <summary>
	/// Base class that describes a Transition. All other transitions must override this class.
	/// </summary>
	public abstract class Transition  {
		
		public bool WaitTillEnd = false;
		protected State destination;
		private State _start_destination = null;
		protected string[] guards;
		protected State start_destination {
			get {
				return _start_destination;
			}
			set {
				_start_destination = value;
			}
		}
		
		/// <summary>
		/// The state where the transition want's to go to.
		/// </summary>
		public State Destination {
			get {
				return this.destination;
			}
		}
		protected State source;
		protected bool finished = true;
		
		/// <summary>
		/// Initiate the transition. Called when a transition is started from ChangeState().
		/// </summary>
		/// <param name="dest">
		/// A <see cref="State"/>
		/// </param>
		public virtual void Start(State dest) {
			finished = false;
			this.destination = dest;
			if( !this.destination.IsLooping ) {
				this.destination.ResetTime( 0f );
			}
		}
		
		/// <summary>
		/// Called when the state is changed and the transition is finished.
		/// </summary>
		public virtual void Finish(){
			
		}
		
		/// <summary>
		/// Returns whether or the transition can be taken to the destination state.
		/// </summary>
		/// <param name="dest">
		/// A <see cref="State"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		public virtual bool CanBeMade( State dest) {
			if( guards != null ) {
				foreach( string g in guards ) {
					if( source.layer.graph.TransitionHandler != null && !source.layer.graph.TransitionHandler.CanTransitionTo( g , source.name , dest.name ) ) {
						return false;
					}
				}
			}
			
			if( start_destination == null ) {
				return true;
			} else {
				return( dest == start_destination );
			}
		}
		
		/// <summary>
		/// Compute the weight of the Source state and the Destination state based on the remaining weight.
		/// </summary>
		/// <param name="remaining_weight">
		/// A <see cref="System.Single"/>
		/// </param>
		public abstract void UpdateGraph( float remaining_weight );
		
		
		/// <summary>
		/// Returns whether or not the transition has completed. Tells the state machine to set the Current State = Destination.
		/// </summary>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		public bool IsDone() {
			return finished;
		}
		
		public override string ToString ()
		{
			return string.Format ("[Transition: Source={0} Destination={1} Type={2}]", source.name , (start_destination == null ? "*" : start_destination.name) , this.GetType().ToString());
		}
	}
	
	/// <summary>
	/// Transitions between two states by crossfading between them over a duration. This means immeditately playing the next state with weight 0 and fading it in to weight 1, while
	/// fading the current state to weight 0.
	/// </summary>
	public class CrossfadeTransition : Transition {
		
		private float t_weight = 0f;
		
		public CrossfadeTransition( State source , State destination , float duration , string[] guards ) {
			this.source = source;
			this.start_destination = destination;
			this.duration = duration;
			this.guards = guards;
		}
		
		
		public override void Start(State dest) {
			base.Start(dest);
			t_weight = 0f;
			destination.ResetTime(0f);
		}
		
		private float duration = 0f;
		public override void UpdateGraph (float remaining_weight)
		{
			if( WaitTillEnd && (source.MaxTime < (source.MaxTimeLength - duration) ) ) {
				source.UpdateGraph( remaining_weight );
				destination.UpdateGraph( 0f );
				destination.ResetTime(0f);
			} else {
				t_weight = Mixamo.Util.CrossFadeUp( t_weight , this.duration );
				destination.UpdateGraph( t_weight * remaining_weight );
				source.UpdateGraph( (1-t_weight) * remaining_weight );
				
				if( t_weight >= 1f ) {
					finished = true;
				}
			}
		}
		
		public override void Finish ()
		{
		}
		
	}
	
	/// <summary>
	/// Transitions between two states by playing an inbetween clip.
	/// This transitions crossfades the current state with the inbetween clip in duration_in seconds and then crossfades the inbetween clip with the destination state in duration_out seconds.
	/// If duration_in == 0f then we wait till the source state actually finishes playing before transitioning.
	/// </summary>
	public class ClipTransition : Transition {
		public Clip clip;
		public float duration_in = 0f;
		public float duration_out = 0f;
		private float t_weight_start = 0f;
		private float t_weight_end = 0f;
		
		public ClipTransition(  Clip c , State source , State dest , float dur_in , float dur_out , string[] guards ) {
			clip = c;
			clip.anim_state.wrapMode = WrapMode.ClampForever;
			clip.anim_state.enabled = true;
			this.source = source;
			this.start_destination = dest;
			duration_in = dur_in;
			if( duration_in == 0f ) {
				WaitTillEnd = true;
			}
			duration_out = dur_out;
			this.guards = guards;
		}
		public override void Start ( State dest)
		{
			base.Start(dest);
			t_weight_start = 0f;
			t_weight_end = 0f;
			clip.ResetTime(0f);
			dest.ResetTime(0f);
			if( duration_in == 0f ) {
				source.SetCurrentWrapMode( MixamoWrapMode.ClampForever );
			}
		}
		
		public override void UpdateGraph (float remaining_weight)
		{
			
			if( WaitTillEnd && (source.NormalizedTime < 1f ) ) {
				source.UpdateGraph( remaining_weight );
				clip.UpdateGraph(0);
				destination.UpdateGraph( 0f );
				destination.ResetTime(0f);
				clip.ResetTime(0f);
			} else if( clip.anim_state.time < this.duration_in ) {
				// fade in
				t_weight_start = Mixamo.Util.CrossFadeUp( t_weight_start , this.duration_in );
				source.UpdateGraph( (1-t_weight_start) * remaining_weight );
				clip.UpdateGraph( (t_weight_start) * remaining_weight );
				destination.UpdateGraph( 0f );
				destination.ResetTime(0f);
			} else if( clip.anim_state.time > (clip.anim_state.length - this.duration_out ) && clip.anim_state.time < clip.anim_state.length ) {
				// fade out
				t_weight_end = Mixamo.Util.CrossFadeUp( t_weight_end , this.duration_out );
				source.UpdateGraph( 0f );
				clip.UpdateGraph( ( 1-t_weight_end) * remaining_weight );
				destination.UpdateGraph( t_weight_end * remaining_weight );
			} else if( clip.anim_state.time < clip.anim_state.length ) {
				// play normally
				clip.UpdateGraph( remaining_weight );
				source.UpdateGraph( 0f );
				destination.UpdateGraph( 0f );
				destination.ResetTime(0f);
			} else {
				// end
				destination.UpdateGraph( remaining_weight );
				source.UpdateGraph( 0f );
				clip.UpdateGraph( 0f );
				source.ResetWrapMode();
				finished = true;
			}
		}
	}
	
	
	/// <summary>
	/// A layer of the animation graph. Each layer can be in one state at a time. Weighting is computed by calculating a normalized weight (weights summing to 1) for each layer and then using
	/// Unity's weight calculation to average each layer depending on it's layer priority and any MixingTransforms on the individual bone.
	/// </summary>
	public class Layer {
		public string name;
		public State[] states;
		public AnimationGraph graph;
		public int priority;
		public Transition CreateDefaultTransition(  State source  ) {
			// default transition is to crossfade to every state
			Transition t = new CrossfadeTransition( source , null , 0.1f , new string[0] {} );
			return t;
		}
		
		State _current_state;
		Transition _current_transition = null;
		
		public Layer() {
			
		}
		
		public void Init() {
			_current_state = states[0];
			_desired_state = _current_state;
		}
		
		public State GetCurrentState() {
			return _current_state;
		}
		
		public State GetCurrentDestinationState() {
			if( _current_transition != null ) {
				return _current_transition.Destination;
			} else {
				return null;
			}
		}
		
		private State _desired_state;
		public bool ChangeState( string name ) {
			State next = this.GetStateByName( name );
			if( next != null ) {
				// save this state, in case you need to transition to it immediately after a non looping state
				_desired_state = next;
			}
			if( _current_transition != null ) {
				// you can't change state if you're in a transition
				return false;
			} else if( next == null ) {
				Debug.LogError( "Could not find the state: " + name.ToString() );
				return false;
			} else if( next != _current_state ) {
				// find a transition to the next state and make it if possible
				Transition t = _current_state.GetTransitionTo( next );
				if( t != null ) {
					_current_transition = t;
					_current_transition.Start( next );
					return true;
				} else {
					return false;
				}
			} else {
				return true;
			}
		}
		
		public override string ToString ()
		{
			string str = ( "Layer: " + name + "\n" );
			str += "States: \n";
			foreach( State s in states ) {


Answer: 				str += s.ToString() + "\n";