using Unity.Collections;
using Unity.Mathematics;

namespace UnityEngine.Rendering.Universal.UTess
{

    struct Refinery
    {

        // Old min and max are 0.5 and 0.05. We pretty much do the same with some relaxing on both ends.
        private static readonly float kMinAreaFactor = 0.0482f;
        private static readonly float kMaxAreaFactor = 0.4820f;
        // After doing more tests with a number of Sprites, this is area to which we can reduce to considering quality and CPU cost. 
        private static readonly int kMaxSteinerCount = 4084;

        // Check if Triangle is Ok.
        static bool RequiresRefining(UTriangle tri, float maxArea)
        {
            // Add any further criteria later on. 
            return (tri.area > maxArea);
        }

        static void FetchEncroachedSegments(NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, ref NativeArray<UEncroachingSegment> encroach, ref int encroachCount, UCircle c)
        {
            for (int i = 0; i < pgEdgeCount; ++i)
            {
                var edge = pgEdges[i];
                var edgeA = pgPoints[edge.x];
                var edgeB = pgPoints[edge.y];

                // Check if center is along the Edge.
                if (!math.any(c.center - edgeA) || !math.any(c.center - edgeB))
                    continue;

                // Get Radius
                var edgeD = edgeA - edgeB;
                var edgeM = (edgeA + edgeB) * 0.5f;
                var edgeR = math.length(edgeD) * 0.5f;
                if (math.length(edgeM - c.center) > edgeR)
                    continue;

                UEncroachingSegment es = new UEncroachingSegment();
                es.a = edgeA;
                es.b = edgeB;
                es.index = i;
                encroach[encroachCount++] = es;
            }
        }

        static void InsertVertex(ref NativeArray<float2> pgPoints, ref int pgPointCount, float2 newVertex, ref int nid)
        {
            nid = pgPointCount;
            pgPoints[nid] = newVertex;
            pgPointCount++;
        }

        static int FindSegment(NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, UEncroachingSegment es)
        {
            for (int i = es.index; i < pgEdgeCount; ++i)
            {
                var edge = pgEdges[i];
                var edgeA = pgPoints[edge.x];
                var edgeB = pgPoints[edge.y];
                if (math.any(edgeA - es.a) || math.any(edgeB - es.b))
                    continue;
                return i;
            }
            return -1;
        }

        static void SplitSegments(ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> pgEdges, ref int pgEdgeCount, UEncroachingSegment es)
        {
            var sid = FindSegment(pgPoints, pgPointCount, pgEdges, pgEdgeCount, es);
            if (sid == -1)
                return;

            var edge = pgEdges[sid];
            var edgeA = pgPoints[edge.x];
            var edgeB = pgPoints[edge.y];
            var split = (edgeA + edgeB) * 0.5f;
            var neid = 0;
            if (math.abs(edge.x - edge.y) == 1)
            {
                neid = (edge.x > edge.y) ? edge.x : edge.y;
                InsertVertex(ref pgPoints, ref pgPointCount, split, ref neid);

                // Add the split segments.
                var rep = pgEdges[sid];
                pgEdges[sid] = new int2(rep.x, neid);
                for (int i = pgEdgeCount; i > (sid + 1); --i)
                    pgEdges[i] = pgEdges[i - 1];
                pgEdges[sid + 1] = new int2(neid, rep.y);
                pgEdgeCount++;
            }
            else
            {
                neid = pgPointCount;
                pgPoints[pgPointCount++] = split;
                pgEdges[sid] = new int2(math.max(edge.x, edge.y), neid);
                pgEdges[pgEdgeCount++] = new int2(math.min(edge.x, edge.y), neid);
            }
        }

        internal static bool Condition(Allocator allocator, float factorArea, float targetArea, ref NativeArray<float2> pgPoints, ref int pgPointCount, ref NativeArray<int2> pgEdges, ref int pgEdgeCount, ref NativeArray<float2> vertices, ref int vertexCount, ref NativeArray<int> indices, ref int indexCount, ref float maxArea)
        {

            // Process Triangles.
            maxArea = 0.0f;
            var minArea = 0.0f;
            var avgArea = 0.0f;
            var refined = false;
            var validGraph = true;

            // Temporary Stuffs.
            int triangleCount = 0, invalidTriangle = -1, inputPointCount = pgPointCount;
            var encroach = new NativeArray<UEncroachingSegment>(ModuleHandle.kMaxEdgeCount, allocator);
            var triangles = new NativeArray<UTriangle>(ModuleHandle.kMaxTriangleCount, allocator);
            ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref maxArea, ref avgArea, ref minArea);
            factorArea = factorArea != 0 ? math.clamp(factorArea, kMinAreaFactor, kMaxAreaFactor) : factorArea;
            var criArea = maxArea * factorArea;
            criArea = math.max(criArea, targetArea);

            // Refine
            while (!refined && validGraph)
            {

                // Check if any of the Triangle is Invalid or Segment is invalid. If yes, Refine.
                for (int i = 0; i < triangleCount; ++i)
                {
                    if (RequiresRefining(triangles[i], criArea))
                    {
                        invalidTriangle = i;
                        break;
                    }
                }

                // Find any Segment that can be Split based on the Input Length. 
                // todo.

                if (invalidTriangle != -1)
                {

                    // Get all Segments that are encroached.
                    var t = triangles[invalidTriangle];
                    var encroachCount = 0;
                    FetchEncroachedSegments(pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref encroach, ref encroachCount, t.c);

                    // Split each Encroached Segments. If no segments are encroached. Split the Triangle.
                    if (encroachCount != 0)
                    {
                        for (int i = 0; i < encroachCount; ++i)
                        {
                            SplitSegments(ref pgPoints, ref pgPointCount, ref pgEdges, ref pgEdgeCount, encroach[i]);
                        }
                    }
                    else
                    {
                        // Update Triangulation.
                        var split = t.c.center;
                        pgPoints[pgPointCount++] = split;
                    }

                    // Tessellate again.
                    indexCount = 0; vertexCount = 0;
                    validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref vertices, ref vertexCount, ref indices, ref indexCount);

                    // Build Internal Triangles.
                    encroachCount = 0; triangleCount = 0; invalidTriangle = -1;
                    if (validGraph)
                        ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref maxArea, ref avgArea, ref minArea);

                    // More than enough Steiner points inserted. This handles all sort of weird input sprites very well (even random point cloud).
                    if (pgPointCount - inputPointCount > kMaxSteinerCount)
                        break;
                }
                else
                {
                    refined = true;
                }

            }

            // Dispose off
            triangles.Dispose();
            encroach.Dispose();
            return refined;

        }

    }
}