// FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023-2025 Kybernetik //

using FlexiMotion.Jobs;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;

namespace FlexiMotion.Modifiers
{
    /// <summary>A <see cref="FlexiMotionModifier"/> which accounts for scaled objects.</summary>
    /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Modifiers/ScaleModifier
    /// 
    [AddComponentMenu(FMStrings.ModifiersMenuPrefix + "Scale Modifier")]
    [HelpURL(FMStrings.DocsURLs.ModifiersAPIDocumentation + "/" + nameof(ScaleModifier))]
    [DefaultExecutionOrder(1000)]// Initialize after colliders.
    public class ScaleModifier : FlexiMotionModifier,
        JobModifierGroup.IOnInitialize,
        JobModifierGroup.IPreUpdate
    {
        /************************************************************************************************************************/

        /// <summary>Execute before other things that might need the scaled values.</summary>
        public const int ExecutionTime = -2000;

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

        [SerializeField, Tooltip("Colliders that will have their values scaled.")]
        private SphereCollidersModifier _SphereColliders;

        /// <summary>Colliders that will have their values scaled.</summary>
        public ref SphereCollidersModifier SphereColliders => ref _SphereColliders;

        [SerializeField, Tooltip("Does the scale change after initialization?")]
        private bool _IsDynamic;

        /// <summary>Does the scale change after initialization?</summary>
        public bool IsDynamic
        {
            get => _IsDynamic;
            set
            {
                if (_IsDynamic == value)
                    return;

                if (Target != null &&
                    Target.Runtime != null)
                {
                    if (_IsDynamic)
                    {
                        if (Target.Modifiers.PreUpdate.Remove(ExecutionTime))
                            RevertToBaseRadii();
                    }
                    else
                    {
                        Target.Modifiers.PreUpdate.Add(ExecutionTime, this);
                    }
                }

                _IsDynamic = value;
            }
        }

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

        private NativeArray<float> _BaseTailRadii;
        private NativeArray<float> _BaseColliderRadii;

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

        /// <inheritdoc/>
        protected override void OnValidate()
        {
            base.OnValidate();

            if (_SphereColliders == null)
                TryGetComponent(out _SphereColliders);

            if (_SphereColliders != null)
                Target = _SphereColliders.Target;
        }

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

        /// <inheritdoc/>
        protected override void Enable(JobModifierGroup modifiers)
        {
            base.Enable(modifiers);

            modifiers.AddOnInitialize(ExecutionTime, this);
            if (_IsDynamic)
                modifiers.PreUpdate.Add(ExecutionTime, this);
        }

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

        /// <inheritdoc/>
        protected override bool Disable(JobModifierGroup modifiers)
        {
            base.Disable(modifiers);

            RevertToBaseRadii();

            var wasEnabled = false;
            wasEnabled |= modifiers.OnInitialize.Remove(ExecutionTime);
            if (_IsDynamic)
                wasEnabled |= modifiers.PreUpdate.Remove(ExecutionTime);
            return wasEnabled;
        }

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

        private void RevertToBaseRadii()
        {
            if (_BaseTailRadii.IsCreated &&
                Target != null &&
                Target.Runtime != null &&
                Target.Runtime.RuntimeSprings.TailRadii.Length == _BaseTailRadii.Length)
            {
                Target.Runtime.RuntimeSprings.TailRadii.CopyFrom(_BaseTailRadii);
            }

            if (_BaseColliderRadii.IsCreated &&
                _SphereColliders != null &&
                _SphereColliders.Runtime != null &&
                _SphereColliders.Runtime.Radii.Length == _BaseColliderRadii.Length)
            {
                _SphereColliders.Runtime.Radii.CopyFrom(_BaseColliderRadii);
            }
        }

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

        /// <inheritdoc/>
        public virtual JobHandle OnInitialize(JobHandle dependsOn = default)
        {
            FMUtilities.CopyArray(ref _BaseTailRadii, Target.Runtime.RuntimeSprings.TailRadii);
            if (_SphereColliders != null)
                FMUtilities.CopyArray(ref _BaseColliderRadii, _SphereColliders.Runtime.Radii);

            if (_IsDynamic)
                return dependsOn;

            var job = new ScaleValues1Job(Target.Runtime.RuntimeSprings.TailRadii)
                .Schedule(Target.Runtime.RuntimeSprings.SpringTransforms, dependsOn);

            if (_SphereColliders != null)
                job = JobHandle.CombineDependencies(job,
                    new ScaleValues1Job(_SphereColliders.Runtime.Radii)
                        .Schedule(_SphereColliders.Runtime.Transforms, dependsOn));

            return job;
        }

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

        /// <inheritdoc/>
        public virtual JobHandle PreUpdate(JobHandle dependsOn = default)
        {
            var job = new ScaleValues2Job(_BaseTailRadii, Target.Runtime.RuntimeSprings.TailRadii)
                .Schedule(Target.Runtime.RuntimeSprings.SpringTransforms, dependsOn);

            if (_SphereColliders != null)
                job = JobHandle.CombineDependencies(job,
                    new ScaleValues2Job(_BaseColliderRadii, _SphereColliders.Runtime.Radii)
                        .Schedule(_SphereColliders.Runtime.Transforms, dependsOn));

            return job;
        }

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

        /// <summary>Disposes everything allocated by this modifier.</summary>
        protected virtual void OnDestroy()
        {
            if (_BaseTailRadii.IsCreated)
                _BaseTailRadii.Dispose();
            if (_BaseColliderRadii.IsCreated)
                _BaseColliderRadii.Dispose();
        }

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

