/*
 * Copyright (c) 2016 Vivid Solutions.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */

package org.locationtech.jts.triangulate.quadedge;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Triangle;
import org.locationtech.jts.io.WKTWriter;


/**
 * A class that contains the {@link QuadEdge}s representing a planar
 * subdivision that models a triangulation. 
 * The subdivision is constructed using the
 * quadedge algebra defined in the class {@link QuadEdge}.
 * All metric calculations
 * are done in the {@link Vertex} class.
 * In addition to a triangulation, subdivisions
 * support extraction of Voronoi diagrams.
 * This is easily accomplished, since the Voronoi diagram is the dual
 * of the Delaunay triangulation.
 * <p>
 * Subdivisions can be provided with a tolerance value. Inserted vertices which
 * are closer than this value to vertices already in the subdivision will be
 * ignored. Using a suitable tolerance value can prevent robustness failures
 * from happening during Delaunay triangulation.
 * <p>
 * Subdivisions maintain a <b>frame</b> triangle around the client-created
 * edges. The frame is used to provide a bounded "container" for all edges
 * within a TIN. Normally the frame edges, frame connecting edges, and frame
 * triangles are not included in client processing.
 * 
 * @author David Skea
 * @author Martin Davis
 */
public class QuadEdgeSubdivision {
	/**
	 * Gets the edges for the triangle to the left of the given {@link QuadEdge}.
	 * 
	 * @param startQE
	 * @param triEdge
	 * 
	 * @throws IllegalArgumentException
	 *           if the edges do not form a triangle
	 */
	public static void getTriangleEdges(QuadEdge startQE, QuadEdge[] triEdge) {
		triEdge[0] = startQE;
		triEdge[1] = triEdge[0].lNext();
		triEdge[2] = triEdge[1].lNext();
		if (triEdge[2].lNext() != triEdge[0])
			throw new IllegalArgumentException("Edges do not form a triangle");
	}

	private final static double EDGE_COINCIDENCE_TOL_FACTOR = 1000;

	// debugging only - preserve current subdiv statically
	// private static QuadEdgeSubdivision currentSubdiv;

	// used for edge extraction to ensure edge uniqueness
	private int visitedKey = 0;
//	private Set quadEdges = new HashSet();
	private List quadEdges = new ArrayList();
	private QuadEdge startingEdge;
	private double tolerance;
	private double edgeCoincidenceTolerance;
	private Vertex[] frameVertex = new Vertex[3];
	private Envelope frameEnv;
	private QuadEdgeLocator locator = null;

	/**
	 * Creates a new instance of a quad-edge subdivision based on a frame triangle
	 * that encloses a supplied bounding box. A new super-bounding box that
	 * contains the triangle is computed and stored.
	 * 
	 * @param env
	 *          the bounding box to surround
	 * @param tolerance
	 *          the tolerance value for determining if two sites are equal
	 */
	public QuadEdgeSubdivision(Envelope env, double tolerance) {
		// currentSubdiv = this;
		this.tolerance = tolerance;
		edgeCoincidenceTolerance = tolerance / EDGE_COINCIDENCE_TOL_FACTOR;

		createFrame(env);
		
		startingEdge = initSubdiv();
		locator = new LastFoundQuadEdgeLocator(this);
	}

	private void createFrame(Envelope env)
	{
		double deltaX = env.getWidth();
		double deltaY = env.getHeight();
		double offset = 0.0;
		if (deltaX > deltaY) {
			offset = deltaX * 10.0;
		} else {
			offset = deltaY * 10.0;
		}

		frameVertex[0] = new Vertex((env.getMaxX() + env.getMinX()) / 2.0, env
				.getMaxY()
				+ offset);
		frameVertex[1] = new Vertex(env.getMinX() - offset, env.getMinY() - offset);
		frameVertex[2] = new Vertex(env.getMaxX() + offset, env.getMinY() - offset);

		frameEnv = new Envelope(frameVertex[0].getCoordinate(), frameVertex[1]
				.getCoordinate());
		frameEnv.expandToInclude(frameVertex[2].getCoordinate());
	}
	
