// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Animancer
{
    /// A set of up/right/down/left animations.
    /// 
    /// Documentation:
    /// 
    /// Directional Animation Sets
    /// 
    /// https://kybernetik.com.au/animancer/api/Animancer/DirectionalAnimationSet
    /// 
    [CreateAssetMenu(
        menuName = Strings.MenuPrefix + "Directional Animation Set/4 Directions",
        order = Strings.AssetMenuOrder + 3)]
    [AnimancerHelpUrl(typeof(DirectionalAnimationSet))]
    public class DirectionalAnimationSet : ScriptableObject,
        IAnimationClipSource
    {
        /************************************************************************************************************************/
        [SerializeField]
        private AnimationClip _Up;
        /// [] The animation facing up (0, 1).
        ///  was not called before setting this value.
        public AnimationClip Up
        {
            get => _Up;
            set
            {
                AssertCanSetClips();
                _Up = value;
                AnimancerUtilities.SetDirty(this);
            }
        }
        /************************************************************************************************************************/
        [SerializeField]
        private AnimationClip _Right;
        /// [] The animation facing right (1, 0).
        ///  was not called before setting this value.
        public AnimationClip Right
        {
            get => _Right;
            set
            {
                AssertCanSetClips();
                _Right = value;
                AnimancerUtilities.SetDirty(this);
            }
        }
        /************************************************************************************************************************/
        [SerializeField]
        private AnimationClip _Down;
        /// [] The animation facing down (0, -1).
        ///  was not called before setting this value.
        public AnimationClip Down
        {
            get => _Down;
            set
            {
                AssertCanSetClips();
                _Down = value;
                AnimancerUtilities.SetDirty(this);
            }
        }
        /************************************************************************************************************************/
        [SerializeField]
        private AnimationClip _Left;
        /// [] The animation facing left (-1, 0).
        ///  was not called before setting this value.
        public AnimationClip Left
        {
            get => _Left;
            set
            {
                AssertCanSetClips();
                _Left = value;
                AnimancerUtilities.SetDirty(this);
            }
        }
        /************************************************************************************************************************/
#if UNITY_ASSERTIONS
        private bool _AllowSetClips;
#endif
        /// [Assert-Only]
        /// Determines whether the  properties are allowed to be set.
        /// 
        [System.Diagnostics.Conditional(Strings.Assertions)]
        public void AllowSetClips(bool allow = true)
        {
#if UNITY_ASSERTIONS
            _AllowSetClips = allow;
#endif
        }
        /// [Assert-Only]
        /// Throws an  if  wasn't called.
        /// 
        [System.Diagnostics.Conditional(Strings.Assertions)]
        public void AssertCanSetClips()
        {
#if UNITY_ASSERTIONS
            AnimancerUtilities.Assert(_AllowSetClips,
                $"{nameof(AllowSetClips)}() must be called before attempting to set any of" +
                $" the animations in a {nameof(DirectionalAnimationSet)}" +
                $" to ensure that they are not changed accidentally.");
#endif
        }
        /************************************************************************************************************************/
        /// Returns the animation closest to the specified `direction`.
        public virtual AnimationClip GetClip(Vector2 direction)
        {
            if (direction.x >= 0)
            {
                if (direction.y >= 0)
                    return direction.x > direction.y ? _Right : _Up;
                else
                    return direction.x > -direction.y ? _Right : _Down;
            }
            else
            {
                if (direction.y >= 0)
                    return direction.x < -direction.y ? _Left : _Up;
                else
                    return direction.x < direction.y ? _Left : _Down;
            }
        }
        /************************************************************************************************************************/
        #region Directions
        /************************************************************************************************************************/
        /// The number of animations in this set.
        public virtual int ClipCount
            => 4;
        /************************************************************************************************************************/
        /// Up, Right, Down, or Left.
        /// 
        /// Documentation:
        /// 
        /// Directional Animation Sets
        /// 
        /// https://kybernetik.com.au/animancer/api/Animancer/Direction
        /// 
        public enum Direction
        {
            /// .
            Up,
            /// .
            Right,
            /// .
            Down,
            /// .
            Left,
        }
        /************************************************************************************************************************/
        /// Returns the name of the specified `direction`.
        protected virtual string GetDirectionName(int direction)
            => ((Direction)direction).ToString();
        /************************************************************************************************************************/
        /// Returns the animation associated with the specified `direction`.
        public AnimationClip GetClip(Direction direction)
            => direction switch
            {
                Direction.Up => _Up,
                Direction.Right => _Right,
                Direction.Down => _Down,
                Direction.Left => _Left,
                _ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction),
            };
        /// Returns the animation associated with the specified `direction`.
        public virtual AnimationClip GetClip(int direction)
            => GetClip((Direction)direction);
        /************************************************************************************************************************/
        /// Sets the animation associated with the specified `direction`.
        public void SetClip(Direction direction, AnimationClip clip)
        {
            switch (direction)
            {
                case Direction.Up: Up = clip; break;
                case Direction.Right: Right = clip; break;
                case Direction.Down: Down = clip; break;
                case Direction.Left: Left = clip; break;
                default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
            }
        }
        /// Sets the animation associated with the specified `direction`.
        public virtual void SetClip(int direction, AnimationClip clip) => SetClip((Direction)direction, clip);
        /************************************************************************************************************************/
        /// [Editor-Only]
        /// Attempts to assign the `clip` to one of this set's fields based on its name and
        /// returns the direction index of that field (or -1 if it was unable to determine the direction).
        /// 
        public virtual int SetClipByName(AnimationClip clip)
        {
            var name = clip.name;
            int bestDirection = -1;
            int bestDirectionIndex = -1;
            var directionCount = ClipCount;
            for (int i = 0; i < directionCount; i++)
            {
                var index = name.LastIndexOf(GetDirectionName(i));
                if (bestDirectionIndex < index)
                {
                    bestDirectionIndex = index;
                    bestDirection = i;
                }
            }
            if (bestDirection >= 0)
                SetClip(bestDirection, clip);
            return bestDirection;
        }
        /************************************************************************************************************************/
        #region Conversion
        /************************************************************************************************************************/
        /// Returns a vector representing the specified `direction`.
        public static Vector2 DirectionToVector(Direction direction)
            => direction switch
            {
                Direction.Up => Vector2.up,
                Direction.Right => Vector2.right,
                Direction.Down => Vector2.down,
                Direction.Left => Vector2.left,
                _ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction),
            };
        /// Returns a vector representing the specified `direction`.
        public virtual Vector2 GetDirection(int direction)
            => DirectionToVector((Direction)direction);
        /************************************************************************************************************************/
        /// Returns the direction closest to the specified `vector`.
        public static Direction VectorToDirection(Vector2 vector)
        {
            if (vector.x >= 0)
            {
                if (vector.y >= 0)
                    return vector.x > vector.y ? Direction.Right : Direction.Up;
                else
                    return vector.x > -vector.y ? Direction.Right : Direction.Down;
            }
            else
            {
                if (vector.y >= 0)
                    return vector.x < -vector.y ? Direction.Left : Direction.Up;
                else
                    return vector.x < vector.y ? Direction.Left : Direction.Down;
            }
        }
        /************************************************************************************************************************/
        /// Returns a copy of the `vector` pointing in the closest direction this set type has an animation for.
        public static Vector2 SnapVectorToDirection(Vector2 vector)
        {
            var magnitude = vector.magnitude;
            var direction = VectorToDirection(vector);
            vector = DirectionToVector(direction) * magnitude;
            return vector;
        }
        /// Returns a copy of the `vector` pointing in the closest direction this set has an animation for.
        public virtual Vector2 Snap(Vector2 vector)
            => SnapVectorToDirection(vector);
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Collections
        /************************************************************************************************************************/
        /// Adds all animations from this set to the `clips`, starting from the specified `index`.
        public void AddClips(AnimationClip[] clips, int index)
        {
            var count = ClipCount;
            for (int i = 0; i < count; i++)
                clips[index + i] = GetClip(i);
        }
        /// [] Adds all animations from this set to the `clips`.
        public void GetAnimationClips(List clips)
        {
            var count = ClipCount;
            for (int i = 0; i < count; i++)
                clips.Add(GetClip(i));
        }
        /************************************************************************************************************************/
        /// 
        /// Adds unit vectors corresponding to each of the animations in this set to the `directions`, starting from
        /// the specified `index`.
        /// 
        public void AddDirections(Vector2[] directions, int index)
        {
            var count = ClipCount;
            for (int i = 0; i < count; i++)
                directions[index + i] = GetDirection(i);
        }
        /************************************************************************************************************************/
        /// Calls  and .
        public void AddClipsAndDirections(AnimationClip[] clips, Vector2[] directions, int index)
        {
            AddClips(clips, index);
            AddDirections(directions, index);
        }
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
    }
}