using System.Collections;
using System.Collections.Generic;
namespace Engine.Core;
/// 
/// Represents a shape defined by a collection of vertices.
/// 
/// The vertices of the shape.
/// 
/// Initializes a new instance of a  struct with the specified vertices.
/// 
[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
public class Shape2D(List vertices) : IEnumerable
{
    public static Shape2D Triangle => CreateNgon(3, Vector2D.Up);
    public static Shape2D Square => CreateNgon(4, Vector2D.One);
    public static Shape2D Pentagon => CreateNgon(5, Vector2D.Up);
    public static Shape2D Hexagon => CreateNgon(6, Vector2D.Right);
    public Event OnShapeUpdated { get; } = new();
    private List _vertices = vertices;
    /// 
    /// Gets the vertices of the .
    /// 
    public IReadOnlyList Vertices
    {
        get => _vertices;
        set
        {
            _vertices.Clear();
            foreach (Vector2D vertex in value)
                _vertices.Add(vertex);
            OnShapeUpdated?.Invoke(this);
        }
    }
    /// 
    /// The vertex at the specified index.
    /// 
    /// The zero-based index of the vertex to get or set.
    /// The vertex at the specified index.
    public Vector2D this[System.Index index] => Vertices[index];
    /// 
    /// Returns a copy of the current .
    /// 
    /// The  to copy.
    /// A copy of the input .
    public static Shape2D CreateCopy(Shape2D shape) => new(new List(shape.Vertices));
    /// 
    /// Creates a regular polygon (ngon) with the specified number of vertices.
    /// 
    /// The number of vertices in the polygon.
    /// A regular polygon with the specified number of vertices.
    public static Shape2D CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up);
    /// 
    /// Creates a regular polygon (ngon) with the specified number of vertices and a rotation position.
    /// 
    /// The number of vertices in the polygon.
    /// The position to use for rotation.
    /// A regular polygon with the specified number of vertices and rotation position.
    public static Shape2D CreateNgon(int vertexCount, Vector2D positionToRotate)
    {
        if (vertexCount < 3)
            throw new System.ArgumentException($"{nameof(vertexCount)} must have a value of more than 2.");
        List vertices = new(vertexCount);
        float radiansPerVertex = 360f / vertexCount * Math.DegreeToRadian;
        for (int i = 0; i < vertexCount; i++)
            vertices.Add(positionToRotate.Rotate(i * radiansPerVertex));
        return new(vertices);
    }
    /// 
    /// Gets the super triangle that encloses the given .
    /// 
    /// The  to enclose.
    /// The super triangle that encloses the given .
    public static Triangle GetSuperTriangle(Shape2D shape)
    {
        float minX = float.MaxValue, minY = float.MaxValue;
        float maxX = float.MinValue, maxY = float.MinValue;
        foreach (Vector2D point in shape.Vertices)
        {
            minX = Math.Min(minX, point.X);
            minY = Math.Min(minY, point.Y);
            maxX = Math.Max(maxX, point.X);
            maxY = Math.Max(maxY, point.Y);
        }
        float dx = maxX - minX;
        float dy = maxY - minY;
        float deltaMax = Math.Max(dx, dy);
        float midX = (minX + maxX) / 2;
        float midY = (minY + maxY) / 2;
        Vector2D p1 = new((float)midX - 20f * (float)deltaMax, (float)midY - (float)deltaMax);
        Vector2D p2 = new((float)midX, (float)midY + 20 * (float)deltaMax);
        Vector2D p3 = new((float)midX + 20 * (float)deltaMax, (float)midY - (float)deltaMax);
        return new Triangle(p1, p2, p3);
    }
    /// 
    /// Triangulates the given convex .
    /// 
    /// The  to triangulate.
    /// The list to populate with triangles.
    public static void TriangulateConvex(Shape2D shape, IList triangles)
    {
        triangles.Clear();
        for (int i = 2; i < shape.Vertices.Count; i++)
            triangles.Add(new Triangle(shape[0], shape[i], shape[i - 1]));
    }
    /// 
    /// Triangulates the given convex .
    /// 
    /// The  to triangulate.
    /// A list of s that makes up the given convex .
    public static List TriangulateConvex(Shape2D shape)
    {
        List triangles = new(shape.Vertices.Count - 2);
        TriangulateConvex(shape, triangles);
        return triangles;
    }
    /// 
    /// Gets the s that form the edges of the .
    /// 
    /// The  to get s from.
    /// The list to populate with .
    public static void GetLines(Shape2D shape, IList lines)
    {
        lines.Clear();
        for (int i = 0; i < shape.Vertices.Count - 1; i++)
            lines.Add(new(shape.Vertices[i], shape.Vertices[i + 1]));
        lines.Add(new(shape.Vertices[^1], shape.Vertices[0]));
    }
    /// 
    /// Gets a list of s that form the edges of the .
    /// 
    /// The shape to get s from.
    /// A list of s that form the edges of the .
    public static List GetLines(Shape2D shape)
    {
        List lines = new(shape.Vertices.Count - 1);
        GetLines(shape, lines);
        return lines;
    }
    /// 
    /// Projects the  onto a 1D plane.
    /// 
    /// The shape to project.
    /// The vector to project onto.
    /// The list to populate with projected values.
    public static void Project(Shape2D shape, Vector2D projectionVector, IList list)
    {
        list.Clear();
        int count = shape.Vertices.Count;
        for (int i = 0; i < count; i++)
            list.Add(projectionVector.Dot(shape[i]));
    }
    /// 
    /// Projects the  onto a vector.
    /// 
    /// The  to project.
    /// The vector to project onto.
    /// The projection of the  onto the vector.
    public static Projection1D Project(Shape2D shape, Vector2D projectionVector)
    {
        float min = float.MaxValue;
        float max = float.MinValue;
        for (int i = 0; i < shape.Vertices.Count; i++)
        {
            float projectedLength = projectionVector.Dot(shape.Vertices[i]);
            min = Math.Min(projectedLength, min);
            max = Math.Max(projectedLength, max);
        }
        return new(min, max);
    }
    /// 
    /// Transforms the  using the specified .
    /// 
    /// The  to transform.
    /// The  to apply.
    /// The transformed .
    public static Shape2D Transform(Shape2D shape, ITransform2D transform)
    {
        List vertices = new(shape.Vertices.Count);
        int count = shape.Vertices.Count;
        for (int i = 0; i < count; i++)
            vertices.Add(transform.Transform(shape[i]));
        return new Shape2D(vertices);
    }
    /// 
    /// Transforms the  using the specified .
    /// 
    /// The  to transform.
    /// The  to apply.
    /// The transformed .
    public static void Transform(Shape2D from, ITransform2D transform, Shape2D to)
    {
        to._vertices.Clear();
        int count = from._vertices.Count;
        for (int i = 0; i < count; i++)
            to._vertices.Add(transform.Transform(from[i]));
        to.OnShapeUpdated?.Invoke(to);
    }
    /// 
    /// Determines whether two s are approximately equal.
    /// 
    /// The first  to compare.
    /// The second  to compare.
    /// The epsilon range.
    /// true if the s are approximately equal; otherwise, false.
    public static bool ApproximatelyEquals(Shape2D left, Shape2D right, float epsilon = float.Epsilon)
    {
        if (left.Vertices.Count != right.Vertices.Count)
            return false;
        for (int i = 0; i < left.Vertices.Count; i++)
            if (!left.Vertices[i].ApproximatelyEquals(right.Vertices[i], epsilon))
                return false;
        return true;
    }
    /// 
    /// Determines whether the specified object is equal to the current .
    /// 
    /// The object to compare with the current .
    ///  if the specified object is equal to the current ; otherwise, .
    public override bool Equals(object? obj) => obj is Shape2D shape2D && _vertices.Equals(shape2D._vertices);
    /// 
    /// Generates a hash code for the .
    /// 
    /// A hash code for the .
    public override int GetHashCode() => System.HashCode.Combine(Vertices);
    /// 
    /// Converts the  to its string representation.
    /// 
    /// A string representation of the .
    public override string ToString()
    {
        System.Text.StringBuilder stringBuilder = new(Vertices[0].ToString());
        for (int i = 1; i < Vertices.Count; i++)
        {
            stringBuilder.Append(", ");
            stringBuilder.Append(Vertices[i].ToString());
        }
        return $"{nameof(Shape2D)}({stringBuilder})";
    }
    /// 
    public IEnumerator GetEnumerator() => Vertices.GetEnumerator();
    /// 
    IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
}
/// 
/// Provides extension methods for the  struct.
/// 
public static class Shape2DExtensions
{
    /// 
    public static Shape2D CreateCopy(this Shape2D shape) => Shape2D.CreateCopy(shape);
    /// 
    public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape);
    /// 
    public static void ToTrianglesConvex(this Shape2D shape, IList triangles) => Shape2D.TriangulateConvex(shape, triangles);
    /// 
    public static List ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape);
    /// 
    public static void ToLines(this Shape2D shape, IList lines) => Shape2D.GetLines(shape, lines);
    /// 
    public static List ToLines(this Shape2D shape) => Shape2D.GetLines(shape);
    /// 
    public static void ToProjection(this Shape2D shape, Vector2D projectionVector, IList list) => Shape2D.Project(shape, projectionVector, list);
    /// 
    public static Projection1D ToProjection(this Shape2D shape, Vector2D projectionVector) => Shape2D.Project(shape, projectionVector);
    /// 
    public static Shape2D Transform(this ITransform2D transform, Shape2D shape) => Shape2D.Transform(shape, transform);
    /// 
    public static void Transform(this ITransform2D transform, Shape2D from, Shape2D to) => Shape2D.Transform(from, transform, to);
    /// 
    public static Shape2D Transform(this Shape2D shape, ITransform2D transform) => Shape2D.Transform(shape, transform);
    /// 
    public static void Transform(this Shape2D from, ITransform2D transform, Shape2D to) => Shape2D.Transform(from, transform, to);
    /// 
    public static bool ApproximatelyEquals(this Shape2D left, Shape2D right, float epsilon = float.Epsilon) => Shape2D.ApproximatelyEquals(left, right, epsilon);
}