	private QuadEdge initSubdiv()
	{
		// build initial subdivision from frame
		QuadEdge ea = makeEdge(frameVertex[0], frameVertex[1]);
		QuadEdge eb = makeEdge(frameVertex[1], frameVertex[2]);
		QuadEdge.splice(ea.sym(), eb);
		QuadEdge ec = makeEdge(frameVertex[2], frameVertex[0]);
		QuadEdge.splice(eb.sym(), ec);
		QuadEdge.splice(ec.sym(), ea);
		return ea;
	}
	
	/**
	 * Gets the vertex-equality tolerance value
	 * used in this subdivision
	 * 
	 * @return the tolerance value
	 */
	public double getTolerance() {
		return tolerance;
	}

	/**
	 * Gets the envelope of the Subdivision (including the frame).
	 * 
	 * @return the envelope
	 */
	public Envelope getEnvelope() {
		return new Envelope(frameEnv);
	}

	/**
	 * Gets the collection of base {@link QuadEdge}s (one for every pair of
	 * vertices which is connected).
	 * 
	 * @return a collection of QuadEdges
	 */
	public Collection getEdges() {
		return quadEdges;
	}

	/**
	 * Sets the {@link QuadEdgeLocator} to use for locating containing triangles
	 * in this subdivision.
	 * 
	 * @param locator
	 *          a QuadEdgeLocator
	 */
	public void setLocator(QuadEdgeLocator locator) {
		this.locator = locator;
	}

	/**
	 * Creates a new quadedge, recording it in the edges list.
	 * 
	 * @param o
	 * @param d
	 * @return a new quadedge
	 */
	public QuadEdge makeEdge(Vertex o, Vertex d) {
		QuadEdge q = QuadEdge.makeEdge(o, d);
		quadEdges.add(q);
		return q;
	}

	/**
	 * Creates a new QuadEdge connecting the destination of a to the origin of b,
	 * in such a way that all three have the same left face after the connection
	 * is complete. The quadedge is recorded in the edges list.
	 * 
	 * @param a
	 * @param b
	 * @return a quadedge
	 */
	public QuadEdge connect(QuadEdge a, QuadEdge b) {
		QuadEdge q = QuadEdge.connect(a, b);
		quadEdges.add(q);
		return q;
	}

	/**
	 * Deletes a quadedge from the subdivision. Linked quadedges are updated to
	 * reflect the deletion.
	 * 
	 * @param e
	 *          the quadedge to delete
	 */
	public void delete(QuadEdge e) {
		QuadEdge.splice(e, e.oPrev());
		QuadEdge.splice(e.sym(), e.sym().oPrev());

		QuadEdge eSym = e.sym();
		QuadEdge eRot = e.rot();
		QuadEdge eRotSym = e.rot().sym();

		// this is inefficient on an ArrayList, but this method should be called infrequently
		quadEdges.remove(e);
		quadEdges.remove(eSym);
		quadEdges.remove(eRot);
		quadEdges.remove(eRotSym);

		e.delete();
		eSym.delete();
		eRot.delete();
		eRotSym.delete();
	}

	/**
	 * Locates an edge of a triangle which contains a location 
	 * specified by a Vertex v. 
	 * The edge returned has the
	 * property that either v is on e, or e is an edge of a triangle containing v.
	 * The search starts from startEdge amd proceeds on the general direction of v.
	 * <p>
	 * This locate algorithm relies on the subdivision being Delaunay. For
	 * non-Delaunay subdivisions, this may loop for ever.
	 * 
	 * @param v the location to search for
	 * @param startEdge an edge of the subdivision to start searching at
	 * @return a QuadEdge which contains v, or is on the edge of a triangle containing v
	 * @throws LocateFailureException
	 *           if the location algorithm fails to converge in a reasonable
	 *           number of iterations
	 */
	public QuadEdge locateFromEdge(Vertex v, QuadEdge startEdge) {
		int iter = 0;
		int maxIter = quadEdges.size();

		QuadEdge e = startEdge;

		while (true) {
			iter++;

			/**
			 * So far it has always been the case that failure to locate indicates an
			 * invalid subdivision. So just fail completely. (An alternative would be
			 * to perform an exhaustive search for the containing triangle, but this
			 * would mask errors in the subdivision topology)
			 * 
			 * This can also happen if two vertices are located very close together,
			 * since the orientation predicates may experience precision failures.
			 */
			if (iter > maxIter) {
				throw new LocateFailureException(e.toLineSegment());
				// String msg = "Locate failed to converge (at edge: " + e + ").
				// Possible causes include invalid Subdivision topology or very close
				// sites";
				// System.err.println(msg);
				// dumpTriangles();
			}

			if ((v.equals(e.orig())) || (v.equals(e.dest()))) {
				break;
			} else if (v.rightOf(e)) {
				e = e.sym();
			} else if (!v.rightOf(e.oNext())) {
				e = e.oNext();
			} else if (!v.rightOf(e.dPrev())) {
				e = e.dPrev();
			} else {
				// on edge or in triangle containing edge
				break;
			}
		}
		// System.out.println("Locate count: " + iter);
		return e;
	}

