using UnityEngine;

namespace pdinklag
{
    public static class RayExtensions
    {
        /// <summary>
        /// Computes the distance between a ray and a point.
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <param name="point">the vector in question</param>
        /// <returns>the distance between vector and ray</returns>
        public static float Distance(this Ray ray, Vector3 point)
        {
            return Vector3.Cross(ray.direction, point - ray.origin).magnitude;
        }

        /// <summary>
        /// The delta between the ray's origin and the closest point on the ray to the specified point.
        /// 
        /// This is equivalent to the closest point's distance from the origin, but also indicates the direction (negative or positive).
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <param name="point">the point in question</param>
        /// <returns></returns>
        public static float ClosestPointDelta(this Ray ray, Vector3 point)
        {
            return Vector3.Dot(ray.direction, point - ray.origin);
        }

        /// <summary>
        /// Computes the closest point on the ray to the specified point.
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <param name="point">the point in question</param>
        /// <returns>the point on this ray closest to the given point</returns>
        public static Vector3 ClosestPoint(this Ray ray, Vector3 point)
        {
            return ray.origin + ray.direction * ray.ClosestPointDelta(point);
        }

        /// <summary>
        /// Computes the closest point on a section of a ray to the specified point.
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <param name="point">the point in question</param>
        /// <param name="section">the minimum and maximum points on the ray (0 corresponds to origin)</param>
        /// <returns>the point on the section of this ray closest to the given point</returns>
        public static Vector3 ClosestPointInSection(this Ray ray, Vector3 point, MinMax section)
        {
            return ray.origin + ray.direction * Mathf.Clamp(ray.ClosestPointDelta(point), section.Min, section.Max);
        }

        /// <summary>
        /// Tests whether the closest point on the ray to the specified point lies within the specified section.
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <param name="point">the point in question</param>
        /// <param name="section">the minimum and maximum points on the ray (0 corresponds to origin)</param>
        /// <returns></returns>
        public static bool IsClosestPointInSection(this Ray ray, Vector3 point, MinMax section)
        {
            var t = ray.ClosestPointDelta(point);
            return t >= section.Min && t <= section.Max;
        }

        /// <summary>
        /// Converts this ray to a 2D ray on the XZ plane.
        /// </summary>
        /// <param name="ray">the ray</param>
        /// <returns></returns>
        public static Ray2D XZ(this Ray ray)
        {
            return new Ray2D(ray.origin.XZ(), ray.direction.XZ());
        }
    }
}
