// Animancer // Copyright 2020 Kybernetik //

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

namespace Animancer
{
    /// <summary>
    /// The main component through which other scripts can interact with <see cref="Animancer"/>. It allows you to play
    /// animations on an <see cref="UnityEngine.Animator"/> without using a <see cref="RuntimeAnimatorController"/>.
    /// <para></para>
    /// This class can be used as a custom yield instruction to wait until all animations finish playing.
    /// </summary>
    /// <remarks>
    /// This class is mostly just a wrapper that connects an <see cref="AnimancerPlayable"/> to an
    /// <see cref="UnityEngine.Animator"/>.
    /// </remarks>
    [AddComponentMenu(Strings.MenuPrefix + "Animancer Component")]
    [HelpURL(Strings.APIDocumentationURL + "/AnimancerComponent")]
    [DefaultExecutionOrder(-5000)]// Initialise before anything else tries to use this component.
    public class AnimancerComponent : MonoBehaviour,
        IAnimancerComponent, IEnumerable<AnimancerState>, IEnumerator, IAnimationClipSource, IAnimationClipCollection
    {
        /************************************************************************************************************************/
        #region Fields and Properties
        /************************************************************************************************************************/

        [SerializeField, Tooltip("The Animator component which this script controls")]
        private Animator _Animator;

        /// <summary>[<see cref="SerializeField"/>]
        /// The <see cref="UnityEngine.Animator"/> component which this script controls.
        /// </summary>
        public Animator Animator
        {
            get { return _Animator; }
            set
            {
#if UNITY_EDITOR
                Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, true);
                Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(value, false);
#endif

                // It doesn't seem to be possible to stop the old Animator from playing the graph.

                _Animator = value;
                if (IsPlayableInitialised)
                    _Playable.SetOutput(value, this);
            }
        }

#if UNITY_EDITOR
        /// <summary>[Editor-Only] The name of the serialized backing field for the <see cref="Animator"/> property.</summary>
        string IAnimancerComponent.AnimatorFieldName { get { return "_Animator"; } }
#endif

        /************************************************************************************************************************/

        private AnimancerPlayable _Playable;

        /// <summary>
        /// The internal system which manages the playing animations.
        /// Accessing this property will automatically initialise it.
        /// </summary>
        public AnimancerPlayable Playable
        {
            get
            {
                InitialisePlayable();
                return _Playable;
            }
        }

        /// <summary>Indicates whether the <see cref="Playable"/> has been initialised.</summary>
        public bool IsPlayableInitialised { get { return _Playable != null && _Playable.IsValid; } }

        /************************************************************************************************************************/

        /// <summary>The states managed by this component.</summary>
        public AnimancerPlayable.StateDictionary States { get { return Playable.States; } }

        /// <summary>The layers which each manage their own set of animations.</summary>
        public AnimancerPlayable.LayerList Layers { get { return Playable.Layers; } }

        /// <summary>Returns layer 0.</summary>
        public static implicit operator AnimancerLayer(AnimancerComponent animancer)
        {
            return animancer.Playable.Layers[0];
        }

        /************************************************************************************************************************/

        [SerializeField, Tooltip("Determines what happens when this component is disabled" +
            " or its GameObject becomes inactive (i.e. in OnDisable):" +
            "\n- Stop all animations" +
            "\n- Pause all animations" +
            "\n- Continue playing" +
            "\n- Reset to the original values" +
            "\n- Destroy all layers and states")]
        private DisableAction _ActionOnDisable;

        /// <summary>[<see cref="SerializeField"/>]
        /// Determines what happens when this component is disabled or its <see cref="GameObject"/> becomes inactive
        /// (i.e. in <see cref="OnDisable"/>).
        /// <para></para>
        /// The default value is <see cref="DisableAction.Stop"/>.
        /// </summary>
        public DisableAction ActionOnDisable
        {
            get { return _ActionOnDisable; }
            set { _ActionOnDisable = value; }
        }

        /// <summary>Determines whether the object will be reset to its original values when disabled.</summary>
        bool IAnimancerComponent.ResetOnDisable { get { return _ActionOnDisable == DisableAction.Reset; } }

        /// <summary>
        /// An action to perform when disabling an <see cref="AnimancerComponent"/>. See <see cref="ActionOnDisable"/>.
        /// </summary>
        public enum DisableAction
        {
            /// <summary>
            /// Stop all animations and rewind them, but leave all animated values as they are (unlike
            /// <see cref="Reset"/>).
            /// <para></para>
            /// Calls <see cref="Stop()"/> and <see cref="AnimancerPlayable.PauseGraph"/>.
            /// </summary>
            Stop,

