// -----------------------------------------------------------------------
// <copyright file="Converter.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------

namespace UnityEngine.U2D.Animation.TriangleNet
    .Meshing
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Animation.TriangleNet.Geometry;
    using Animation.TriangleNet.Topology;
    using Animation.TriangleNet.Topology.DCEL;

    using HVertex = Animation.TriangleNet.Topology.DCEL.Vertex;
    using TVertex = Animation.TriangleNet.Geometry.Vertex;

    /// <summary>
    /// The Converter class provides methods for mesh reconstruction and conversion.
    /// </summary>
    internal static class Converter
    {
        #region Triangle mesh conversion

        /// <summary>
        /// Reconstruct a triangulation from its raw data representation.
        /// </summary>
        internal static Mesh ToMesh(Polygon polygon, IList<ITriangle> triangles)
        {
            return ToMesh(polygon, triangles.ToArray());
        }

        /// <summary>
        /// Reconstruct a triangulation from its raw data representation.
        /// </summary>
        internal static Mesh ToMesh(Polygon polygon, ITriangle[] triangles)
        {
            Otri tri = default(Otri);
            Osub subseg = default(Osub);
            int i = 0;

            int elements = triangles == null ? 0 : triangles.Length;
            int segments = polygon.Segments.Count;

            // TODO: Configuration should be a function argument.
            var mesh = new Mesh(new Configuration());

            mesh.TransferNodes(polygon.Points);

            mesh.regions.AddRange(polygon.Regions);
            mesh.behavior.useRegions = polygon.Regions.Count > 0;

            if (polygon.Segments.Count > 0)
            {
                mesh.behavior.Poly = true;
                mesh.holes.AddRange(polygon.Holes);
            }

            // Create the triangles.
            for (i = 0; i < elements; i++)
            {
                mesh.MakeTriangle(ref tri);
            }

            if (mesh.behavior.Poly)
            {
                mesh.insegments = segments;

                // Create the subsegments.
                for (i = 0; i < segments; i++)
                {
                    mesh.MakeSegment(ref subseg);
                }
            }

            var vertexarray = SetNeighbors(mesh, triangles);

            SetSegments(mesh, polygon, vertexarray);

            return mesh;
        }

        /// <summary>
        /// Finds the adjacencies between triangles by forming a stack of triangles for
        /// each vertex. Each triangle is on three different stacks simultaneously.
        /// </summary>
        private static List<Otri>[] SetNeighbors(Mesh mesh, ITriangle[] triangles)
        {
            Otri tri = default(Otri);
            Otri triangleleft = default(Otri);
            Otri checktri = default(Otri);
            Otri checkleft = default(Otri);
            Otri nexttri;
            TVertex tdest, tapex;
            TVertex checkdest, checkapex;
            int[] corner = new int[3];
            int aroundvertex;
            int i;

            // Allocate a temporary array that maps each vertex to some adjacent triangle.
            var vertexarray = new List<Otri>[mesh.vertices.Count];

            // Each vertex is initially unrepresented.
            for (i = 0; i < mesh.vertices.Count; i++)
            {
                Otri tmp = default(Otri);
                tmp.tri = mesh.dummytri;
                vertexarray[i] = new List<Otri>(3);
                vertexarray[i].Add(tmp);
            }

            i = 0;

            // Read the triangles from the .ele file, and link
            // together those that share an edge.
            foreach (var item in mesh.triangles)
            {
                tri.tri = item;

                // Copy the triangle's three corners.
                for (int j = 0; j < 3; j++)
                {
                    corner[j] = triangles[i].GetVertexID(j);

                    if ((corner[j] < 0) || (corner[j] >= mesh.invertices))
                    {
                        Log.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()");
                        throw new Exception("Triangle has an invalid vertex index.");
                    }
                }

                // Read the triangle's attributes.
                tri.tri.label = triangles[i].Label;

                // TODO: VarArea
                if (mesh.behavior.VarArea)
                {
                    tri.tri.area = triangles[i].Area;
                }

                // Set the triangle's vertices.
                tri.orient = 0;
                tri.SetOrg(mesh.vertices[corner[0]]);
                tri.SetDest(mesh.vertices[corner[1]]);
                tri.SetApex(mesh.vertices[corner[2]]);

                // Try linking the triangle to others that share these vertices.
                for (tri.orient = 0; tri.orient < 3; tri.orient++)
                {
                    // Take the number for the origin of triangleloop.
                    aroundvertex = corner[tri.orient];

                    int index = vertexarray[aroundvertex].Count - 1;

                    // Look for other triangles having this vertex.
                    nexttri = vertexarray[aroundvertex][index];

                    // Push the current triangle onto the stack.
                    vertexarray[aroundvertex].Add(tri);

                    checktri = nexttri;

                    if (checktri.tri.id != Mesh.DUMMY)
                    {
                        tdest = tri.Dest();
                        tapex = tri.Apex();

                        // Look for other triangles that share an edge.
                        do
                        {
                            checkdest = checktri.Dest();
                            checkapex = checktri.Apex();

                            if (tapex == checkdest)
                            {
                                // The two triangles share an edge; bond them together.
                                tri.Lprev(ref triangleleft);
                                triangleleft.Bond(ref checktri);
                            }
                            if (tdest == checkapex)
                            {
                                // The two triangles share an edge; bond them together.
                                checktri.Lprev(ref checkleft);
                                tri.Bond(ref checkleft);
                            }
                            // Find the next triangle in the stack.
                            index--;
                            nexttri = vertexarray[aroundvertex][index];

                            checktri = nexttri;
                        }
                        while (checktri.tri.id != Mesh.DUMMY);
                    }
                }

                i++;
            }

            return vertexarray;
        }

        /// <summary>
        /// Finds the adjacencies between triangles and subsegments.
        /// </summary>
        private static void SetSegments(Mesh mesh, Polygon polygon, List<Otri>[] vertexarray)
        {
            Otri checktri = default(Otri);
            Otri nexttri; // Triangle
            TVertex checkdest;
            Otri checkneighbor = default(Otri);
            Osub subseg = default(Osub);
            Otri prevlink; // Triangle

            TVertex tmp;
            TVertex sorg, sdest;

            bool notfound;

            //bool segmentmarkers = false;
            int boundmarker;
            int aroundvertex;
            int i;

            int hullsize = 0;

            // Prepare to count the boundary edges.
            if (mesh.behavior.Poly)
            {
                // Link the segments to their neighboring triangles.
                boundmarker = 0;
                i = 0;
                foreach (var item in mesh.subsegs.Values)
                {
                    subseg.seg = item;

                    sorg = polygon.Segments[i].GetVertex(0);
                    sdest = polygon.Segments[i].GetVertex(1);

                    boundmarker = polygon.Segments[i].Label;

                    if ((sorg.id < 0 || sorg.id >= mesh.invertices) || (sdest.id < 0 || sdest.id >= mesh.invertices))
                    {
                        Log.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()");
                        throw new Exception("Segment has an invalid vertex index.");
                    }

                    // set the subsegment's vertices.
                    subseg.orient = 0;
                    subseg.SetOrg(sorg);
                    subseg.SetDest(sdest);
                    subseg.SetSegOrg(sorg);
                    subseg.SetSegDest(sdest);
                    subseg.seg.boundary = boundmarker;
                    // Try linking the subsegment to triangles that share these vertices.
                    for (subseg.orient = 0; subseg.orient < 2; subseg.orient++)
                    {
                        // Take the number for the destination of subsegloop.
                        aroundvertex = subseg.orient == 1 ? sorg.id : sdest.id;

                        int index = vertexarray[aroundvertex].Count - 1;

                        // Look for triangles having this vertex.
                        prevlink = vertexarray[aroundvertex][index];
                        nexttri = vertexarray[aroundvertex][index];

                        checktri = nexttri;
                        tmp = subseg.Org();
                        notfound = true;
                        // Look for triangles having this edge.  Note that I'm only
                        // comparing each triangle's destination with the subsegment;
                        // each triangle's apex is handled through a different vertex.
                        // Because each triangle appears on three vertices' lists, each
                        // occurrence of a triangle on a list can (and does) represent
                        // an edge.  In this way, most edges are represented twice, and
                        // every triangle-subsegment bond is represented once.
                        while (notfound && (checktri.tri.id != Mesh.DUMMY))
                        {
                            checkdest = checktri.Dest();

                            if (tmp == checkdest)
                            {
                                // We have a match. Remove this triangle from the list.
                                //prevlink = vertexarray[aroundvertex][index];
                                vertexarray[aroundvertex].Remove(prevlink);
                                // Bond the subsegment to the triangle.
                                checktri.SegBond(ref subseg);
                                // Check if this is a boundary edge.
                                checktri.Sym(ref checkneighbor);
                                if (checkneighbor.tri.id == Mesh.DUMMY)
                                {
                                    // The next line doesn't insert a subsegment (because there's
                                    // already one there), but it sets the boundary markers of
                                    // the existing subsegment and its vertices.
                                    mesh.InsertSubseg(ref checktri, 1);
                                    hullsize++;
                                }
                                notfound = false;
                            }
                            index--;
                            // Find the next triangle in the stack.
                            prevlink = vertexarray[aroundvertex][index];
                            nexttri = vertexarray[aroundvertex][index];

                            checktri = nexttri;
                        }
                    }

                    i++;
                }
            }

            // Mark the remaining edges as not being attached to any subsegment.
            // Also, count the (yet uncounted) boundary edges.
            for (i = 0; i < mesh.vertices.Count; i++)
            {
                // Search the stack of triangles adjacent to a vertex.
                int index = vertexarray[i].Count - 1;
                nexttri = vertexarray[i][index];
                checktri = nexttri;

                while (checktri.tri.id != Mesh.DUMMY)
                {
                    // Find the next triangle in the stack before this
                    // information gets overwritten.
                    index--;
                    nexttri = vertexarray[i][index];
                    // No adjacent subsegment.  (This overwrites the stack info.)
                    checktri.SegDissolve(mesh.dummysub);
                    checktri.Sym(ref checkneighbor);
                    if (checkneighbor.tri.id == Mesh.DUMMY)
                    {
                        mesh.InsertSubseg(ref checktri, 1);
                        hullsize++;
                    }

                    checktri = nexttri;
                }
            }

            mesh.hullsize = hullsize;
        }

        #endregion

        #region DCEL conversion

        internal static DcelMesh ToDCEL(Mesh mesh)
        {
            var dcel = new DcelMesh();

            var vertices = new HVertex[mesh.vertices.Count];
            var faces = new Face[mesh.triangles.Count];

            dcel.HalfEdges.Capacity = 2 * mesh.NumberOfEdges;

            mesh.Renumber();

            HVertex vertex;

            foreach (var v in mesh.vertices.Values)
            {
                vertex = new HVertex(v.x, v.y);
                vertex.id = v.id;
                vertex.label = v.label;

                vertices[v.id] = vertex;
            }

            // Maps a triangle to its 3 edges (used to set next pointers).
            var map = new List<HalfEdge>[mesh.triangles.Count];

            Face face;

            foreach (var t in mesh.triangles)
            {
                face = new Face(null);
                face.id = t.id;

                faces[t.id] = face;

                map[t.id] = new List<HalfEdge>(3);
            }

            Otri tri = default(Otri), neighbor = default(Otri);
            Animation.TriangleNet.Geometry.Vertex org, dest;

            int id, nid, count = mesh.triangles.Count;

            HalfEdge edge, twin, next;

            var edges = dcel.HalfEdges;

            // Count half-edges (edge ids).
            int k = 0;

            // Maps a vertex to its leaving boundary edge.
            var boundary = new Dictionary<int, HalfEdge>();

            foreach (var t in mesh.triangles)
            {
                id = t.id;

                tri.tri = t;

                for (int i = 0; i < 3; i++)
                {
                    tri.orient = i;
                    tri.Sym(ref neighbor);

                    nid = neighbor.tri.id;

                    if (id < nid || nid < 0)
                    {
                        face = faces[id];

                        // Get the endpoints of the current triangle edge.
                        org = tri.Org();
                        dest = tri.Dest();

                        // Create half-edges.
                        edge = new HalfEdge(vertices[org.id], face);
                        twin = new HalfEdge(vertices[dest.id], nid < 0 ? Face.Empty : faces[nid]);

                        map[id].Add(edge);

                        if (nid >= 0)
                        {
                            map[nid].Add(twin);
                        }
                        else
                        {
                            boundary.Add(dest.id, twin);
                        }

                        // Set leaving edges.
                        edge.origin.leaving = edge;
                        twin.origin.leaving = twin;

                        // Set twin edges.
                        edge.twin = twin;
                        twin.twin = edge;

                        edge.id = k++;
                        twin.id = k++;

                        edges.Add(edge);
                        edges.Add(twin);
                    }
                }
            }

            // Set next pointers for each triangle face.
            foreach (var t in map)
            {
                edge = t[0];
                next = t[1];

                if (edge.twin.origin.id == next.origin.id)
                {
                    edge.next = next;
                    next.next = t[2];
                    t[2].next = edge;
                }
                else
                {
                    edge.next = t[2];
                    next.next = edge;
                    t[2].next = next;
                }
            }

            // Resolve boundary edges.
            foreach (var e in boundary.Values)
            {
                e.next = boundary[e.twin.origin.id];
            }

            dcel.Vertices.AddRange(vertices);
            dcel.Faces.AddRange(faces);

            return dcel;
        }

        #endregion
    }
}
