content: package tim.prune.function.comparesegments;
  
  import tim.prune.data.Bearing;
  import tim.prune.data.DataPoint;
  import tim.prune.data.Timestamp;
  import tim.prune.data.Track;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Holds the points and lines of a single segment ready for comparison
   */
  public class SegmentData
  {
  	private final PointSequence _points = new PointSequence();
  	private final ArrayList<LineAndBearing> _lines = new ArrayList<>();
  
  	/** Maximum angle change at a point for it to be considered */
  	private static final double MAX_ANGLE_CHANGE_DEGREES = 45.0;
  
  	public SegmentData(Track inTrack, int inStartIndex)
  	{
  		fillPointData(inTrack, _points, inStartIndex);
  		fillLineData(_points, _lines);
  	}
  
  	/** Fill the given PointSequence object with PointData objects for the specified segment */
  	private static void fillPointData(Track inTrack, PointSequence inPoints, int inStartIndex)
  	{
  		ArrayList<DataPoint> points = createPoints(inTrack, inStartIndex);
  		DataPoint prevPoint = null;
  		DataPoint point = null;
  		double radiansSoFar = 0.0;
  		// Loop through the points in threes, taking prevPoint, point and nextPoint
  		for (DataPoint nextPoint : points)
  		{
  			if (point != null)
  			{
  				// Here we consider the point with the one before it and the one after it
  				final double currRadians = DataPoint.calculateRadiansBetween(prevPoint, point);
  				radiansSoFar += currRadians;
  				final Double bearing;
  				if (prevPoint == null) {
  					// first line in segment, just take bearing from this point to the next one
  					bearing = Bearing.calculateDegrees(point, nextPoint);
  				}
  				else if (nextPoint == null) {
  					// last line in segment, just take bearing from prevPoint to point
  					bearing = Bearing.calculateDegrees(prevPoint, point);
  				}
  				else
  				{
  					// line in the middle
  					// Check difference between bearing(prevPrev->prev) and bearing(prev, point)
  					final double angleDiff = Bearing.calculateDegreeChange(prevPoint, point, nextPoint);
  					if (angleDiff > MAX_ANGLE_CHANGE_DEGREES) {
  						bearing = null;
  					}
  					else {
  						bearing = Bearing.calculateDegrees(prevPoint, nextPoint);
  					}
  				}
  				if (bearing != null)
  				{
  					final double speedRadsPerSec = getSpeedRadsPerSec(prevPoint, point, nextPoint);
  					inPoints.addPoint(new PointData(point, bearing, currRadians, radiansSoFar, speedRadsPerSec));
  				}
  			}
  			prevPoint = point;
  			point = nextPoint;
  		}
  	}
  
  	private static double getSpeedRadsPerSec(DataPoint inPrevPoint, DataPoint inPoint, DataPoint inNextPoint)
  	{
  		final Timestamp firstStamp = (inPrevPoint == null ? inPoint.getTimestamp() : inPrevPoint.getTimestamp());
  		final Timestamp secondStamp = (inNextPoint == null ? inPoint.getTimestamp() : inNextPoint.getTimestamp());
  		final long millis = secondStamp.getMillisecondsSince(firstStamp);
  		double totalDistanceRads = DataPoint.calculateRadiansBetween(inPrevPoint, inPoint)
  			+ DataPoint.calculateRadiansBetween(inPoint, inNextPoint);
  		return totalDistanceRads * 1000.0 / millis;
  	}
  
  	/** @return a list of the datapoints in the specified segment, ending with null */
  	private static ArrayList<DataPoint> createPoints(Track inTrack, int inStartIndex)
  	{
  		ArrayList<DataPoint> points = new ArrayList<>();
  		for (int i=inStartIndex; i<inTrack.getNumPoints(); i++)
  		{
  			DataPoint point = inTrack.getPoint(i);
  			if (point == null || point.isWaypoint()) {
  				continue;
  			}
  			if (point.getSegmentStart() && i > inStartIndex) {
  				break; // next segment found
  			}
  			// TODO: Check for a minimum distance between previous point and this one?
  			points.add(point);
  		}
  		// Add a null point at the end so that we can consider the last triplet
  		points.add(null);
  		return points;
  	}
  
  	private void fillLineData(PointSequence inPoints, ArrayList<LineAndBearing> inLines)
  	{
  		PointData prevPoint = null;
  		for (PointData pd : inPoints.getPoints())
  		{
  			if (prevPoint != null) {
  				inLines.add(new LineAndBearing(prevPoint, pd));
  			}
  			prevPoint = pd;
  		}
  	}
  
  	boolean isBefore(SegmentData inOther) {
  		return _points.getFirstTimestamp().isBefore(inOther._points.getFirstTimestamp());
  	}
  
  	// TODO: Could be an immutable copy?
  	PointSequence getPoints() {
  		return _points;
  	}
  
  	// TODO: Could be an immutable copy?
  	List<LineAndBearing> getLines() {
  		return _lines;
  	}
  }
  