// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Animancer
{
    /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerState
    partial class AnimancerState
    {
        /************************************************************************************************************************/
        /// The system which manages the .
        private AnimancerEvent.Dispatcher _EventDispatcher;
        /************************************************************************************************************************/
        /// 
        /// Events which will be triggered while this state plays
        /// based on its .
        /// 
        /// 
        /// 
        /// This property tries to ensure that the event sequence is only referenced by this state.
        /// 
        /// - 
        /// If the reference was null,
        /// a new sequence will be created.
        /// ///
- 
        /// If a reference was assigned to ,
        /// it will be cloned so this state owns the clone.
        /// ///
/// 
        /// Using  or 
        /// is often safer than this property since they help detect if multiple scripts are using the same
        /// state which could lead to unexpected bugs if they each assign conflicting callbacks.
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        public AnimancerEvent.Sequence OwnedEvents
        {
            get
            {
                _EventDispatcher ??= new(this);
                _EventDispatcher.InitializeEvents(out var events);
                return events;
            }
            set
            {
                if (value != null)
                    (_EventDispatcher ??= new(this)).SetEvents(value, true);
                else
                    _EventDispatcher = null;
            }
        }
        /************************************************************************************************************************/
        /// 
        /// Events which will be triggered while this state plays
        /// based on its .
        /// 
        /// 
        /// 
        /// This reference is null by default and once assigned it may be shared by multiple states.
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        public AnimancerEvent.Sequence SharedEvents
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => _EventDispatcher?.Events;
            set
            {
                if (value != null)
                    (_EventDispatcher ??= new(this)).SetEvents(value, false);
                else
                    _EventDispatcher = null;
            }
        }
        /************************************************************************************************************************/
        /// Have the  or  been initialized?
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        public bool HasEvents
            => _EventDispatcher != null;
        /************************************************************************************************************************/
        /// Have the  been initialized?
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        public bool HasOwnedEvents
            => _EventDispatcher != null
            && _EventDispatcher.HasOwnEvents;
        /************************************************************************************************************************/
        /// 
        /// If the  haven't been initialized yet,
        /// this method gets them and returns true.
        /// 
        /// 
        /// 
        /// This method tries to ensure that the event sequence is only referenced by this state.
        ///
        /// - 
        /// If the reference was null,
        /// a new sequence will be created.
        /// ///
- 
        /// If a reference was assigned to ,
        /// it will be cloned so this state owns the clone.
        /// ///
/// In both of those cases, this method returns true
        /// to indicate that the caller should initialize their event callbacks.
        /// 
        /// Also calls .
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        /// Example:
        ///
        /// public static readonly StringReference EventName = "Event Name";
        /// 
        /// ...
        /// 
        /// AnimancerState state = animancerComponent.Play(animation);
        /// if (state.Events(this, out AnimancerEvent.Sequence events))
        /// {
        ///     events.SetCallback(EventName, OnAnimationEvent);
        ///     events.OnEnd = OnAnimationEnded;
        /// }
        /// 
        /// If you only need to initialize the End Event, 
        /// consider using  instead.
        /// 
        public bool Events(object owner, out AnimancerEvent.Sequence events)
        {
            AssertOwnership(owner);
            _EventDispatcher ??= new(this);
            return _EventDispatcher.InitializeEvents(out events);
        }
        /************************************************************************************************************************/
        /// 
        /// If the  haven't been initialized yet,
        /// this method gets them and returns true.
        /// 
        /// 
        /// 
        /// This method tries to ensure that the event sequence is only referenced by this state.
        /// 
        /// - 
        /// If the reference was null,
        /// a new sequence will be created.
        /// ///
- 
        /// If a reference was assigned to ,
        /// it will be cloned so this state owns the clone.
        /// ///
/// 
        /// Also calls .
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        /// Example:
        ///
        /// AnimancerState state = animancerComponent.Play(animation);
        /// state.Events(this).OnEnd ??= OnAnimationEnded;
        /// 
        /// If you need to initialize more than just the End Event, 
        /// consider using  instead.
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public AnimancerEvent.Sequence Events(object owner)
        {
            Events(owner, out var events);
            return events;
        }
        /************************************************************************************************************************/
        /// Copies the contents of the .
        private void CopyEvents(AnimancerState copyFrom, CloneContext context)
        {
            if (copyFrom._EventDispatcher != null)
            {
                var original = copyFrom._EventDispatcher.Events;
                var events = context.GetCloneOrOriginal(original);
                if (events != null)
                {
                    _EventDispatcher ??= new(this);
                    _EventDispatcher.SetEvents(events, false);
                    if (events == original)
                        copyFrom._EventDispatcher.DismissEventOwnership();
                    return;
                }
            }
            _EventDispatcher = null;
        }
        /************************************************************************************************************************/
        /// Should events be raised on a state which is currently fading out?
        /// 
        /// Default false.
        /// 
        /// Documentation:
        /// 
        /// Animancer Events
        /// 
        public static bool RaiseEventsDuringFadeOut { get; set; }
        /// [Internal] Should this state check for events to invoke?
        internal bool ShouldRaiseEvents
            => TargetWeight > 0
            || RaiseEventsDuringFadeOut;
        /************************************************************************************************************************/
        /// 
        /// Checks if any events should be invoked based on the current time of this state.
        /// 
        protected internal virtual void UpdateEvents()
            => _EventDispatcher?.UpdateEvents(ShouldRaiseEvents);
        /// 
        /// Checks if any events should be invoked on the `parent` and its children recursively.
        /// 
        public static void UpdateEventsRecursive(AnimancerState parent)
            => UpdateEventsRecursive(
                parent,
                parent.ShouldRaiseEvents);
        /// 
        /// Checks if any events should be invoked on the `parent` and its children recursively.
        /// 
        public static void UpdateEventsRecursive(AnimancerState parent, bool raiseEvents)
        {
            parent._EventDispatcher?.UpdateEvents(raiseEvents);
            for (int i = parent.ChildCount - 1; i >= 0; i--)
                UpdateEventsRecursive(parent.GetChild(i), raiseEvents);
        }
        /************************************************************************************************************************/
#if UNITY_ASSERTIONS
        /************************************************************************************************************************/
        /// [Assert-Only]
        /// Returns null if Animancer Events will work properly on this type of state,
        /// or a message explaining why they might not work.
        /// 
        protected internal virtual string UnsupportedEventsMessage
            => null;
        /************************************************************************************************************************/
        /// [Assert-Only] An optional reference to the object that owns this state.
        public object Owner { get; private set; }
        /************************************************************************************************************************/
#endif
        /************************************************************************************************************************/
        /// [Assert-Conditional]
        /// Sets the  and asserts that it wasn't already set to a different object.
        /// 
        /// This helps detect if multiple scripts attempt to manage the same state.
        [System.Diagnostics.Conditional(Strings.Assertions)]
        public void AssertOwnership(object owner)
        {
#if UNITY_ASSERTIONS
            if (Owner == owner)
                return;
            if (Owner != null)
            {
                Debug.LogError(
                    $"Multiple objects have asserted ownership over the state '{ToString()}':" +
                    $"\n• Old Owner: {AnimancerUtilities.ToStringOrNull(Owner)}" +
                    $"\n• New Owner: {AnimancerUtilities.ToStringOrNull(owner)}" +
                    $"\n• State: {GetPath()}" +
                    $"\n• Graph: {Graph?.GetDescription("\n• ")}",
                    Graph?.Component as Object);
            }
            Owner = owner;
#endif
        }
        /************************************************************************************************************************/
    }
}