            /// <summary>
            /// Pause all animations in their current state so they can resume later.
            /// <para></para>
            /// Calls <see cref="AnimancerPlayable.PauseGraph"/>.
            /// </summary>
            Pause,

            /// <summary>Keep playing while inactive.</summary>
            Continue,

            /// <summary>
            /// Stop all animations, rewind them, and force the object back into its original state (often called the
            /// bind pose).
            /// <para></para>
            /// WARNING: this must occur before the <see cref="UnityEngine.Animator"/> receives its <c>OnDisable</c>
            /// call, meaning the <see cref="AnimancerComponent"/> must be above it in the Inspector or on a child
            /// object so that <see cref="OnDisable"/> gets called first.
            /// <para></para>
            /// Calls <see cref="Stop()"/>, <see cref="Animator.Rebind"/>, and <see cref="AnimancerPlayable.PauseGraph"/>.
            /// </summary>
            Reset,

            /// <summary>
            /// Destroy the <see cref="PlayableGraph"/> and all its layers and states. This means that any layers or
            /// states referenced by other scripts will no longer be valid so they will need to be recreated if you
            /// want to use this object again.
            /// <para></para>
            /// Calls <see cref="AnimancerPlayable.Destroy()"/>.
            /// </summary>
            Destroy,
        }

        /************************************************************************************************************************/
        #region Update Mode
        /************************************************************************************************************************/

        /// <summary>
        /// Determines when animations are updated and which time source is used. This property is mainly a wrapper
        /// around the <see cref="Animator.updateMode"/>.
        /// <para></para>
        /// Note that changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at runtime has no effect.
        /// </summary>
        /// <exception cref="NullReferenceException">Thrown if no <see cref="Animator"/> is assigned.</exception>
        public AnimatorUpdateMode UpdateMode
        {
            get { return _Animator.updateMode; }
            set
            {
                _Animator.updateMode = value;

                if (!IsPlayableInitialised)
                    return;

                // UnscaledTime on the Animator is actually identical to Normal when using the Playables API so we need
                // to set the graph's DirectorUpdateMode to determine how it gets its delta time.
                _Playable.UpdateMode = value == AnimatorUpdateMode.UnscaledTime ?
                    DirectorUpdateMode.UnscaledGameTime :
                    DirectorUpdateMode.GameTime;

#if UNITY_EDITOR
                if (InitialUpdateMode == null)
                {
                    InitialUpdateMode = value;
                }
                else if (UnityEditor.EditorApplication.isPlaying)
                {
                    if (AnimancerPlayable.HasChangedToOrFromAnimatePhysics(InitialUpdateMode, value))
                        Debug.LogWarning("Changing the Animator.updateMode to or from AnimatePhysics at runtime will have no effect." +
                            " You must set it in the Unity Editor or on startup.");
                }
#endif
            }
        }

        /************************************************************************************************************************/
#if UNITY_EDITOR
        /************************************************************************************************************************/

        /// <summary>[Editor-Only]
        /// The <see cref="UpdateMode"/> what was first used when this script initialised.
        /// This is used to give a warning when changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at
        /// runtime since it won't work correctly.
        /// </summary>
        public AnimatorUpdateMode? InitialUpdateMode { get; private set; }

        /************************************************************************************************************************/
#endif
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Animation Events
        /************************************************************************************************************************/
        // These methods are above their regular overloads so Animation Events find them first (because the others can't be used).
        /************************************************************************************************************************/

        /// <summary>Calls <see cref="Play(AnimationClip, int)"/>.</summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void Play(AnimationEvent animationEvent)
        {
            var clip = (AnimationClip)animationEvent.objectReferenceParameter;
            var layerIndex = animationEvent.intParameter;
            if (layerIndex < 0)
                Play(clip);
            else
                Layers[layerIndex].Play(clip);
        }

        /// <summary>
        /// Calls <see cref="Play(AnimationClip, int)"/> and sets the <see cref="AnimancerState.Time"/> = 0.
        /// </summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void PlayFromStart(AnimationEvent animationEvent)
        {
            var clip = (AnimationClip)animationEvent.objectReferenceParameter;
            var layerIndex = animationEvent.intParameter;
            if (layerIndex < 0)
                Play(clip).Time = 0;
            else
                Layers[layerIndex].Play(clip).Time = 0;
        }

