package tim.prune.function;

import javax.swing.JOptionPane;

import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Track;
import tim.prune.undo.UndoInterpolate;

/**
 * Function to interpolate between the points in a range
 */
public class InterpolateFunction extends SingleNumericParameterFunction
{
	/**
	 * Constructor
	 * @param inApp app object
	 */
	public InterpolateFunction(App inApp) {
		super(inApp, 1, 1000);
	}

	/** @return name key */
	public String getNameKey() {
		return "function.interpolate";
	}

	/** @return description key for input parameter */
	public String getDescriptionKey() {
		return "dialog.interpolate.parameter.text";
	}

	/** @return current (or default) parameter value */
	public int getCurrentParamValue() {
		return 0;
	}

	/**
	 * Perform the operation
	 */
	public void begin()
	{
		// not needed, we just use the completeFunction method instead
	}

	/**
	 * Complete the function after the input parameter has been chosen
	 */
	public void completeFunction(int inParam)
	{
		// Firstly, work out whether the selected range only contains waypoints or not
		final int startIndex = _app.getTrackInfo().getSelection().getStart();
		final int endIndex   = _app.getTrackInfo().getSelection().getEnd();
		boolean betweenWaypoints = false;
		// if there are only waypoints, then ask whether to interpolate them
		if (!selectedRangeHasTrackpoints(_app.getTrackInfo().getTrack(), startIndex, endIndex))
		{
			int answer = JOptionPane.showConfirmDialog(_parentFrame,
				I18nManager.getText("dialog.interpolate.betweenwaypoints"),
				I18nManager.getText(getNameKey()), JOptionPane.YES_NO_OPTION);
			if (answer == JOptionPane.NO_OPTION) {
				// user said no, so nothing to do
				return;
			}
			betweenWaypoints = true;
		}

		if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex) {
			return;
		}

		// construct new point array with the interpolated points
		final int numToAdd = inParam;
		final Track track = _app.getTrackInfo().getTrack();
		final int maxToAdd = (endIndex-startIndex) * numToAdd;
		final int extendedSize = track.getNumPoints() + maxToAdd;
		DataPoint[] oldPoints = track.cloneContents();
		DataPoint[] newPoints = new DataPoint[extendedSize];
		// Copy points before
		System.arraycopy(oldPoints, 0, newPoints, 0, startIndex);
		// Loop, copying points and interpolating
		int destIndex = startIndex;
		DataPoint prevPoint = null;
		for (int i=startIndex; i<= endIndex; i++)
		{
			DataPoint p = _app.getTrackInfo().getTrack().getPoint(i);
			if (prevPoint != null && ((p.isWaypoint() && betweenWaypoints) || (!p.isWaypoint() && !p.getSegmentStart())))
			{
				// interpolate between the previous point and this one
				DataPoint[] addition = prevPoint.interpolate(p, numToAdd);
				System.arraycopy(addition, 0, newPoints, destIndex, numToAdd);
				destIndex += numToAdd;
			}
			// copy point
			newPoints[destIndex] = p;
			destIndex++;
			if (!p.isWaypoint() || betweenWaypoints)
			{
				prevPoint = p;
			}
			else if (!p.isWaypoint()) {
				prevPoint = null;
			}
			// If it's a waypoint, then keep the old prevPoint
		}
		final int totalInserted = destIndex - endIndex - 1;
		// Copy the points after the selected range
		System.arraycopy(oldPoints, endIndex, newPoints, destIndex-1, track.getNumPoints()-endIndex);

		// If necessary, make a new array of the correct size and do another arraycopy into it
		final int newTotalPoints = track.getNumPoints() + totalInserted;
		if (newTotalPoints != newPoints.length)
		{
			DataPoint[] croppedPoints = new DataPoint[newTotalPoints];
			System.arraycopy(newPoints, 0, croppedPoints, 0, newTotalPoints);
			newPoints = croppedPoints;
		}

		// Make undo object
		UndoInterpolate undo = new UndoInterpolate(_app.getTrackInfo(), totalInserted);
		// Replace track with new points array
		if (track.replaceContents(newPoints))
		{
			_app.completeFunction(undo, I18nManager.getText("confirm.interpolate"));
			// Alter selection
			_app.getTrackInfo().getSelection().selectRange(startIndex, endIndex + totalInserted);
		}
	}

	/**
	 * Check if the given Track has trackpoints in the specified range
	 * @param inTrack track object
	 * @param inStart start index
	 * @param inEnd end index
	 * @return true if there are any non-waypoints in the range
	 */
	private static boolean selectedRangeHasTrackpoints(Track inTrack, int inStart, int inEnd)
	{
		for (int i=inStart; i<= inEnd; i++)
		{
			DataPoint p = inTrack.getPoint(i);
			if (p != null && !p.isWaypoint()) {
				return true;
			}
		}
		return false;
	}
}
