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

using System;
using UnityEngine;

namespace Animancer
{
    /// <summary>A set of up/right/down/left animations with diagonals as well.</summary>
    /// <remarks>
    /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/playing/directional-sets">Directional Animation Sets</see>
    /// </remarks>
    /// https://kybernetik.com.au/animancer/api/Animancer/DirectionalAnimationSet8
    /// 
    [CreateAssetMenu(menuName = Strings.MenuPrefix + "Directional Animation Set/8 Directions", order = Strings.AssetMenuOrder + 11)]
    [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(DirectionalAnimationSet8))]
    public class DirectionalAnimationSet8 : DirectionalAnimationSet
    {
        /************************************************************************************************************************/

        [SerializeField]
        private AnimationClip _UpRight;

        /// <summary>[<see cref="SerializeField"/>] The animation facing diagonally up-right ~(0.7, 0.7).</summary>
        /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
        public AnimationClip UpRight
        {
            get => _UpRight;
            set
            {
                AssertCanSetClips();
                _UpRight = value;
                AnimancerUtilities.SetDirty(this);
            }
        }

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

        [SerializeField]
        private AnimationClip _DownRight;

        /// <summary>[<see cref="SerializeField"/>] The animation facing diagonally down-right ~(0.7, -0.7).</summary>
        /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
        public AnimationClip DownRight
        {
            get => _DownRight;
            set
            {
                AssertCanSetClips();
                _DownRight = value;
                AnimancerUtilities.SetDirty(this);
            }
        }

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

        [SerializeField]
        private AnimationClip _DownLeft;

        /// <summary>[<see cref="SerializeField"/>] The animation facing diagonally down-left ~(-0.7, -0.7).</summary>
        /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
        public AnimationClip DownLeft
        {
            get => _DownLeft;
            set
            {
                AssertCanSetClips();
                _DownLeft = value;
                AnimancerUtilities.SetDirty(this);
            }
        }

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

        [SerializeField]
        private AnimationClip _UpLeft;

        /// <summary>[<see cref="SerializeField"/>] The animation facing diagonally up-left ~(-0.7, 0.7).</summary>
        /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
        public AnimationClip UpLeft
        {
            get => _UpLeft;
            set
            {
                AssertCanSetClips();
                _UpLeft = value;
                AnimancerUtilities.SetDirty(this);
            }
        }

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

        /// <summary>Returns the animation closest to the specified `direction`.</summary>
        public override AnimationClip GetClip(Vector2 direction)
        {
            var angle = Mathf.Atan2(direction.y, direction.x);
            var octant = Mathf.RoundToInt(8 * angle / (2 * Mathf.PI) + 8) % 8;
            switch (octant)
            {
                case 0: return Right;
                case 1: return _UpRight;
                case 2: return Up;
                case 3: return _UpLeft;
                case 4: return Left;
                case 5: return _DownLeft;
                case 6: return Down;
                case 7: return _DownRight;
                default: throw new ArgumentOutOfRangeException("Invalid octant");
            }
        }

        /************************************************************************************************************************/
        #region Directions
        /************************************************************************************************************************/

        /// <summary>Constants for each of the diagonal directions.</summary>
        /// <remarks>
        /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/playing/directional-sets">Directional Animation Sets</see>
        /// </remarks>
        /// https://kybernetik.com.au/animancer/api/Animancer/Diagonals
        /// 
        public static class Diagonals
        {
            /************************************************************************************************************************/

            /// <summary>1 / (Square Root of 2).</summary>
            public const float OneOverSqrt2 = 0.70710678118f;

            /// <summary>A vector with a magnitude of 1 pointing up to the right.</summary>
            /// <remarks>The value is approximately (0.7, 0.7).</remarks>
            public static Vector2 UpRight => new Vector2(OneOverSqrt2, OneOverSqrt2);

            /// <summary>A vector with a magnitude of 1 pointing down to the right.</summary>
            /// <remarks>The value is approximately (0.7, -0.7).</remarks>
            public static Vector2 DownRight => new Vector2(OneOverSqrt2, -OneOverSqrt2);

            /// <summary>A vector with a magnitude of 1 pointing down to the left.</summary>
            /// <remarks>The value is approximately (-0.7, -0.7).</remarks>
            public static Vector2 DownLeft => new Vector2(-OneOverSqrt2, -OneOverSqrt2);

            /// <summary>A vector with a magnitude of 1 pointing up to the left.</summary>
            /// <remarks>The value is approximately (-0.707, 0.707).</remarks>
            public static Vector2 UpLeft => new Vector2(-OneOverSqrt2, OneOverSqrt2);

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

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

        public override int ClipCount => 8;

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

        /// <summary>Up, Right, Down, Left, or their diagonals.</summary>
        /// <remarks>
        /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/playing/directional-sets">Directional Animation Sets</see>
        /// </remarks>
        /// https://kybernetik.com.au/animancer/api/Animancer/Direction
        /// 
        public new enum Direction
        {
            /// <summary><see cref="Vector2.up"/>.</summary>
            Up,

            /// <summary><see cref="Vector2.right"/>.</summary>
            Right,

            /// <summary><see cref="Vector2.down"/>.</summary>
            Down,

            /// <summary><see cref="Vector2.left"/>.</summary>
            Left,

            /// <summary><see cref="Vector2"/>(0.7..., 0.7...).</summary>
            UpRight,

            /// <summary><see cref="Vector2"/>(0.7..., -0.7...).</summary>
            DownRight,

            /// <summary><see cref="Vector2"/>(-0.7..., -0.7...).</summary>
            DownLeft,

            /// <summary><see cref="Vector2"/>(-0.7..., 0.7...).</summary>
            UpLeft,
        }

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

        protected override string GetDirectionName(int direction) => ((Direction)direction).ToString();

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

        /// <summary>Returns the animation associated with the specified `direction`.</summary>
        public AnimationClip GetClip(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up: return Up;
                case Direction.Right: return Right;
                case Direction.Down: return Down;
                case Direction.Left: return Left;
                case Direction.UpRight: return _UpRight;
                case Direction.DownRight: return _DownRight;
                case Direction.DownLeft: return _DownLeft;
                case Direction.UpLeft: return _UpLeft;
                default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
            }
        }

        public override AnimationClip GetClip(int direction) => GetClip((Direction)direction);

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

        /// <summary>Sets the animation associated with the specified `direction`.</summary>
        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;
                case Direction.UpRight: UpRight = clip; break;
                case Direction.DownRight: DownRight = clip; break;
                case Direction.DownLeft: DownLeft = clip; break;
                case Direction.UpLeft: UpLeft = clip; break;
                default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
            }
        }

        public override void SetClip(int direction, AnimationClip clip) => SetClip((Direction)direction, clip);

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

        /// <summary>Returns a vector representing the specified `direction`.</summary>
        public static Vector2 DirectionToVector(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up: return Vector2.up;
                case Direction.Right: return Vector2.right;
                case Direction.Down: return Vector2.down;
                case Direction.Left: return Vector2.left;
                case Direction.UpRight: return Diagonals.UpRight;
                case Direction.DownRight: return Diagonals.DownRight;
                case Direction.DownLeft: return Diagonals.DownLeft;
                case Direction.UpLeft: return Diagonals.UpLeft;
                default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
            }
        }

        public override Vector2 GetDirection(int direction) => DirectionToVector((Direction)direction);

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

        /// <summary>Returns the direction closest to the specified `vector`.</summary>
        public new static Direction VectorToDirection(Vector2 vector)
        {
            var angle = Mathf.Atan2(vector.y, vector.x);
            var octant = Mathf.RoundToInt(8 * angle / (2 * Mathf.PI) + 8) % 8;
            switch (octant)
            {
                case 0: return Direction.Right;
                case 1: return Direction.UpRight;
                case 2: return Direction.Up;
                case 3: return Direction.UpLeft;
                case 4: return Direction.Left;
                case 5: return Direction.DownLeft;
                case 6: return Direction.Down;
                case 7: return Direction.DownRight;
                default: throw new ArgumentOutOfRangeException("Invalid octant");
            }
        }

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

        /// <summary>Returns a copy of the `vector` pointing in the closest direction this set type has an animation for.</summary>
        public new static Vector2 SnapVectorToDirection(Vector2 vector)
        {
            var magnitude = vector.magnitude;
            var direction = VectorToDirection(vector);
            vector = DirectionToVector(direction) * magnitude;
            return vector;
        }

        public override Vector2 Snap(Vector2 vector) => SnapVectorToDirection(vector);

        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
        #region Name Based Operations
        /************************************************************************************************************************/
#if UNITY_EDITOR
        /************************************************************************************************************************/

        public override int SetClipByName(AnimationClip clip)
        {
            var name = clip.name;

            var directionCount = ClipCount;
            for (int i = directionCount - 1; i >= 0; i--)
            {
                if (name.Contains(GetDirectionName(i)))
                {
                    SetClip(i, clip);
                    return i;
                }
            }

            return -1;
        }

        /************************************************************************************************************************/
#endif
        /************************************************************************************************************************/
        #endregion
        /************************************************************************************************************************/
    }
}