// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System.Collections.Generic;
using UnityEngine;
namespace Animancer
{
    /// 
    /// A system for synchronizing the 
    /// of animations within the same "group".
    /// 
    /// 
    /// 
    /// 
    /// - Store a  in a field.///
- Call any of the  methods before playing a new animation.///
- Then call any of the  methods after playing the animation.///
/// Sample:
    /// 
    /// Character Controller -> Synchronization
    ///
    /// // 1. Define your group type.
    /// // You could use strings or ints or whatever you want, but enums are often best.
    /// public enum AnimationGroup
    /// {
    ///     None,
    ///     Movement,
    /// }
    /// 
    /// [SerializeField] private AnimancerComponent _Animancer;
    /// 
    /// // 2. Store a TimeSynchronizer in a field.
    /// private readonly TimeSynchronizer<AnimationGroup>
    ///     TimeSynchronizer = new();
    /// 
    /// public AnimancerState Play(AnimationClip clip, AnimationGroup group)
    /// {
    ///     // 3. Call one of the StoreTime methods before playing a new animation.
    ///     TimeSynchronizer.StoreTime(_Animancer);
    ///     
    ///     // 4. Play an animation.
    ///     var state = _Animancer.Play(clip);
    ///     
    ///     // 5. Call one of the SyncTime methods after playing the animation.
    ///     // If the `group` was the same as the value from last time you called it,
    ///     // then the state's NormalizedTime will be set to the stored value.
    ///     TimeSynchronizer.SyncTime(state, group);
    ///     
    ///     return state;
    /// }
    /// 
    /// 
    /// https://kybernetik.com.au/animancer/api/Animancer/TimeSynchronizer_1
    /// 
    public class TimeSynchronizer
    {
        /************************************************************************************************************************/
        /// The group that the current animation is in.
        public T CurrentGroup { get; set; }
        /// Should synchronization be applied when the  is at its default value?
        /// This is false by default so that the default group represents "ungrouped".
        public bool SynchronizeDefaultGroup { get; set; }
        /// The state which the  came from (to avoid syncing with itself).
        public AnimancerState State { get; set; }
        /// The stored .
        public double NormalizedTime { get; set; } = double.NaN;
        /************************************************************************************************************************/
        /// Creates a new .
        public TimeSynchronizer()
        { }
        /// Creates a new .
        public TimeSynchronizer(T group, bool synchronizeDefaultGroup = false)
        {
            CurrentGroup = group;
            SynchronizeDefaultGroup = synchronizeDefaultGroup;
        }
        /************************************************************************************************************************/
        /// 
        /// Stores the  of the .
        /// 
        public void StoreTime(AnimancerLayer layer)
            => StoreTime(layer.CurrentState);
        /// Stores the  of the `state`.
        public void StoreTime(AnimancerState state)
            => StoreTime(state, state != null ? state.NormalizedTimeD : double.NaN);
        /************************************************************************************************************************/
        /// Sets the  and .
        public void StoreTime(AnimancerState state, double normalizedTime)
        {
            State = state;
            NormalizedTime = normalizedTime;
        }
        /************************************************************************************************************************/
        /// 
        /// Applies the  to the 
        /// if the `group` matches the .
        /// 
        public bool SyncTime(AnimancerLayer layer, T group)
            => SyncTime(layer.CurrentState, group, Time.deltaTime);
        /// 
        /// Applies the  to the 
        /// if the `group` matches the .
        /// 
        public bool SyncTime(AnimancerLayer layer, T group, float deltaTime)
            => SyncTime(layer.CurrentState, group, deltaTime);
        /// 
        /// Applies the  to the `state`
        /// if the `group` matches the .
        /// 
        public bool SyncTime(AnimancerState state, T group)
            => SyncTime(state, group, Time.deltaTime);
        /// 
        /// Applies the  to the `state`
        ///  and returns true if the `group` matches the .
        /// 
        /// 
        /// If the `state` is the same one the time was stored from, this method does nothing and returns false.
        /// 
        public bool SyncTime(AnimancerState state, T group, float deltaTime)
        {
            if (state == null ||
                state == State ||
                double.IsNaN(NormalizedTime) ||
                !EqualityComparer.Default.Equals(CurrentGroup, group) ||
                (!SynchronizeDefaultGroup && EqualityComparer.Default.Equals(default, group)))
            {
                CurrentGroup = group;
                return false;
            }
            // Setting the Time forces it to stay at that value after the next animation update.
            // But we actually want it to keep playing, so we need to add deltaTime manually.
            state.MoveTime(NormalizedTime * state.Length + deltaTime * state.EffectiveSpeed, false);
            return true;
        }
        /************************************************************************************************************************/
    }
}