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

namespace pdinklag
{
    /// <summary>
    /// Geometry utilities.
    /// </summary>
    public static class Geometry
    {
        private static readonly Bounds Empty = new Bounds(Vector3.zero, Vector3.zero);
        private static readonly Vector3 PositiveInfinity = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
        private static readonly Vector3 NegativeInfinity = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);

        /// <summary>
        /// Computes the bounding box of a set of objects.
        /// </summary>
        /// <param name="objects">the points</param>
        /// <param name="pos">the function to retrieve an object's position</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Bounds Bounds<T>(this T[] objects, Func<T, Vector3> pos)
        {
            var num = objects.Length;
            if (num == 0) return Empty;

            var bounds = new Bounds(pos(objects[0]), Vector3.zero);
            for (var i = 1; i < num; i++) bounds.Encapsulate(pos(objects[i]));
            return bounds;
        }

        /// <summary>
        /// Computes the bounding box of a set of objects.
        /// </summary>
        /// <param name="objects">the points</param>
        /// <param name="pos">the function to retrieve an object's position</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Bounds Bounds<T>(this IReadOnlyList<T> objects, Func<T, Vector3> pos)
        {
            var num = objects.Count;
            if (num == 0) return Empty;

            var bounds = new Bounds(pos(objects[0]), Vector3.zero);
            for (var i = 1; i < num; i++) bounds.Encapsulate(pos(objects[i]));
            return bounds;
        }

        /// <summary>
        /// Computes the bounding box of a set of points.
        /// </summary>
        /// <param name="points">the points</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Bounds Bounds(this Vector3[] points) => points.Bounds(p => p);

        /// <summary>
        /// Computes the bounding box of a set of points.
        /// </summary>
        /// <param name="points">the points</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Bounds Bounds(this InlineArray<Vector3> points) => points.Bounds(p => p);

        /// <summary>
        /// Computes the center of the given points' bounding box.
        /// </summary>
        /// <param name="points">the points</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Vector3 Center(this Vector3[] points) => points.Bounds().center;

        /// <summary>
        /// Computes the center of the given points' bounding box.
        /// </summary>
        /// <param name="points">the points</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Vector3 Center(this InlineArray<Vector3> points) => points.Bounds().center;

        /// <summary>
        /// Computes the center of the bounding box of a set of objects.
        /// </summary>
        /// <param name="objects">the points</param>
        /// <param name="pos">the function to retrieve an object's position</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Vector3 Center<T>(this T[] objects, Func<T, Vector3> pos) => objects.Bounds(pos).center;

        /// <summary>
        /// Computes the center of the bounding box of a set of objects.
        /// </summary>
        /// <param name="objects">the points</param>
        /// <param name="pos">the function to retrieve an object's position</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Vector3 Center<T>(this IReadOnlyList<T> objects, Func<T, Vector3> pos) => objects.Bounds(pos).center;

        /// <summary>
        /// Computes the center of the bounding box of a set of objects.
        /// </summary>
        /// <param name="objects">the points</param>
        /// <param name="pos">the function to retrieve an object's position</param>
        /// <returns>the center of the points' bounding box</returns>
        public static Vector3 Center(this Collision c)
        {
            var num = c.contactCount;
            if (num == 0) return Vector3.zero;

            var bounds = new Bounds(c.GetContact(0).point, Vector3.zero);
            for (var i = 1; i < num; i++) bounds.Encapsulate(c.GetContact(i).point);
            return bounds.center;
        }

        /// <summary>
        /// Computes the impulse direction of the given collision.
        /// </summary>
        /// <param name="c">the collision</param>
        /// <returns>the impulse direction</returns>
        public static Vector3 Direction(this Collision c)
        {
            var num = c.contactCount;
            if (num == 0) return Vector3.zero;

            var impulse = Vector3.zero;
            for (var i = 0; i < num; i++) impulse += c.GetContact(i).impulse;
            return -(impulse / num).normalized;
        }
    }
}
