/*
 * 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 test.jts.perf.algorithm;

import java.util.Iterator;
import java.util.List;

import org.locationtech.jts.algorithm.RayCrossingCounter;
import org.locationtech.jts.algorithm.locate.PointOnGeometryLocator;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Location;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.util.LinearComponentExtracter;
import org.locationtech.jts.index.SpatialIndex;
import org.locationtech.jts.index.chain.MonotoneChain;
import org.locationtech.jts.index.chain.MonotoneChainBuilder;
import org.locationtech.jts.index.chain.MonotoneChainSelectAction;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.noding.BasicSegmentString;
import org.locationtech.jts.noding.SegmentString;


/**
 * Determines the location of {@link Coordinate}s relative to
 * a {@link Polygonal} geometry, using indexing for efficiency.
 * This algorithm is suitable for use in cases where
 * many points will be tested against a given area.
 * 
 * @author Martin Davis
 *
 */
public class MCIndexedPointInAreaLocator 
	implements PointOnGeometryLocator
{
	private Geometry areaGeom;
	private MCIndexedGeometry index;
	private double maxXExtent;
		
	public MCIndexedPointInAreaLocator(Geometry g)
	{
		areaGeom = g;
		if (! (g instanceof Polygonal))
			throw new IllegalArgumentException("Argument must be Polygonal");
		buildIndex(g);
    Envelope env = g.getEnvelopeInternal();
		maxXExtent = env.getMaxX() + 1.0;
	}
	
	private void buildIndex(Geometry g)
	{
		index = new MCIndexedGeometry(g);
	}
		
  /**
   * Determines the {@link Location} of a point in an areal {@link Geometry}.
   * 
   * @param p the point to test
   * @return the location of the point in the geometry  
   */
	public int locate(Coordinate p)
	{
		RayCrossingCounter rcc = new RayCrossingCounter(p);
		MCSegmentCounter mcSegCounter = new MCSegmentCounter(rcc);
		Envelope rayEnv = new Envelope(p.x, maxXExtent, p.y, p.y);
		List mcs = index.query(rayEnv);
		countSegs(rcc, rayEnv, mcs, mcSegCounter);
		
		return rcc.getLocation();
	}
	
	private void countSegs(RayCrossingCounter rcc, Envelope rayEnv, List monoChains, MCSegmentCounter mcSegCounter)
	{
		for (Iterator i = monoChains.iterator(); i.hasNext(); ) {
			MonotoneChain mc = (MonotoneChain) i.next();
			mc.select(rayEnv, mcSegCounter);
			// short-circuit if possible
			if (rcc.isOnSegment()) return;
		}
	}
	
  static class MCSegmentCounter extends MonotoneChainSelectAction
  {
  	RayCrossingCounter rcc;

    public MCSegmentCounter(RayCrossingCounter rcc)
    {
      this.rcc = rcc;
    }

    public void select(LineSegment ls)
    {
      rcc.countSegment(ls.getCoordinate(0), ls.getCoordinate(1));
    }
  }

}

class MCIndexedGeometry
{
  private SpatialIndex index= new STRtree();

	public MCIndexedGeometry(Geometry geom)
	{
		init(geom);
	}
	
	private void init(Geometry geom)
	{
		List lines = LinearComponentExtracter.getLines(geom);
		for (Iterator i = lines.iterator(); i.hasNext(); ) {
			LineString line = (LineString) i.next();
			Coordinate[] pts = line.getCoordinates();
			addLine(pts);
		}
	}
	
	private void addLine(Coordinate[] pts)
	{
			SegmentString segStr = new BasicSegmentString(pts, null);
	    List segChains = MonotoneChainBuilder.getChains(segStr.getCoordinates(), segStr);
	    for (Iterator i = segChains.iterator(); i.hasNext(); ) {
	      MonotoneChain mc = (MonotoneChain) i.next();
	      index.insert(mc.getEnvelope(), mc);
	    }
	}
	
	public List query(Envelope searchEnv)
	{
		return index.query(searchEnv);
	}
}