	/**
	 * Finds a quadedge of a triangle containing a location 
	 * specified by a {@link Vertex}, if one exists.
	 * 
	 * @param v the vertex to locate
	 * @return a quadedge on the edge of a triangle which touches or contains the location
	 * or null if no such triangle exists
	 */
	public QuadEdge locate(Vertex v) {
		return locator.locate(v);
	}

	/**
	 * Finds a quadedge of a triangle containing a location
	 * specified by a {@link Coordinate}, if one exists.
	 * 
	 * @param p the Coordinate to locate
	 * @return a quadedge on the edge of a triangle which touches or contains the location
	 * or null if no such triangle exists
	 */
	public QuadEdge locate(Coordinate p) {
		return locator.locate(new Vertex(p));
	}

	/**
	 * Locates the edge between the given vertices, if it exists in the
	 * subdivision.
	 * 
	 * @param p0 a coordinate
	 * @param p1 another coordinate
	 * @return the edge joining the coordinates, if present
	 * or null if no such edge exists
	 */
	public QuadEdge locate(Coordinate p0, Coordinate p1) {
		// find an edge containing one of the points
		QuadEdge e = locator.locate(new Vertex(p0));
		if (e == null)
			return null;

		// normalize so that p0 is origin of base edge
		QuadEdge base = e;
		if (e.dest().getCoordinate().equals2D(p0))
			base = e.sym();
		// check all edges around origin of base edge
		QuadEdge locEdge = base;
		do {
			if (locEdge.dest().getCoordinate().equals2D(p1))
				return locEdge;
			locEdge = locEdge.oNext();
		} while (locEdge != base);
		return null;
	}

	/**
	 * Inserts a new site into the Subdivision, connecting it to the vertices of
	 * the containing triangle (or quadrilateral, if the split point falls on an
	 * existing edge).
	 * <p>
	 * This method does NOT maintain the Delaunay condition. If desired, this must
	 * be checked and enforced by the caller.
	 * <p>
	 * This method does NOT check if the inserted vertex falls on an edge. This
	 * must be checked by the caller, since this situation may cause erroneous
	 * triangulation
	 * 
	 * @param v
	 *          the vertex to insert
	 * @return a new quad edge terminating in v
	 */
	public QuadEdge insertSite(Vertex v) {
		QuadEdge e = locate(v);

		if ((v.equals(e.orig(), tolerance)) || (v.equals(e.dest(), tolerance))) {
			return e; // point already in subdivision.
		}

		// Connect the new point to the vertices of the containing
		// triangle (or quadrilateral, if the new point fell on an
		// existing edge.)
		QuadEdge base = makeEdge(e.orig(), v);
		QuadEdge.splice(base, e);
		QuadEdge startEdge = base;
		do {
			base = connect(e, base.sym());
			e = base.oPrev();
		} while (e.lNext() != startEdge);

		return startEdge;
	}

	/**
	 * Tests whether a QuadEdge is an edge incident on a frame triangle vertex.
	 * 
	 * @param e
	 *          the edge to test
	 * @return true if the edge is connected to the frame triangle
	 */
	public boolean isFrameEdge(QuadEdge e) {
		if (isFrameVertex(e.orig()) || isFrameVertex(e.dest()))
			return true;
		return false;
	}