        /// <summary>Calls <see cref="CrossFade(AnimationClip, float, int)"/>.</summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void CrossFade(AnimationEvent animationEvent)
        {
            var clip = (AnimationClip)animationEvent.objectReferenceParameter;

            var fadeDuration = animationEvent.floatParameter;
            if (fadeDuration <= 0)
                fadeDuration = AnimancerPlayable.DefaultFadeDuration;

            var layerIndex = animationEvent.intParameter;
            if (layerIndex < 0)
                Play(clip, fadeDuration);
            else
                Layers[layerIndex].Play(clip, fadeDuration);
        }

        /// <summary>Calls <see cref="CrossFadeFromStart(AnimationClip, float, int)"/>.</summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void CrossFadeFromStart(AnimationEvent animationEvent)
        {
            var clip = (AnimationClip)animationEvent.objectReferenceParameter;

            var fadeDuration = animationEvent.floatParameter;
            if (fadeDuration <= 0)
                fadeDuration = AnimancerPlayable.DefaultFadeDuration;

            var layerIndex = animationEvent.intParameter;
            if (layerIndex < 0)
                Play(clip, fadeDuration, FadeMode.FromStart);
            else
                Layers[layerIndex].Play(clip, fadeDuration, FadeMode.FromStart);
        }

        /// <summary>Calls <see cref="Transition(ITransition, int)"/>.</summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void Transition(AnimationEvent animationEvent)
        {
            var transition = (ITransition)animationEvent.objectReferenceParameter;
            var layerIndex = animationEvent.intParameter;
            if (layerIndex < 0)
                Play(transition);
            else
                Layers[layerIndex].Play(transition);
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Invokes the <see cref="Animancerstate.Events.OnEndCallback"/> event of the
        /// <see cref="CurrentState"/> if it is playing the <see cref="AnimationClip"/> which triggered the event.
        /// <para></para>
        /// Logs a warning if no state is registered for that animation.
        /// </summary>
        /// <remarks>This method is called by Animation Events.</remarks>
        private void End(AnimationEvent animationEvent)
        {
            if (_Playable == null)
            {
                // This could only happen if another Animator triggers the event on this object somehow.
                Debug.LogWarning("AnimationEvent 'End' was triggered by " + animationEvent.animatorClipInfo.clip +
                    ", but the AnimancerComponent.Playable hasn't been initialised.",
                    this);
                return;
            }

            if (_Playable.OnEndEventReceived(animationEvent))
                return;

            if (animationEvent.messageOptions == SendMessageOptions.RequireReceiver)
            {
                Debug.LogWarning("AnimationEvent 'End' was triggered by " + animationEvent.animatorClipInfo.clip +
                    ", but no state was found with that key.",
                    this);
            }
        }

        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Initialisation
        /************************************************************************************************************************/

#if UNITY_EDITOR
        /// <summary>[Editor-Only]
        /// Called by the Unity Editor when this component is first added (in Edit Mode) and whenever the Reset command
        /// is executed from its context menu.
        /// <para></para>
        /// Destroys the playable if one has been initialised.
        /// Searches for an <see cref="UnityEngine.Animator"/> on this object, or it's children or parents.
        /// Removes the <see cref="Animator.runtimeAnimatorController"/> if it finds one.
        /// <para></para>
        /// This method also prevents you from adding multiple copies of this component to a single object. Doing so
        /// will destroy the new one immediately and change the old one's type to match the new one, allowing you to
        /// change the type without losing the values of any serialized fields they share.
        /// </summary>
        protected virtual void Reset()
        {
            OnDestroy();

            _Animator = Editor.AnimancerEditorUtilities.GetComponentInHierarchy<Animator>(gameObject);

            if (_Animator != null)
            {
                _Animator.runtimeAnimatorController = null;
                Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, false);

                // Collapse the Animator property because the custom Inspector uses that to control whether the
                // Animator's Inspector is expanded.
                using (var serializedObject = new UnityEditor.SerializedObject(this))
                {
                    var property = serializedObject.FindProperty("_Animator");
                    property.isExpanded = false;
                    serializedObject.ApplyModifiedProperties();
                }
            }

            AnimancerUtilities.IfMultiComponentThenChangeType(this);
        }
#endif

        /************************************************************************************************************************/

        /// <summary>
        /// Called by Unity when this component becomes enabled and active.
        /// <para></para>
        /// Ensures that the <see cref="PlayableGraph"/> is playing.
        /// </summary>
        protected virtual void OnEnable()
        {
            if (IsPlayableInitialised)
                _Playable.UnpauseGraph();
        }

