using Unity.Mathematics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;

namespace UnityEngine.Rendering.Universal.UTess
{

    struct Smoothen
    {

        // This is an arbitrary value less than 2 to ensure points are not moved too far away from the source polygon.
        private static readonly float kMaxAreaTolerance = 1.842f;
        private static readonly float kMaxEdgeTolerance = 2.482f;

        // Trim Edges
        static void RefineEdges(ref NativeArray<int4> refinedEdges, ref NativeArray<int4> delaEdges, ref int delaEdgeCount, ref NativeArray<int4> voronoiEdges)
        {
            int origEdgeCount = delaEdgeCount;
            delaEdgeCount = 0;

            // Check Neighbour Triangles.
            for (int i = 0; i < origEdgeCount - 1; ++i)
            {
                var edge = delaEdges[i];
                var neighbor = delaEdges[i + 1];
                if (edge.x == neighbor.x && edge.y == neighbor.y)
                {
                    // Found the Opposite Edge. i.e Nearby Triangle.
                    edge.w = neighbor.z;
                    ++i;
                }
                // Update new list.
                refinedEdges[delaEdgeCount++] = edge;
            }

            // Generate Voronoi Edges.
            for (int i = 0; i < delaEdgeCount; ++i)
            {
                var ti1 = refinedEdges[i].z;
                var ti2 = refinedEdges[i].w;

                // We only really care about Bounded Edges. This is simplification. Hoping this garbage works.
                if (ti1 != -1 && ti2 != -1)
                {
                    // Get Triangles
                    int4 e = new int4(ti2, ti1, i, 0);
                    voronoiEdges[i] = e;
                }
            }

            ModuleHandle.Copy(refinedEdges, delaEdges, delaEdgeCount);
        }

        // Get all the Edges that has this Point.
        static void GetAffectingEdges(int pointIndex, NativeArray<int4> edges, int edgeCount, ref NativeArray<int> resultSet, ref NativeArray<int> checkSet, ref int resultCount)
        {
            resultCount = 0;
            for (int i = 0; i < edgeCount; ++i)
            {
                if (pointIndex == edges[i].x || pointIndex == edges[i].y)
                    resultSet[resultCount++] = i;
                checkSet[i] = 0;
            }
        }

        // Add to Centroids Triangles.
        static void CentroidByPoints(int triIndex, NativeArray<UTriangle> triangles, ref NativeArray<int> centroidTris, ref int centroidCount, ref float2 aggregate, ref float2 point)
        {
            for (int i = 0; i < centroidCount; ++i)
                if (triIndex == centroidTris[i])
                    return;
            centroidTris[centroidCount++] = triIndex;
            aggregate += triangles[triIndex].c.center;
            point = aggregate / centroidCount;
        }

        static void CentroidByPolygon(int4 e, NativeArray<UTriangle> triangles, ref float2 centroid, ref float area, ref float distance)
        {
            var es = triangles[e.x].c.center;
            var ee = triangles[e.y].c.center;
            var d = es.x * ee.y - ee.x * es.y;
            distance = distance + math.distance(es, ee);
            area = area + d;
            centroid.x += (ee.x + es.x) * d;
            centroid.y += (ee.y + es.y) * d;
        }

        // Connect Triangles
        static bool ConnectTriangles(ref NativeArray<int4> connectedTri, ref NativeArray<int> affectEdges, ref NativeArray<int> checkSet, NativeArray<int4> voronoiEdges, int triangleCount)
        {
            var ei = affectEdges[0];
            var ni = affectEdges[0];
            connectedTri[0] = new int4(voronoiEdges[ei].x, voronoiEdges[ei].y, 0, 0);
            checkSet[ni] = 1;

            for (int i = 1; i < triangleCount; ++i)
            {
                ni = affectEdges[i];
                if (checkSet[ni] == 0)
                {
                    if (voronoiEdges[ni].x == connectedTri[i - 1].y)
                    {
                        connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0);
                        checkSet[ni] = 1;
                        continue;
                    }
                    else
                    {
                        if (voronoiEdges[ni].y == connectedTri[i - 1].y)
                        {
                            connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0);
                            checkSet[ni] = 1;
                            continue;
                        }
                    }
                }

                var connected = false;
                for (int j = 0; j < triangleCount; ++j)
                {
                    ni = affectEdges[j];
                    if (checkSet[ni] == 1)
                        continue;
                    if (voronoiEdges[ni].x == connectedTri[i - 1].y)
                    {
                        connectedTri[i] = new int4(voronoiEdges[ni].x, voronoiEdges[ni].y, 0, 0);
                        checkSet[ni] = 1;
                        connected = true;
                        break;
                    }
                    else if (voronoiEdges[ni].y == connectedTri[i - 1].y)
                    {
                        connectedTri[i] = new int4(voronoiEdges[ni].y, voronoiEdges[ni].x, 0, 0);
                        checkSet[ni] = 1;
                        connected = true;
                        break;
                    }
                }
                if (!connected)
                    return false;
            }