	/**
	 * Tests whether a QuadEdge is an edge on the border of the frame facets and
	 * the internal facets. E.g. an edge which does not itself touch a frame
	 * vertex, but which touches an edge which does.
	 * 
	 * @param e
	 *          the edge to test
	 * @return true if the edge is on the border of the frame
	 */
	public boolean isFrameBorderEdge(QuadEdge e) {
		// MD debugging
		QuadEdge[] leftTri = new QuadEdge[3];
		getTriangleEdges(e, leftTri);
		// System.out.println(new QuadEdgeTriangle(leftTri).toString());
		QuadEdge[] rightTri = new QuadEdge[3];
		getTriangleEdges(e.sym(), rightTri);
		// System.out.println(new QuadEdgeTriangle(rightTri).toString());

		// check other vertex of triangle to left of edge
		Vertex vLeftTriOther = e.lNext().dest();
		if (isFrameVertex(vLeftTriOther))
			return true;
		// check other vertex of triangle to right of edge
		Vertex vRightTriOther = e.sym().lNext().dest();
		if (isFrameVertex(vRightTriOther))
			return true;

		return false;
	}

	/**
	 * Tests whether a vertex is a vertex of the outer triangle.
	 * 
	 * @param v
	 *          the vertex to test
	 * @return true if the vertex is an outer triangle vertex
	 */
	public boolean isFrameVertex(Vertex v) {
		if (v.equals(frameVertex[0]))
			return true;
		if (v.equals(frameVertex[1]))
			return true;
		if (v.equals(frameVertex[2]))
			return true;
		return false;
	}

	private LineSegment seg = new LineSegment();

	/**
	 * Tests whether a {@link Coordinate} lies on a {@link QuadEdge}, up to a
	 * tolerance determined by the subdivision tolerance.
	 * 
	 * @param e
	 *          a QuadEdge
	 * @param p
	 *          a point
	 * @return true if the vertex lies on the edge
	 */
	public boolean isOnEdge(QuadEdge e, Coordinate p) {
		seg.setCoordinates(e.orig().getCoordinate(), e.dest().getCoordinate());
		double dist = seg.distance(p);
		// heuristic (hack?)
		return dist < edgeCoincidenceTolerance;
	}

	/**
	 * Tests whether a {@link Vertex} is the start or end vertex of a
	 * {@link QuadEdge}, up to the subdivision tolerance distance.
	 * 
	 * @param e
	 * @param v
	 * @return true if the vertex is a endpoint of the edge
	 */
	public boolean isVertexOfEdge(QuadEdge e, Vertex v) {
		if ((v.equals(e.orig(), tolerance)) || (v.equals(e.dest(), tolerance))) {
			return true;
		}
		return false;
	}

  /**
   * Gets the unique {@link Vertex}es in the subdivision,
   * including the frame vertices if desired.
   * 
	 * @param includeFrame
	 *          true if the frame vertices should be included
   * @return a collection of the subdivision vertices
   * 
   * @see #getVertexUniqueEdges
   */
  public Collection getVertices(boolean includeFrame) 
  {
    Set vertices = new HashSet();
    for (Iterator i = quadEdges.iterator(); i.hasNext();) {
      QuadEdge qe = (QuadEdge) i.next();
      Vertex v = qe.orig();
      //System.out.println(v);
      if (includeFrame || ! isFrameVertex(v))
        vertices.add(v);
      
      /**
       * Inspect the sym edge as well, since it is
       * possible that a vertex is only at the 
       * dest of all tracked quadedges.
       */
      Vertex vd = qe.dest();
      //System.out.println(vd);
      if (includeFrame || ! isFrameVertex(vd))
        vertices.add(vd);
    }
    return vertices;
  }

  /**
   * Gets a collection of {@link QuadEdge}s whose origin
   * vertices are a unique set which includes
   * all vertices in the subdivision. 
   * The frame vertices can be included if required.
   * <p>
   * This is useful for algorithms which require traversing the 
   * subdivision starting at all vertices.
   * Returning a quadedge for each vertex
   * is more efficient than 
   * the alternative of finding the actual vertices
   * using {@link #getVertices} and then locating 
   * quadedges attached to them.
   * 
   * @param includeFrame true if the frame vertices should be included
   * @return a collection of QuadEdge with the vertices of the subdivision as their origins
   */
  public List getVertexUniqueEdges(boolean includeFrame) 
  {
  	List edges = new ArrayList();
    Set visitedVertices = new HashSet();
    for (Iterator i = quadEdges.iterator(); i.hasNext();) {
      QuadEdge qe = (QuadEdge) i.next();
      Vertex v = qe.orig();
      //System.out.println(v);
      if (! visitedVertices.contains(v)) {
      	visitedVertices.add(v);
        if (includeFrame || ! isFrameVertex(v)) {
        	edges.add(qe);
        }
      }
      
      /**
       * Inspect the sym edge as well, since it is
       * possible that a vertex is only at the 
       * dest of all tracked quadedges.
       */
      QuadEdge qd = qe.sym();
      Vertex vd = qd.orig();
      //System.out.println(vd);
      if (! visitedVertices.contains(vd)) {
      	visitedVertices.add(vd);
        if (includeFrame || ! isFrameVertex(vd)) {
        	edges.add(qd);
        }
      }
    }
    return edges;
  }

