// Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //

using System.Collections.Generic;
using UnityEngine;

namespace Animancer
{
    /// <summary>A system for synchronizing the <see cref="AnimancerState.NormalizedTime"/> of certain animations.</summary>
    /// <example>
    /// <list type="number">
    /// <item>Initialize a <see cref="TimeSynchronizationGroup"/> by adding any objects you want to synchronize.</item>
    /// <item>Call any of the <see cref="StoreTime(object)"/> methods before playing a new animation.</item>
    /// <item>Call any of the <see cref="SyncTime(object)"/> methods after playing that animation.</item>
    /// </list>
    /// Example: <see href="https://kybernetik.com.au/animancer/docs/examples/directional-sprites/character-controller#synchronization">Character Controller -> Synchronization</see>
    /// </example>
    /// https://kybernetik.com.au/animancer/api/Animancer/TimeSynchronizationGroup
    /// 
    public class TimeSynchronizationGroup : HashSet<object>
    {
        /************************************************************************************************************************/

        private AnimancerComponent _Animancer;

        /// <summary>The <see cref="AnimancerComponent"/> this group is synchronizing.</summary>
        /// <remarks>
        /// This reference is not required if you always use the store and sync methods that take an
        /// <see cref="AnimancerState"/>.
        /// </remarks>
        public AnimancerComponent Animancer
        {
            get => _Animancer;
            set
            {
                _Animancer = value;
                NormalizedTime = null;
            }
        }

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

        /// <summary>The stored <see cref="AnimancerState.NormalizedTime"/> or <c>null</c> if no value was stored.</summary>
        public float? NormalizedTime { get; set; }

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

        /// <summary>Creates a new <see cref="TimeSynchronizationGroup"/> and sets its <see cref="Animancer"/>.</summary>
        public TimeSynchronizationGroup(AnimancerComponent animancer) => Animancer = animancer;

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

        /// <summary>
        /// Stores the <see cref="AnimancerState.NormalizedTime"/> of the <see cref="Animancer"/>'s current state if
        /// the `key` is in this group.
        /// </summary>
        public bool StoreTime(object key) => StoreTime(key, Animancer.States.Current);

        /// <summary>
        /// Stores the <see cref="AnimancerState.NormalizedTime"/> of the `state` if the `key` is in this group.
        /// </summary>
        public bool StoreTime(object key, AnimancerState state)
        {
            if (state != null && Contains(key))
            {
                NormalizedTime = state.NormalizedTime;
                return true;
            }
            else
            {
                NormalizedTime = null;
                return false;
            }
        }

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

        /// <summary>
        /// Applies the <see cref="NormalizedTime"/> to the <see cref="Animancer"/>'s current state if the `key` is in
        /// this group.
        /// </summary>
        public bool SyncTime(object key) => SyncTime(key, Time.deltaTime);

        /// <summary>
        /// Applies the <see cref="NormalizedTime"/> to the <see cref="Animancer"/>'s current state if the `key` is in
        /// this group.
        /// </summary>
        public bool SyncTime(object key, float deltaTime) => SyncTime(key, Animancer.States.Current, deltaTime);

        /// <summary>Applies the <see cref="NormalizedTime"/> to the `state` if the `key` is in this group.</summary>
        public bool SyncTime(object key, AnimancerState state) => SyncTime(key, state, Time.deltaTime);

        /// <summary>Applies the <see cref="NormalizedTime"/> to the `state` if the `key` is in this group.</summary>
        public bool SyncTime(object key, AnimancerState state, float deltaTime)
        {
            if (NormalizedTime == null ||
                state == null ||
                !Contains(key))
                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.Time = NormalizedTime.Value * state.Length + deltaTime * state.EffectiveSpeed;
            return true;
        }

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