        /// <summary>
        /// Called by Unity when this component becomes disabled or inactive. Acts according to the
        /// <see cref="ActionOnDisable"/>.
        /// </summary>
        protected virtual void OnDisable()
        {
            if (!IsPlayableInitialised)
                return;

            switch (_ActionOnDisable)
            {
                case DisableAction.Stop:
                    Stop();
                    _Playable.PauseGraph();
                    break;

                case DisableAction.Pause:
                    _Playable.PauseGraph();
                    break;

                case DisableAction.Continue:
                    break;

                case DisableAction.Reset:
                    Debug.Assert(_Animator.isActiveAndEnabled,
                        "DisableAction.Reset failed because the Animator is not enabled." +
                        " This most likely means you are disabling the GameObject and the Animator is above the" +
                        " AnimancerComponent in the Inspector so it got disabled right before this method was called." +
                        " See the Inspector of " + this + " to fix the issue or use DisableAction.Stop and call Animator.Rebind" +
                        " manually before disabling the GameObject.",
                        this);

                    Stop();
                    _Animator.Rebind();
                    _Playable.PauseGraph();
                    break;

                case DisableAction.Destroy:
                    _Playable.Destroy();
                    _Playable = null;
                    break;

                default:
                    throw new ArgumentOutOfRangeException("ActionOnDisable");
            }
        }

        /************************************************************************************************************************/