	/**
	 * Gets all primary quadedges in the subdivision. 
   * A primary edge is a {@link QuadEdge}
	 * which occupies the 0'th position in its array of associated quadedges. 
	 * These provide the unique geometric edges of the triangulation.
	 * 
	 * @param includeFrame true if the frame edges are to be included
	 * @return a List of QuadEdges
	 */
	public List getPrimaryEdges(boolean includeFrame) {
		visitedKey++;

		List edges = new ArrayList();
		Stack edgeStack = new Stack();
		edgeStack.push(startingEdge);
		
		Set visitedEdges = new HashSet();

		while (!edgeStack.empty()) {
			QuadEdge edge = (QuadEdge) edgeStack.pop();
			if (! visitedEdges.contains(edge)) {
				QuadEdge priQE = edge.getPrimary();

				if (includeFrame || ! isFrameEdge(priQE))
					edges.add(priQE);

				edgeStack.push(edge.oNext());
				edgeStack.push(edge.sym().oNext());
				
				visitedEdges.add(edge);
				visitedEdges.add(edge.sym());
			}
		}
		return edges;
	}
  
  /**
   * A TriangleVisitor which computes and sets the 
   * circumcentre as the origin of the dual 
   * edges originating in each triangle.
   * 
   * @author mbdavis
   *
   */
	private static class TriangleCircumcentreVisitor implements TriangleVisitor 
	{
		public TriangleCircumcentreVisitor() {
		}

		public void visit(QuadEdge[] triEdges) 
		{
			Coordinate a = triEdges[0].orig().getCoordinate();
			Coordinate b = triEdges[1].orig().getCoordinate();
			Coordinate c = triEdges[2].orig().getCoordinate();
			
			// TODO: choose the most accurate circumcentre based on the edges
      Coordinate cc = Triangle.circumcentreDD(a, b, c);
			Vertex ccVertex = new Vertex(cc);
			// save the circumcentre as the origin for the dual edges originating in this triangle
			for (int i = 0; i < 3; i++) {
				triEdges[i].rot().setOrig(ccVertex);
			}
		}
	}

	/*****************************************************************************
	 * Visitors
	 ****************************************************************************/

	public void visitTriangles(TriangleVisitor triVisitor,
			boolean includeFrame) {
		visitedKey++;

		// visited flag is used to record visited edges of triangles
		// setVisitedAll(false);
		Stack edgeStack = new Stack();
		edgeStack.push(startingEdge);

		Set visitedEdges = new HashSet();
		
		while (!edgeStack.empty()) {
			QuadEdge edge = (QuadEdge) edgeStack.pop();
			if (! visitedEdges.contains(edge)) {
				QuadEdge[] triEdges = fetchTriangleToVisit(edge, edgeStack,
						includeFrame, visitedEdges);
				if (triEdges != null)
					triVisitor.visit(triEdges);
			}
		}
	}

	/**
	 * The quadedges forming a single triangle.
   * Only one visitor is allowed to be active at a
	 * time, so this is safe.
	 */
	private QuadEdge[] triEdges = new QuadEdge[3];

	/**
	 * Stores the edges for a visited triangle. Also pushes sym (neighbour) edges
	 * on stack to visit later.
	 * 
	 * @param edge
	 * @param edgeStack
	 * @param includeFrame
	 * @return the visited triangle edges
	 * or null if the triangle should not be visited (for instance, if it is
	 *         outer)
	 */
	private QuadEdge[] fetchTriangleToVisit(QuadEdge edge, Stack edgeStack,
			boolean includeFrame, Set visitedEdges) {
		QuadEdge curr = edge;
		int edgeCount = 0;
		boolean isFrame = false;
		do {
			triEdges[edgeCount] = curr;

			if (isFrameEdge(curr))
				isFrame = true;
			
			// push sym edges to visit next
			QuadEdge sym = curr.sym();
			if (! visitedEdges.contains(sym))
				edgeStack.push(sym);
			
			// mark this edge as visited
			visitedEdges.add(curr);
			
			edgeCount++;
			curr = curr.lNext();
		} while (curr != edge);

		if (isFrame && !includeFrame)
			return null;
		return triEdges;
	}

