// Sort of bad to introduce a dependency on the Path class, but this works.
        // The option to have this logic in QPExtractor is worse imo (does not promote code reuse and is hard to test,
        // because QPExtractor would not expose the method).
        public static Orientation GetOrientationOfCycle <TVertex>(this IReadOnlyUndirectedGraph <TVertex> graph, IEnumerable <TVertex> closedPath)
            where TVertex : IEquatable <TVertex>, IVertexInPlane
        {
            if (graph is null)
            {
                throw new ArgumentNullException(nameof(graph));
            }
            if (closedPath is null)
            {
                throw new ArgumentNullException(nameof(closedPath));
            }

            if (!closedPath.First().Equals(closedPath.Last()))
            {
                throw new ArgumentException($"The path is not closed.", nameof(closedPath));
            }
            if (closedPath.Count() == 1)
            {
                throw new ArgumentException($"The path is stationary.");
            }

            var pathAsCircularListOfVertices = new CircularList <TVertex>(closedPath.SkipLast(1));

            // Sum the external angle at every vertex
            double externalAngleSum = 0;

            for (int i = 0; i < pathAsCircularListOfVertices.Count; i++)
            {
                var vertex1 = pathAsCircularListOfVertices[i - 1];
                var vertex2 = pathAsCircularListOfVertices[i];
                var vertex3 = pathAsCircularListOfVertices[i + 1];

                var pos1 = vertex1.Position;
                var pos2 = vertex2.Position;
                var pos3 = vertex3.Position;

                externalAngleSum += PlaneUtility.GetExternalAngle(pos1, pos2, pos3);
            }

            const double Tolerance = 0.01;

            if (Math.Abs(externalAngleSum - 2 * Math.PI) < Tolerance)
            {
                return(Orientation.Counterclockwise);
            }
            else if (Math.Abs(externalAngleSum + 2 * Math.PI) < Tolerance)
            {
                return(Orientation.Clockwise);
            }
            else
            {
                throw new OrientationException($"Failed to determine the orientation of {closedPath}; external angle sum was {externalAngleSum}.");
            }
        }
        /// <summary>
        /// Finds the faces of the specified graph.
        /// </summary>
        /// <typeparam name="TVertex">The type of the vertices.</typeparam>
        /// <param name="graph">The graph whose faces to find.</param>
        /// <param name="boundingCycles">Output parameter for the collection of faces, each
        /// represented by a bounding cycle oriented counterclockwise.</param>
        /// <returns><see langword="true"/> if the search was successful (or equivalently, the
        /// graph is plane). <see langword="false"/> if the search failed (or equivalently, the
        /// graph is not plane).</returns>
        public bool TryFindFaces <TVertex>(IReadOnlyUndirectedGraph <TVertex> graph, out IEnumerable <IEnumerable <TVertex> > boundingCycles)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>, IVertexInPlane
        {
            if (graph is null)
            {
                throw new ArgumentNullException(nameof(graph));
            }

            if (!graph.IsPlane())
            {
                boundingCycles = null;
                return(false);
            }

            boundingCycles = SearchForFaces(graph, Orientation.Counterclockwise);
            return(true);
        }
        /// <summary>
        /// Returns a boolean value indicating whether the quiver is plane, i.e., whether the
        /// arrows (drawn as straight lines) are pairwise non-intersecting.
        /// </summary>
        /// <returns>A boolean value indicating whether the quiver is plane.</returns>
        public static bool IsPlane <TVertex>(this IReadOnlyUndirectedGraph <TVertex> graph) where TVertex : IEquatable <TVertex>, IVertexInPlane
        {
            // Remark: Deconstructing lambda arguments does not seem to be possible as of this writing. In other words,
            // the following would not work:
            //     ... .Where( ((e1, e2)) => e1 != e2 )
            foreach (var(edge1, edge2) in Utility.CartesianProduct(graph.Edges, graph.Edges).Where(p => p.Item1 != p.Item2))
            {
                var lineSegment1 = new OrientedLineSegment(edge1.Vertex1.Position, edge1.Vertex2.Position);
                var lineSegment2 = new OrientedLineSegment(edge2.Vertex1.Position, edge2.Vertex2.Position);
                if (lineSegment1.IntersectsProperly(lineSegment2))
                {
                    return(false);
                }
            }

            return(true);
        }
        private IEnumerable <IEnumerable <TVertex> > SearchForFaces <TVertex>(IReadOnlyUndirectedGraph <TVertex> graph, Orientation searchOrientation)
            where TVertex : IEquatable <TVertex>, IComparable <TVertex>, IVertexInPlane
        {
            // Algorithm (outline):
            // Keep track of traversed edges *and the direction in which the edge was traversed*
            // (i.e., keep track of traversed directed edges).
            //
            // For every directed edge (not already traversed), make a search:
            //   Keep track of the path of edges traversed.
            //   Start by traversing that edge.
            //   Keep turning "maximally" in the direction given by the search orientation until a vertex is visited twice.
            //   This gives a full path that decomposes into a path and a cycle.
            //   The path always consists of only boundary directed edges (boundary w.r.t. the search orientation),
            //   while the cycle bounds a (bounded) face precisely when its orientation agrees with the search orientation.

            // Remark: Sort of bad to use Arrow here (writing a DirectedEdge class with a common interface would be a proper solution),
            // but it allows so much code to be reused.

            var cycles              = new HashSet <DetachedCycle <TVertex> >();
            var directedEdges       = graph.Edges.SelectMany(e => new Arrow <TVertex>[] { new Arrow <TVertex>(e.Vertex1, e.Vertex2), new Arrow <TVertex>(e.Vertex2, e.Vertex1) });
            var remainingEdges      = new HashSet <Arrow <TVertex> >(directedEdges);
            var remainingEdgesStack = new Stack <Arrow <TVertex> >(directedEdges);

            while (remainingEdges.Count > 0)
            {
                Arrow <TVertex> startEdge;
                while (!remainingEdges.Contains(startEdge = remainingEdgesStack.Pop()))
                {
                    ;
                }

                var path = new Path <TVertex>(startEdge.Source, startEdge.Target);

                var prevVertex = startEdge.Source;
                var curVertex  = startEdge.Target;

                // Begin the search for this start edge!
                while (true)
                {
                    // Terminate the search if we have found a cycle
                    if (path.TryExtractTrailingCycle(out var closedPath, out int startIndex))
                    {
                        var cycleOrientation = graph.GetOrientationOfCycle(closedPath.Vertices);
                        // If the orientations agree, then the cycle is a bounding cycle for a (bounded) face
                        // Else, it bounds the unbounded face (which we do not care about)
                        if (cycleOrientation == searchOrientation)
                        {
                            var detachedCycle = new DetachedCycle <TVertex>(closedPath);

                            // When restarting with a boundary arrow (directed edge), we might get the same cycle again
                            if (!cycles.Contains(detachedCycle))
                            {
                                cycles.Add(new DetachedCycle <TVertex>(closedPath));
                            }
                        }

                        foreach (var edge in path.Arrows)
                        {
                            remainingEdges.Remove(edge);
                        }
                        break;
                    }

                    // Get next successor (next in the angle sense)
                    // If no successor, terminate the search (all arrows are direction-boundary arrows)
                    // Else, update path, prevVertex and curVertex and do next iteration

                    var neighbors = graph.AdjacencyLists[curVertex]; // Note that this includes prevVertex
                    if (neighbors.Count == 1)                        // The last edge was a dead end!
                    {
                        foreach (var arrow in path.Arrows)
                        {
                            remainingEdges.Remove(arrow);
                        }
                        break;
                    }

                    var baseVertex = curVertex;
                    var basePos    = baseVertex.Position;

                    // Sort the vertices by angle so that the vertex following the predecessor vertex (prevVertex) is
                    // the vertex corresponding to a "maximal" turn.
                    var neighborsSortedByAngle = searchOrientation == Orientation.Clockwise ?
                                                 neighbors.OrderBy(vertex => vertex.Position, new AngleBasedPointComparer(basePos)) :
                                                 neighbors.OrderByDescending(vertex => vertex.Position, new AngleBasedPointComparer(basePos));

                    var neighborsSortedByAngleList = new CircularList <TVertex>(neighborsSortedByAngle);
                    int predecessorIndex           = neighborsSortedByAngleList.IndexOf(prevVertex);
                    neighborsSortedByAngleList.RotateLeft(predecessorIndex);

                    var nextVertex = neighborsSortedByAngleList.Skip(1).First();

                    path       = path.AppendVertex(nextVertex);
                    prevVertex = curVertex;
                    curVertex  = nextVertex;
                }
            }

            return(cycles.Select(cycle => cycle.CanonicalPath.Vertices));
        }