/*
 * 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.operation.buffer;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LineString;


/**
 * Generates random {@link LineString}s, which are somewhat coherent
 * in terms of how far they deviate from a given line segment,
 * and how much they twist around.
 * <p>
 * The method is to recursively perturb line segment midpoints by a random offset.  
 * 
 * @author mbdavis
 *
 */
public class RandomOffsetLineStringGenerator
{
	public static Geometry generate(double maxSegLen, int numPts, GeometryFactory fact)
	{
		RandomOffsetLineStringGenerator rlg = new RandomOffsetLineStringGenerator(maxSegLen, numPts);
		return rlg.generate(fact);
	}
	
	private double maxSegLen;
	private int numPts;
	private int exponent2 = 5;
	private Coordinate[] pts;
	private Coordinate endPoint;
	
	
	public RandomOffsetLineStringGenerator(double maxSegLen, int numPts)
	{
		this.maxSegLen = maxSegLen;
		
		exponent2 = (int) (Math.log(numPts) / Math.log(2));
		int pow2 = pow2(exponent2);
		if (pow2 < numPts) 
			exponent2 += 1;
		
		this.numPts = pow2(exponent2) + 1;
	}

	public Geometry generate(GeometryFactory fact)
	{
		pts = new Coordinate[numPts];
		
		pts[0] = new Coordinate();
		
		double ang = Math.PI * Math.random();
		endPoint = new Coordinate(maxSegLen * Math.cos(ang),maxSegLen * Math.sin(ang));
		pts[numPts - 1] = endPoint;
		
		
		int interval = numPts / 2;
		while (interval >= 1) {
			createRandomOffsets(interval);
			interval /= 2;
		}
		return fact.createLineString(pts);
	}
	
	private void createRandomOffsets(int interval)
	{
//		for (int i = 0; i )
		int inc = pow2(exponent2);
		
		while (inc > 1) {
			computeRandomOffsets(inc);
			inc /= 2;
		}
	}
	
	private void computeRandomOffsets(int inc)
	{
		int inc2 = inc / 2;
		for (int i = 0; i + inc2 < numPts; i += inc) {
			int midIndex = i + inc2;
			int endIndex = i + inc;
			
			Coordinate segEndPoint;
			
			double segFrac = 0.5 + randomFractionPerturbation();
			
			if (endIndex >= numPts) {
				segEndPoint = endPoint;
				segFrac = midIndex / numPts;
			}
			else {
				segEndPoint = pts[i + inc];
			}
			pts[midIndex] = computeRandomOffset(pts[i], segEndPoint, segFrac);
		}
	}
	
	private Coordinate computeRandomOffset(Coordinate p0, Coordinate p1, double segFrac)
	{
		double len = p0.distance(p1);
		double len2 = len / 2;
		double offsetLen = (len * Math.random()) - len2;
		LineSegment seg = new LineSegment(p0, p1);
		return seg.pointAlongOffset(segFrac, offsetLen);
	}
	
	private double randomFractionPerturbation()
	{
		double rnd = Math.random();
		double mag = rnd * rnd * rnd;
		int sign = Math.random() > 0.5 ? 1 : -1;
		return sign * mag;
	}
	
	private static int pow2(int exponent)
	{
		int pow2 = 1;
		for (int i = 0; i < exponent; i++) {
			pow2 *= 2;
		}
		return pow2;
	}

}
