// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //

using System;

namespace Animancer
{
    /// <summary>[Pro-Only]
    /// A callback to be triggered after an <see cref="AnimancerNode"/>
    /// either starts or finishes fading out to 0 <see cref="AnimancerNode.EffectiveWeight"/>.
    /// </summary>
    /// 
    /// <remarks>
    /// The <see cref="AnimancerNode.EffectiveWeight"/> is only checked at the end of the animation update
    /// so if it's set multiple times in the same frame then the callback might not be triggered.
    /// <para></para>
    /// Most <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">Finite State Machine</see>
    /// systems already have their own mechanism for notifying your code when a state is exited
    /// so this system is generally only useful when something like that is not already available.
    /// <para></para>
    /// <strong>Example:</strong> see the <see cref="ExitEvent(AnimancerNode, Action, bool)"/> constructor.
    /// </remarks>
    /// 
    /// https://kybernetik.com.au/animancer/api/Animancer/ExitEvent
    /// 
    public class ExitEvent : Updatable
    {
        /************************************************************************************************************************/

        private Action _Callback;

        /// <summary>The method to invoke when this event is triggered.</summary>
        public Action Callback
        {
            get => _Callback;
            set
            {
                _Callback = value;

                if (_Callback != null)
                    EnableIfActive();
                else
                    Disable();
            }
        }

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

        private AnimancerNode _Node;

        /// <summary>The target node which determines when to trigger this event.</summary>
        public AnimancerNode Node
        {
            get => _Node;
            set
            {
                _Node = value;

                if (_Node != null)
                    EnableIfActive();
                else
                    Disable();
            }
        }

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

        /// <summary>
        /// Should the <see cref="Callback"/> be invoked when the <see cref="Node"/> starts fading out?
        /// Otherwise, it will be invoked after the <see cref="AnimancerNode.EffectiveWeight"/> reaches 0.
        /// Default is <c>false</c>.
        /// </summary>
        public bool InvokeOnStartExiting { get; set; }

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

        /// <summary>Creates a new <see cref="ExitEvent"/>.</summary>
        /// 
        /// <remarks>
        /// <strong>Example:</strong><code>
        /// private ExitEvent _OnStateExited;
        /// 
        /// void ExitEventExample(AnimancerComponent animancer, AnimationClip clip)
        /// {
        ///     var state = animancer.Play(clip);
        ///     
        ///     // One line initialization:
        ///     (_OnClipExit ??= new(state, OnStateExited)).Enable();
        ///     
        ///     // Or two lines:
        ///     _OnClipExit ??= new(state, OnStateExited);
        ///     _OnClipExit.Enable();
        /// }
        /// 
        /// private void OnStateExited()
        /// {
        ///     Debug.Log(_OnClipExit.State + " Exited");
        /// }
        /// </code></remarks>
        /// 
        public ExitEvent(
            AnimancerNode node,
            Action callback,
            bool invokeOnStartExiting = false)
        {
            _Node = node;
            _Callback = Callback;
            InvokeOnStartExiting = invokeOnStartExiting;
        }

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

        /// <summary>Registers this event to start receiving updates.</summary>
        public void Enable()
        {
            if (_Callback != null)
                _Node?.Graph?.RequirePostUpdate(this);
        }

        /// <summary>
        /// Registers this event to start receiving updates if the
        /// <see cref="AnimancerNode.TargetWeight"/> is above 0 (i.e. it's not fading out).
        /// </summary>
        public void EnableIfActive()
        {
            if (_Callback != null &&
                _Node != null &&
                _Node.Graph != null &&
                _Node.TargetWeight > 0)
                _Node.Graph.RequirePostUpdate(this);
        }

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

        /// <summary>Cancels this event to stop receiving updates.</summary>
        public void Disable()
        {
            _Node?.Graph?.CancelPostUpdate(this);
        }

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

        /// <inheritdoc/>
        public override void Update()
        {
            if (!_Node.IsValid())
                return;

            if (InvokeOnStartExiting)
            {
                if (_Node.TargetWeight != 0)
                    return;
            }
            else
            {
                if (_Node.EffectiveWeight > 0)
                    return;
            }

            _Callback();
            Disable();
        }

        /************************************************************************************************************************/
    }
}

