using System.Collections.Generic;

namespace Syntriax.Engine.Core;

/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
{
    /// <summary>
    /// The lower boundary of the <see cref="AABB"/>.
    /// </summary>
    public readonly Vector2D LowerBoundary = lowerBoundary;

    /// <summary>
    /// The upper boundary of the <see cref="AABB"/>.
    /// </summary>
    public readonly Vector2D UpperBoundary = upperBoundary;

    /// <summary>
    /// Gets the center point of the <see cref="AABB"/>.
    /// </summary>
    public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;

    /// <summary>
    /// Gets the size of the <see cref="AABB"/>.
    /// </summary>
    public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();

    /// <summary>
    /// Gets half the size of the <see cref="AABB"/>.
    /// </summary>
    public readonly Vector2D SizeHalf => Size * .5f;

    /// <summary>
    /// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
    /// </summary>
    /// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
    /// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
    public static AABB FromVectors(IEnumerable<Vector2D> vectors)
    {
        int counter = 0;

        Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
        Vector2D upperBoundary = new(float.MinValue, float.MinValue);

        foreach (Vector2D vector in vectors)
        {
            lowerBoundary = Vector2D.Min(lowerBoundary, vector);
            upperBoundary = Vector2D.Max(upperBoundary, vector);
            counter++;
        }

        if (counter < 2)
            throw new System.ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");

        return new(lowerBoundary, upperBoundary);
    }

    /// <summary>
    /// Converts the <see cref="AABB"/> to its string representation.
    /// </summary>
    /// <returns>A string representation of the <see cref="AABB"/>.</returns>
    public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";

    /// <summary>
    /// Checks if two <see cref="AABB"/>s are approximately equal.
    /// </summary>
    /// <param name="left">The first <see cref="AABB"/>.</param>
    /// <param name="right">The second <see cref="AABB"/>.</param>
    /// <param name="epsilon">The epsilon range.</param>
    /// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
    public static bool ApproximatelyEquals(AABB left, AABB right, float epsilon = float.Epsilon)
        => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
}

/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions
{
    /// <inheritdoc cref="AABB.ToAABB" />
    public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);

    /// <inheritdoc cref="AABB.ApproximatelyEquals" />
    public static bool ApproximatelyEquals(this AABB left, AABB right, float epsilon = float.Epsilon) => AABB.ApproximatelyEquals(left, right, epsilon);
}