        /// <summary>Creates a new <see cref="AnimancerPlayable"/> if it doesn't already exist.</summary>
        private void InitialisePlayable()
        {
            if (IsPlayableInitialised)
                return;

#if UNITY_EDITOR
            var currentEvent = Event.current;
            if (currentEvent != null && (currentEvent.type == EventType.Layout || currentEvent.type == EventType.Repaint))
                Debug.LogWarning("Creating an AnimancerPlayable during a " + currentEvent.type + " event is likely undesirable.");
#endif

            if (_Animator == null)
                _Animator = GetComponent<Animator>();

            AnimancerPlayable.SetNextGraphName(name + ".Animancer");
            _Playable = AnimancerPlayable.Create();
            _Playable.SetOutput(_Animator, this);

#if UNITY_EDITOR
            if (_Animator != null)
                InitialUpdateMode = UpdateMode;
#endif
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Called by Unity when this component is destroyed.
        /// Ensures that the <see cref="Playable"/> is properly cleaned up.
        /// </summary>
        protected virtual void OnDestroy()
        {
            if (IsPlayableInitialised)
            {
                _Playable.Destroy();
                _Playable = null;
            }
        }

        /************************************************************************************************************************/

#if UNITY_EDITOR
        /// <summary>[Editor-Only]
        /// Ensures that the <see cref="PlayableGraph"/> is destroyed.
        /// </summary>
        ~AnimancerComponent()
        {
            if (_Playable != null)
                Editor.AnimancerEditorUtilities.EditModeDelayCall(OnDestroy);
        }
#endif

        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Play Management
        /************************************************************************************************************************/

        /// <summary>
        /// Returns the `clip` itself. This method is used to determine the dictionary key to use for an animation
        /// when none is specified by the user, such as in <see cref="Play(AnimationClip)"/>. It can be overridden by
        /// child classes to use something else as the key.
        /// </summary>
        public virtual object GetKey(AnimationClip clip)
        {
            return clip;
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Stops all other animations, plays the `clip`, and returns its state.
        /// <para></para>
        /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
        /// To restart it from the beginning you can use <c>...Play(clip, layerIndex).Time = 0;</c>.
        /// </summary>
        public AnimancerState Play(AnimationClip clip)
        {
            return Play(States.GetOrCreate(clip));
        }

        /// <summary>
        /// Stops all other animations, plays the `state`, and returns it.
        /// <para></para>
        /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
        /// To restart it from the beginning you can use <c>...Play(state).Time = 0;</c>.
        /// </summary>
        public AnimancerState Play(AnimancerState state)
        {
            return Playable.Play(state);
        }

        /// <summary>
        /// Creates a state for the `transition` if it didn't already exist, then calls
        /// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
        /// depending on <see cref="ITransition.CrossFadeFromStart"/>.
        /// </summary>
        public AnimancerState Play(ITransition transition)
        {
            return Playable.Play(transition);
        }

        /// <summary>
        /// Stops all other animations, plays the animation registered with the `key`, and returns that
        /// state. If no state is registered with the `key`, this method does nothing and returns null.
        /// <para></para>
        /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
        /// To restart it from the beginning you can use <c>...Play(key).Time = 0;</c>.
        /// </summary>
        public AnimancerState Play(object key)
        {
            return Playable.Play(key);
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Starts fading in the `clip` over the course of the `fadeDuration` while fading out all others in the same
        /// layer. Returns its state.
        /// <para></para>
        /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
        /// method will allow it to complete the existing fade rather than starting a slower one.
        /// <para></para>
        /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
        /// and simply <see cref="AnimancerState.Play(AnimationClip)"/> the `clip`.
        /// <para></para>
        /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
        /// </summary>
        public AnimancerState Play(AnimationClip clip, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
        {
            return Play(States.GetOrCreate(clip), fadeDuration, mode);
        }

        /// <summary>
        /// Starts fading in the `state` over the course of the `fadeDuration` while fading out all others in the same
        /// layer. Returns the `state`.
        /// <para></para>
        /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
        /// method will allow it to complete the existing fade rather than starting a slower one.
        /// <para></para>
        /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
        /// and simply <see cref="AnimancerState.Play(AnimancerState)"/> the `state`.
        /// <para></para>
        /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
        /// </summary>
        public AnimancerState Play(AnimancerState state, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
        {
            return Playable.Play(state, fadeDuration, mode);
        }

        /// <summary>
        /// Creates a state for the `transition` if it didn't already exist, then calls
        /// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
        /// depending on <see cref="ITransition.CrossFadeFromStart"/>.
        /// </summary>
        public AnimancerState Play(ITransition transition, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
        {
            return Playable.Play(transition, fadeDuration, mode);
        }

        /// <summary>
        /// Starts fading in the animation registered with the `key` over the course of the `fadeDuration` while fading
        /// out all others in the same layer. Returns the animation's state (or null if none was registered).
        /// <para></para>
        /// If the state was already playing and fading in with less time remaining than the `fadeDuration`, this
        /// method will allow it to complete the existing fade rather than starting a slower one.
        /// <para></para>
        /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
        /// and simply <see cref="AnimancerState.Play(AnimancerState)"/> the state.
        /// <para></para>
        /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
        /// </summary>
        public AnimancerState Play(object key, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
        {
            return Playable.Play(key, fadeDuration, mode);
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Gets the state associated with the `clip`, stops and rewinds it to the start, then returns it.
        /// </summary>
        public AnimancerState Stop(AnimationClip clip)
        {
            return Stop(GetKey(clip));
        }

        /// <summary>
        /// Gets the state registered with the <see cref="IHasKey.Key"/>, stops and rewinds it to the start, then
        /// returns it.
        /// </summary>
        public AnimancerState Stop(IHasKey hasKey)
        {
            if (_Playable != null)
                return _Playable.Stop(hasKey);
            else
                return null;
        }

        /// <summary>
        /// Gets the state associated with the `key`, stops and rewinds it to the start, then returns it.
        /// </summary>
        public AnimancerState Stop(object key)
        {
            if (_Playable != null)
                return _Playable.Stop(key);
            else
                return null;
        }

        /// <summary>
        /// Stops all animations and rewinds them to the start.
        /// </summary>
        public void Stop()
        {
            if (_Playable != null)
                _Playable.Stop();
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Returns true if a state is registered for the `clip` and it is currently playing.
        /// <para></para>
        /// The actual dictionary key is determined using <see cref="GetKey"/>.
        /// </summary>
        public bool IsPlaying(AnimationClip clip)
        {
            return IsPlaying(GetKey(clip));
        }

        /// <summary>
        /// Returns true if a state is registered with the <see cref="IHasKey.Key"/> and it is currently playing.
        /// </summary>
        public bool IsPlaying(IHasKey hasKey)
        {
            if (_Playable != null)
                return _Playable.IsPlaying(hasKey);
            else
                return false;
        }

        /// <summary>
        /// Returns true if a state is registered with the `key` and it is currently playing.
        /// </summary>
        public bool IsPlaying(object key)
        {
            if (_Playable != null)
                return _Playable.IsPlaying(key);
            else
                return false;
        }

        /// <summary>
        /// Returns true if at least one animation is being played.
        /// </summary>
        public bool IsPlaying()
        {
            if (_Playable != null)
                return _Playable.IsPlaying();
            else
                return false;
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Returns true if the `clip` is currently being played by at least one state.
        /// <para></para>
        /// This method is inefficient because it searches through every state to find any that are playing the `clip`,
        /// unlike <see cref="IsPlaying(AnimationClip)"/> which only checks the state registered using the `clip`s key.
        /// </summary>
        public bool IsPlayingClip(AnimationClip clip)
        {
            if (_Playable != null)
                return _Playable.IsPlayingClip(clip);
            else
                return false;
        }

        /************************************************************************************************************************/

        /// <summary>
        /// Evaluates all of the currently playing animations to apply their states to the animated objects.
        /// </summary>
        public void Evaluate()
        {
            Playable.Evaluate();
        }

        /// <summary>
        /// Advances all currently playing animations by the specified amount of time (in seconds) and evaluates the
        /// graph to apply their states to the animated objects.
        /// </summary>
        public void Evaluate(float deltaTime)
        {
            Playable.Evaluate(deltaTime);
        }

        /************************************************************************************************************************/
        #region Key Error Methods
#if UNITY_EDITOR
        /************************************************************************************************************************/
        // These are overloads of other methods that take a System.Object key to ensure the user doesn't try to use an
        // AnimancerState as a key, since the whole point of a key is to identify a state in the first place.
        /************************************************************************************************************************/

        /// <summary>[Warning]
        /// You should not use an <see cref="AnimancerState"/> as a key.
        /// Just call <see cref="AnimancerState.Stop"/>.
        /// </summary>
        [Obsolete("You should not use an AnimancerState as a key. Just call AnimancerState.Stop().", true)]
        public AnimancerState Stop(AnimancerState key)
        {
            key.Stop();
            return key;
        }

        /// <summary>[Warning]
        /// You should not use an <see cref="AnimancerState"/> as a key.
        /// Just check <see cref="AnimancerState.IsPlaying"/>.
        /// </summary>
        [Obsolete("You should not use an AnimancerState as a key. Just check AnimancerState.IsPlaying.", true)]
        public bool IsPlaying(AnimancerState key)
        {
            return key.IsPlaying;
        }

        /************************************************************************************************************************/
#endif
        #endregion
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Enumeration
        /************************************************************************************************************************/
        // IEnumerable for 'foreach' statements.
        /************************************************************************************************************************/

        /// <summary>
        /// Returns an enumerator that will iterate through all states in each layer (not states inside mixers).
        /// </summary>
        public IEnumerator<AnimancerState> GetEnumerator()
        {
            if (!IsPlayableInitialised)
                yield break;

            foreach (var state in _Playable.Layers.GetAllStateEnumerable())
                yield return state;
        }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        /************************************************************************************************************************/
        // IEnumerator for yielding in a coroutine to wait until all animations have stopped.
        /************************************************************************************************************************/

        /// <summary>
        /// Determines if any animations are still playing so this object can be used as a custom yield instruction.
        /// </summary>
        bool IEnumerator.MoveNext()
        {
            if (!IsPlayableInitialised)
                return false;

            return ((IEnumerator)_Playable).MoveNext();
        }

        /// <summary>Returns null.</summary>
        object IEnumerator.Current { get { return null; } }

#pragma warning disable UNT0006 // Incorrect message signature.
        /// <summary>Does nothing.</summary>
        void IEnumerator.Reset() { }
#pragma warning restore UNT0006 // Incorrect message signature.

        /************************************************************************************************************************/

        /// <summary>[<see cref="IAnimationClipSource"/>]
        /// Calls <see cref="GatherAnimationClips(ICollection{AnimationClip})"/>.
        /// </summary>
        public void GetAnimationClips(List<AnimationClip> clips)
        {
            var set = ObjectPool.AcquireSet<AnimationClip>();
            set.UnionWith(clips);

            GatherAnimationClips(set);

            clips.Clear();
            clips.AddRange(set);

            ObjectPool.Release(set);
        }

        /// <summary>[<see cref="IAnimationClipCollection"/>]
        /// Gathers all the animations in the <see cref="Playable"/>.
        /// <para></para>
        /// In the Unity Editor this method also gathers animations from other components on parent and child objects.
        /// </summary>
        public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
        {
            if (IsPlayableInitialised)
                _Playable.GatherAnimationClips(clips);

#if UNITY_EDITOR
            Editor.AnimationGatherer.GatherFromGameObject(gameObject, clips);

            if (_Animator != null && _Animator.gameObject != gameObject)
                Editor.AnimationGatherer.GatherFromGameObject(_Animator.gameObject, clips);
#endif
        }

        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
    }
}