	/**
	 * Gets a list of the triangles
	 * in the subdivision, specified as
	 * an array of the primary quadedges around the triangle.
	 * 
	 * @param includeFrame
	 *          true if the frame triangles should be included
	 * @return a List of QuadEdge[3] arrays
	 */
	public List getTriangleEdges(boolean includeFrame) {
		TriangleEdgesListVisitor visitor = new TriangleEdgesListVisitor();
		visitTriangles(visitor, includeFrame);
		return visitor.getTriangleEdges();
	}

	private static class TriangleEdgesListVisitor implements TriangleVisitor {
		private List triList = new ArrayList();

		public void visit(QuadEdge[] triEdges) {
			triList.add(triEdges);
		}

		public List getTriangleEdges() {
			return triList;
		}
	}

	/**
	 * Gets a list of the triangles in the subdivision,
	 * specified as an array of the triangle {@link Vertex}es.
	 * 
	 * @param includeFrame
	 *          true if the frame triangles should be included
	 * @return a List of Vertex[3] arrays
	 */
	public List getTriangleVertices(boolean includeFrame) {
		TriangleVertexListVisitor visitor = new TriangleVertexListVisitor();
		visitTriangles(visitor, includeFrame);
		return visitor.getTriangleVertices();
	}

	private static class TriangleVertexListVisitor implements TriangleVisitor {
		private List triList = new ArrayList();

		public void visit(QuadEdge[] triEdges) {
			triList.add(new Vertex[] { triEdges[0].orig(), triEdges[1].orig(),
					triEdges[2].orig() });
		}

		public List getTriangleVertices() {
			return triList;
		}
	}

	/**
	 * Gets the coordinates for each triangle in the subdivision as an array.
	 * 
	 * @param includeFrame
	 *          true if the frame triangles should be included
	 * @return a list of Coordinate[4] representing each triangle
	 */
	public List getTriangleCoordinates(boolean includeFrame) {
		TriangleCoordinatesVisitor visitor = new TriangleCoordinatesVisitor();
		visitTriangles(visitor, includeFrame);
		return visitor.getTriangles();
	}

	private static class TriangleCoordinatesVisitor implements TriangleVisitor {
		private CoordinateList coordList = new CoordinateList();

		private List triCoords = new ArrayList();

		public TriangleCoordinatesVisitor() {
		}

		public void visit(QuadEdge[] triEdges) {
			coordList.clear();
			for (int i = 0; i < 3; i++) {
				Vertex v = triEdges[i].orig();
				coordList.add(v.getCoordinate());
			}
			if (coordList.size() > 0) {
				coordList.closeRing();
				Coordinate[] pts = coordList.toCoordinateArray();
				if (pts.length != 4) {
					//checkTriangleSize(pts);
					return;
				}

				triCoords.add(pts);
			}
		}

		private void checkTriangleSize(Coordinate[] pts)
		{
			String loc = "";
			if (pts.length >= 2)
				loc = WKTWriter.toLineString(pts[0], pts[1]);
			else {
				if (pts.length >= 1)
					loc = WKTWriter.toPoint(pts[0]);
			}
			// Assert.isTrue(pts.length == 4, "Too few points for visited triangle at " + loc);
			//com.vividsolutions.jts.util.Debug.println("too few points for triangle at " + loc);
		}
		
		public List getTriangles() {
			return triCoords;
		}
	}

	/**
	 * Gets the geometry for the edges in the subdivision as a {@link MultiLineString}
	 * containing 2-point lines.
	 * 
	 * @param geomFact the GeometryFactory to use
	 * @return a MultiLineString
	 */
	public Geometry getEdges(GeometryFactory geomFact) {
		List quadEdges = getPrimaryEdges(false);
		LineString[] edges = new LineString[quadEdges.size()];
		int i = 0;
		for (Iterator it = quadEdges.iterator(); it.hasNext();) {
			QuadEdge qe = (QuadEdge) it.next();
			edges[i++] = geomFact.createLineString(new Coordinate[] {
					qe.orig().getCoordinate(), qe.dest().getCoordinate() });
		}
		return geomFact.createMultiLineString(edges);
	}