            return true;
        }

        
        // Perform Voronoi based Smoothing. Does not add/remove points but merely relocates internal vertices so they are uniform distributed.
        internal static bool Condition(Allocator allocator, ref NativeArray<float2> pgPoints, int pgPointCount, NativeArray<int2> pgEdges, int pgEdgeCount, ref NativeArray<float2> vertices, ref int vertexCount, ref NativeArray<int> indices, ref int indexCount)
        {

            // Build Triangles and Edges.
            float maxArea = 0, cmxArea = 0, minArea = 0, cmnArea = 0, avgArea = 0, minEdge = 0, maxEdge = 0, avgEdge = 0;
            bool polygonCentroid = true, validGraph = true;
            int triangleCount = 0, delaEdgeCount = 0, affectingEdgeCount = 0;
            var triangles = new NativeArray<UTriangle>(indexCount, allocator);    // Intentionally added more room than actual Triangles needed here.
            var delaEdges = new NativeArray<int4>(indexCount, allocator);
            var voronoiEdges = new NativeArray<int4>(indexCount, allocator);
            var connectedTri = new NativeArray<int4>(vertexCount, allocator);
            var voronoiCheck = new NativeArray<int>(indexCount, allocator);
            var affectsEdges = new NativeArray<int>(indexCount, allocator);
            var triCentroids = new NativeArray<int>(vertexCount, allocator);
            ModuleHandle.BuildTrianglesAndEdges(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref delaEdges, ref delaEdgeCount, ref maxArea, ref avgArea, ref minArea);
            var refinedEdges = new NativeArray<int4>(delaEdgeCount, allocator);

            // Sort the Delaunay Edges.
            unsafe
            {
                ModuleHandle.InsertionSort<int4, DelaEdgeCompare>(
                    NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(delaEdges), 0, delaEdgeCount - 1,
                    new DelaEdgeCompare());
            }

            // TrimEdges. Update Triangle Info for Shared Edges and remove Duplicates.
            RefineEdges(ref refinedEdges, ref delaEdges, ref delaEdgeCount, ref voronoiEdges);

            // Now for each point, generate Voronoi diagram.
            for (int i = 0; i < vertexCount; ++i)
            {

                // Try moving this to Centroid of the Voronoi Polygon.
                GetAffectingEdges(i, delaEdges, delaEdgeCount, ref affectsEdges, ref voronoiCheck, ref affectingEdgeCount);
                var bounded = affectingEdgeCount != 0;

                // Check for Boundedness
                for (int j = 0; j < affectingEdgeCount; ++j)
                {
                    // Edge Index.
                    var ei = affectsEdges[j];
                    if (delaEdges[ei].z == -1 || delaEdges[ei].w == -1)
                    {
                        bounded = false;
                        break;
                    }
                }

                // If this is bounded point, relocate to Voronoi Diagram's Centroid
                if (bounded)
                {
                    polygonCentroid = ConnectTriangles(ref connectedTri, ref affectsEdges, ref voronoiCheck, voronoiEdges, affectingEdgeCount);
                    if (!polygonCentroid)
                    {
                        break;
                    }

                    float2 point = float2.zero;
                    float area = 0, distance = 0;
                    for (int k = 0; k < affectingEdgeCount; ++k)
                    {
                        CentroidByPolygon(connectedTri[k], triangles, ref point, ref area, ref distance);
                    }
                    point /= (3 * area);
                    pgPoints[i] = point;
                }

            }

            // Do Delaunay Again.
            int srcIndexCount = indexCount, srcVertexCount = vertexCount;
            indexCount = 0; vertexCount = 0; triangleCount = 0;
            if (polygonCentroid)
            {
                validGraph = Tessellator.Tessellate(allocator, pgPoints, pgPointCount, pgEdges, pgEdgeCount, ref vertices, ref vertexCount, ref indices, ref indexCount);
                if (validGraph)
                    ModuleHandle.BuildTriangles(vertices, vertexCount, indices, indexCount, ref triangles, ref triangleCount, ref cmxArea, ref avgArea, ref cmnArea, ref maxEdge, ref avgEdge, ref minEdge);
                // This Edge validation prevents artifacts by forcing a fallback. todo: Fix the actual bug in Outline generation.
                validGraph = validGraph && (cmxArea < maxArea * kMaxAreaTolerance) && (maxEdge < avgEdge * kMaxEdgeTolerance);
            }

            // Cleanup.
            triangles.Dispose();
            delaEdges.Dispose();
            refinedEdges.Dispose();
            voronoiCheck.Dispose();
            voronoiEdges.Dispose();
            affectsEdges.Dispose();
            triCentroids.Dispose();
            connectedTri.Dispose();
            return (validGraph && srcIndexCount == indexCount && srcVertexCount == vertexCount);

        }

    }

}