/*
 * 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.jtstest.testbuilder.geom;

import java.util.*;

import org.locationtech.jts.geom.*;

/**
 * Locates the paths to facets (vertices and segments) of 
 * a {@link Geometry} which are within a given tolerance
 * of a query point.
 * 
 *  
 * @author Martin Davis
 *
 */
public class FacetLocater 
{
	/**
	 * Creates a list containing all the vertex {@link GeometryLocation}s
	 * in the input collection.
	 * 
	 * @param locations the source collection
	 * @return a list of the vertex locations, if any
	 */
	public static List filterVertexLocations(Collection locations)
	{
		ArrayList vertexLocs = new ArrayList();
		for (Iterator i = locations.iterator(); i.hasNext(); ) {
			GeometryLocation loc = (GeometryLocation) i.next();
			if (loc.isVertex()) vertexLocs.add(loc);
		}
		return vertexLocs;
	}
	
  private Geometry parentGeom;
  private List locations = new ArrayList();
  private Coordinate queryPt;
  private double tolerance = 0.0; 
  
  public FacetLocater(Geometry parentGeom) {
    this.parentGeom = parentGeom;
  }
  
  public List getLocations(Coordinate queryPt, double tolerance)
  {
  	this.queryPt = queryPt;
  	this.tolerance = tolerance;
    findLocations(parentGeom, locations);
    return locations;
  }
  
  private void findLocations(Geometry geom, List locations)
  {
    findLocations(new Stack(), parentGeom, locations);
  }
    
  private void findLocations(Stack path, Geometry geom, List locations)
  {
  	if (geom instanceof GeometryCollection) {
  		for (int i = 0; i < geom.getNumGeometries(); i++ ) {
  			Geometry subGeom = geom.getGeometryN(i);
  			path.push(i);
  			findLocations(path, subGeom, locations);
  			path.pop();
  		}
  	}
  	else if (geom instanceof Polygon) { 
  			findLocations(path, (Polygon) geom, locations);

  	}
  	else {
  		CoordinateSequence seq;
  	
  		if (geom instanceof LineString) {
   		 seq = ((LineString) geom).getCoordinateSequence();
  		}
  		else if (geom instanceof Point) {
  		 seq = ((Point) geom).getCoordinateSequence();
  		}
  		else {
  			throw new IllegalStateException("Unknown geometry type: " + geom.getClass().getName());
  		}
  		findLocations(path, geom, seq, locations);
  	}
  }
  
  private void findLocations(Stack path, Polygon poly, List locations)
  {
		path.push(0);
		findLocations(path, 
				poly.getExteriorRing(),
				poly.getExteriorRing().getCoordinateSequence(), locations);
		path.pop();
		
		for (int i = 0; i < poly.getNumInteriorRing(); i++ ) {
			path.push(i + 1);
			findLocations(path, 
					poly.getInteriorRingN(i), 
					poly.getInteriorRingN(i).getCoordinateSequence(), locations);
			path.pop();
		}
  }

  private void findLocations(Stack path, Geometry compGeom, CoordinateSequence seq, List locations)
  {
  	findVertexLocations(path, compGeom, seq, locations);
  	findSegmentLocations(path, compGeom, seq, locations);
  }

  private void findVertexLocations(Stack path, Geometry compGeom, CoordinateSequence seq, List locations)
  {
  	for (int i = 0; i < seq.size(); i++) {
      Coordinate p = seq.getCoordinate(i);
      double dist = p.distance(queryPt);
      if (dist <= tolerance) 
      	locations.add(new GeometryLocation(parentGeom, compGeom, toIntArray(path), i, true, p));
  	}
  }

  private void findSegmentLocations(Stack path, Geometry compGeom, CoordinateSequence seq, List locations)
  {
  	LineSegment seg = new LineSegment();
  	for (int i = 0; i < seq.size() - 1; i++) {
      seg.p0 = seq.getCoordinate(i);
      seg.p1 = seq.getCoordinate(i+1);
      double dist = seg.distance(queryPt);
      if (dist <= tolerance) 
      	locations.add(new GeometryLocation(parentGeom, compGeom, toIntArray(path), i, false, seg.p0));
  	}
  }

	public static int[] toIntArray(Vector path)
	{
		int[] index = new int[path.size()];
		int i = 0;
		for (Iterator it = path.iterator(); it.hasNext(); ) {
			Integer pathIndex = (Integer) it.next();
			index[i++] = pathIndex.intValue();
		}
		return index;
	}

}