	/**
	 * Gets the geometry for the triangles in a triangulated subdivision as a {@link GeometryCollection}
	 * of triangular {@link Polygon}s.
	 * 
	 * @param geomFact the GeometryFactory to use
	 * @return a GeometryCollection of triangular Polygons
	 */
	public Geometry getTriangles(GeometryFactory geomFact) {
		List triPtsList = getTriangleCoordinates(false);
		Polygon[] tris = new Polygon[triPtsList.size()];
		int i = 0;
		for (Iterator it = triPtsList.iterator(); it.hasNext();) {
			Coordinate[] triPt = (Coordinate[]) it.next();
			tris[i++] = geomFact
					.createPolygon(geomFact.createLinearRing(triPt));
		}
		return geomFact.createGeometryCollection(tris);
	}

	/**
	 * Gets the cells in the Voronoi diagram for this triangulation.
	 * The cells are returned as a {@link GeometryCollection} of {@link Polygon}s
   * <p>
   * The userData of each polygon is set to be the {@link Coordinate}
   * of the cell site.  This allows easily associating external 
   * data associated with the sites to the cells.
	 * 
	 * @param geomFact a geometry factory
	 * @return a GeometryCollection of Polygons
	 */
  public Geometry getVoronoiDiagram(GeometryFactory geomFact)
  {
    List vorCells = getVoronoiCellPolygons(geomFact);
    return geomFact.createGeometryCollection(GeometryFactory.toGeometryArray(vorCells));   
  }
  
	/**
	 * Gets a List of {@link Polygon}s for the Voronoi cells 
	 * of this triangulation.
   * <p>
   * The userData of each polygon is set to be the {@link Coordinate}
   * of the cell site.  This allows easily associating external 
   * data associated with the sites to the cells.
	 * 
	 * @param geomFact a geometry factory
	 * @return a List of Polygons
	 */
  public List getVoronoiCellPolygons(GeometryFactory geomFact)
  {
  	/*
  	 * Compute circumcentres of triangles as vertices for dual edges.
  	 * Precomputing the circumcentres is more efficient, 
  	 * and more importantly ensures that the computed centres
  	 * are consistent across the Voronoi cells.
  	 */ 
  	visitTriangles(new TriangleCircumcentreVisitor(), true);
  	
    List cells = new ArrayList();
    Collection edges = getVertexUniqueEdges(false);
    for (Iterator i = edges.iterator(); i.hasNext(); ) {
    	QuadEdge qe = (QuadEdge) i.next();
      cells.add(getVoronoiCellPolygon(qe, geomFact));
    }
    return cells;
  }
  
  /**
   * Gets the Voronoi cell around a site specified
   * by the origin of a QuadEdge.
   * <p>
   * The userData of the polygon is set to be the {@link Coordinate}
   * of the site.  This allows attaching external 
   * data associated with the site to this cell polygon.
   * 
   * @param qe a quadedge originating at the cell site
   * @param geomFact a factory for building the polygon
   * @return a polygon indicating the cell extent
   */
  public Polygon getVoronoiCellPolygon(QuadEdge qe, GeometryFactory geomFact)
  {
    List cellPts = new ArrayList();
    QuadEdge startQE = qe;
    do {
//    	Coordinate cc = circumcentre(qe);
    	// use previously computed circumcentre
    	Coordinate cc = qe.rot().orig().getCoordinate();
      cellPts.add(cc);
      
      // move to next triangle CW around vertex
      qe = qe.oPrev();
    } while (qe != startQE);
    
    CoordinateList coordList = new CoordinateList();
    coordList.addAll(cellPts, false);
    coordList.closeRing();
    
    if (coordList.size() < 4) {
      System.out.println(coordList);
      coordList.add(coordList.get(coordList.size()-1), true);
    }
    
    Coordinate[] pts = coordList.toCoordinateArray();
    Polygon cellPoly = geomFact.createPolygon(geomFact.createLinearRing(pts));
    
    Vertex v = startQE.orig();
    cellPoly.setUserData(v.getCoordinate());
    return cellPoly;
  }
  
}
