using System;
using System.Collections.Generic;
using UnityEngine;

namespace pdinklag
{
    public static class Vector3Extensions
    {
        /// <summary>
        /// Compares two vectors according to their magnitudes.
        /// </summary>
        public struct MagnitudeComparer : IComparer<Vector3>
        {
            public int Compare(Vector3 x, Vector3 y)
            {
                return x.sqrMagnitude.CompareTo(y.sqrMagnitude);
            }
        }

        /// <summary>
        /// Inverts the given vector component-wise and returns it as a new vector.
        /// </summary>
        /// <param name="v">the vector to invert</param>
        /// <returns>the inverse of the vector</returns>
        public static Vector3 Inverse(this Vector3 v)
        {
            return new Vector3(1.0f / v.x, 1.0f / v.y, 1.0f / v.z);
        }

        /// <summary>
        /// Divides the components of the first vector by those of the second vector.
        /// </summary>
        /// <param name="a">the first vector</param>
        /// <param name="b">the second vector</param>
        /// <returns></returns>
        public static Vector3 ScaleInverse(Vector3 a, Vector3 b) => new(a.x / b.x, a.y / b.y, a.z / b.z);

        /// <summary>
        /// Returns a new vector with the vector's YZ coordinates and the given x coordinate.
        /// </summary>
        /// <param name="v">the vector</param>
        /// <param name="x">the new X coordinate</param>
        /// <returns>the new vector</returns>
        public static Vector3 WithX(this Vector3 v, float x)
        {
            return new Vector3(x, v.y, v.z);
        }

        /// <summary>
        /// Returns a new vector with the vector's XZ coordinates and the given Y coordinate.
        /// </summary>
        /// <param name="v">the vector</param>
        /// <param name="y">the new Y coordinate</param>
        /// <returns>the new vector</returns>
        public static Vector3 WithY(this Vector3 v, float y)
        {
            return new Vector3(v.x, y, v.z);
        }

        /// <summary>
        /// Returns a new vector with the vector's XY coordinates and the given Z coordinate.
        /// </summary>
        /// <param name="v">the vector</param>
        /// <param name="z">the new Z coordinate</param>
        /// <returns>the new vector</returns>
        public static Vector3 WithZ(this Vector3 v, float z)
        {
            return new Vector3(v.x, v.y, z);
        }

        /// <summary>
        /// Gets a 2D vector with the X and Z components of the given vector.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <returns>the XZ vector</returns>
        public static Vector2 XZ(this Vector3 v)
        {
            return new Vector2(v.x, v.z);
        }

        /// <summary>
        /// Tests if all of the vector's components is zero.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <returns>true iff all components are zero</returns>
        public static bool IsZero(this Vector3 v)
        {
            return v.x == 0.0f && v.y == 0.0f && v.z == 0.0f;
        }

        /// <summary>
        /// Tests if any of the vector's components is not zero.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <returns>true iff any component is not zero</returns>
        public static bool IsNonZero(this Vector3 v)
        {
            return v.x != 0.0f || v.y != 0.0f || v.z != 0.0f;
        }

        /// <summary>
        /// Clamps the XZ magnitude of the given vector to the given value.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <param name="maxXZLength"></param>
        /// <returns></returns>
        public static Vector3 ClampMagnitudeXZ(this Vector3 v, float maxXZLength)
        {
            return Vector2.ClampMagnitude(v.XZ(), maxXZLength).AsXZ(v.y);
        }

        /// <summary>
        /// Rounds the vector's components.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <param name="digits">the number of fractional digits</param>
        /// <returns></returns>
        public static Vector3 Round(this Vector3 v, int digits)
        {
            return new Vector3(
                (float)Math.Round(v.x, digits),
                (float)Math.Round(v.y, digits),
                (float)Math.Round(v.z, digits));
        }

        /// <summary>
        /// Gets the maximum coordinate of the vector.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <returns></returns>
        public static float MaxAbsCoord(this Vector3 v)
        {
            return Mathf.Max(Mathf.Abs(v.x), Mathf.Max(Mathf.Abs(v.y), Mathf.Abs(v.z)));
        }

        /// <summary>
        /// Moves the vector towards a target vector by an absolute distance.
        /// </summary>
        /// <param name="v">the input vector</param>
        /// <param name="target">the target vector</param>
        /// <param name="d">the absolute distance to move</param>
        /// <returns>the moved vector</returns>
        public static Vector3 TowardsTarget(this Vector3 v, Vector3 target, float d) => v + d * (target - v).normalized;

        /// <summary>
        /// Gets a ray from the given vector and direction.
        /// </summary>
        /// <param name="v">the origin</param>
        /// <param name="dir">the direction</param>
        /// <returns>the ray</returns>
        public static Ray Ray(this Vector3 v, Vector3 dir) => new(v, dir);
    }
}
