import java.util.Random;

public class Heuristics {
	
	/********************
	 * Helper Functions *
	 ********************/
	
	/**
	 * Generates a Pair array of random length (10-15) with random (x, y) values (0, 9)
	 * 
	 * @return a random Pair array
	 */
	private static Pair[] generatePairArray() {
		Random rand = new Random();
		int length = 10 + rand.nextInt(5); //array length (10-15)
		int x = rand.nextInt(10);
		int y = rand.nextInt(10);
		Pair[] arr = new Pair[length];
		
		for(int i = 0; i < length; i++) {
			arr[i] = new Pair(x, y);
			x = rand.nextInt(10);
			y = rand.nextInt(10);
		}
		
		return arr;
	}

	/**
	 * Calculates Euclidean distance between two (x, y) points
	 * 
	 * @param a a Pair
	 * @param b a Pair
	 * @return the distance between a and b
	 */
	private static double calculateDistance(Pair a, Pair b) {
		int x1 = a.getX();
		int x2 = b.getX();
		int y1 = a.getY();
		int y2 = b.getY();
		double t = (double) x1 - x2;
		double s = (double) y1 - y2;
		double dist = Math.sqrt(Math.pow(t, 2) + (Math.pow(s, 2)));

		return dist;
	}
	
	/**
	 * Loops through every element in an array, starting at index, and finds
	 * element that is closest to Pair p
	 * 
	 * @param p a Pair
	 * @param index a non-negative integer
	 * @param arr a Pair array
	 * @return the index i such that arr[i] is closest element in arr to p
	 */
	private static int findShortestDistanceIndex(Pair p, int index, Pair[] arr) {
		double temp = Double.POSITIVE_INFINITY;
		int j = -1;
		for (int i = index; i < arr.length; i++) {
			double sqrt = calculateDistance(p, arr[i]);
			if (sqrt < temp) {
				temp = sqrt;
				j = i;
			}
		}
		return j;
	}

	/**
	 * Swaps elements arr[i] and arr[j]
	 * 
	 * @param arr a Pair array
	 * @param i a non-negative integer
	 * @param j a non-negative integer
	 */
	private static void swap(Pair[] arr, int i, int j) {
		Pair temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	
	/**
	 * Sees if a given x and y exists in a Pair array. If found, returns a Pair containing the index
	 * the Pair is located in arr, as well as the boolean TRUE. If not found, a Pair containing an
	 * index of -1 and the boolean FALSE is returned.
	 * 
	 * @param arr a Pair array
	 * @param x an integer
	 * @param y an integer
	 * @return a Pair containing the index of the desired (x, y) Pair and a boolean
	 */
	private static Pair contains(Pair[] arr, int x, int y) {
		for(int i = 0; i < arr.length; i++) {
			if((arr[i].getX() == x) && (arr[i].getY() == y)) {
				return new Pair(i, true);
			}
		}
		return new Pair(-1, false);
	}
	
	/**
	 * Searches through a Pair array and returns the Pair with the largest y-value
	 * 
	 * @param arr a Pair array
	 * @return the Pair in arr with the largest y-value
	 */
	private static Pair findMaxY(Pair[] arr) {
		int min = (int)Double.NEGATIVE_INFINITY;
		Pair ret = new Pair(min, min);
		
		for(int i = 0; i < arr.length; i++) {
			if(arr[i].getY() > ret.getY()) {
				ret.setX(arr[i].getX());
				ret.setY(arr[i].getY());
			}
		}
		return ret;
	}
	
	/**
	 * Given a Pair array, calculates the distance between each of the elements
	 * 
	 * @param arr a Pair array
	 */
	private static void calculateTotalDistance(Pair[] arr) {
		int totalDistance = 0;
		for (int i = 0; i < arr.length - 1; i++) {
			double dist = calculateDistance(arr[i], arr[i+1]);
			totalDistance += dist;
		}
		System.out.println("Total Distance: " + totalDistance);
	}
	
	/*********************
	 * Homework Problems *
	 *********************/

	/**
	 * Orders an array of Pairs by selecting a random element in arr, finding
	 * the closest element in arr to that random element, then repeating, comparing consecutive
	 * elements as it continues down arr
	 * 
	 * @param arr a Pair array
	 */
	public static void closestPointNext(Pair[] arr) {
		if (arr.length > 2) {
			/* picks random element and moves to front of array */
			Random rand = new Random();
			int i = rand.nextInt(arr.length);
			swap(arr, 0, i);

			/* walks through each element of arr and determines which element is closest to it,
			 * then places that at next index in arr 
			 */
			for (int j = 0; j < arr.length - 1; j++) {
				int index = findShortestDistanceIndex(arr[j], j + 1, arr);
				if (index > -1) {
					swap(arr, j + 1, index);
				}
			}
		}
	}
	
	/**
	 * Does a diagonal sweep from the top-left corner to the bottom-right corner, placing the elements
	 * in arr in order as they are found along this search. If two elements are on the same diagonal,
	 * the leftmost one is seen first
	 * 
	 * @param arr a Pair array
	 */
	public static void sweepDiagonal(Pair[] arr) {
		int index = 0;
		int location = 0; //placeholder for where to swap matches in arr
		int round = 0;
		
		Pair p = findMaxY(arr);
		int max = p.getX() + p.getY(); //max y search is x + y from (x, y) pair found in findMaxY
		
		for(int y = 0; y < max; y++) {
			int x = 0;
			while (y > -1) {
				/* if arr contains (x, y), put it next in arr */
				if (contains(arr, x, y).getZ()) {
					index = contains(arr, x, y).getX();
					swap(arr, index, location);
					location++;
				}
				y--;
				x++;
			}
			
			/* keeps track of how many times y was decremented, so the next round of searching continues
			 * on the next diagonal further out from the origin
			 */
			round++;
			y = round;
		}
	}
	
	/**
	 * Randomly orders arr
	 * 
	 * @param arr a Pair array
	 */
	public static void randomOrder(Pair[] arr) {
		if (arr.length > 2) {
			/* picks a random element in arr and puts that next in line */
			Random rand = new Random();
			for (int i = 0; i < arr.length; i++) {
				/* chooses an element that hasn't been selected yet */
				int index = i + rand.nextInt(arr.length - i);
				swap(arr, i, index);
			}
		}
	}
	
	/**
	 * Orders an array of Pairs by selecting a random element in arr, finding
	 * the closest element in arr to that random element, then repeating, ordering
	 * arr by elements that are progressively farther away from the original random element
	 * 
	 * @param arr
	 */
	public static void closestToStartingPoint(Pair[] arr) {
		if (arr.length > 2) {
			/* picks random element of arr and moves to front of array */
			Random rand = new Random();
			int i = rand.nextInt(arr.length);
			swap(arr, 0, i);

			/* walks through each element of arr and determines which element is closest to 
			 * the starting point, arr[0] 
			 */
			for (int j = 1; j < arr.length - 1; j++) {
				int index = findShortestDistanceIndex(arr[0], j, arr);
				if (index > -1) {
					swap(arr, j, index);
				}
			}
		}
	}

	public static void main(String[] args) {
		
		/* An array to hold 10 Pair arrays */
		Pair[][] pairArr = {null, null, null, null, null, null, null, null, null, null};
		
		/* Sets each element of pairArr to be a randomized Pair array */
		for(int i = 0; i < pairArr.length; i++) {
			pairArr[i] = generatePairArray();
		}

		/* Walks through every Pair array within pairArr and performs four heuristics while also 
		 * calculating total distance traveled between points for each heuristic 
		 */
		for(int i = 0; i < pairArr.length; i++) {
			System.out.println("Original array:");
			
			/* Prints out starting Pair array */
			for (int j = 0; j < pairArr[i].length; j++) {
				System.out.print("(" + pairArr[i][j].getX() + ", " + pairArr[i][j].getY() + ")\n");
			}
			
			System.out.println(); //added for clarity of console output

			/* Closest Point First*/
			closestPointNext(pairArr[i]);
			System.out.print("Closest Point First ");
			calculateTotalDistance(pairArr[i]);

			/* Diagonal Sweep */
			sweepDiagonal(pairArr[i]);
			System.out.print("Diagonal ");
			calculateTotalDistance(pairArr[i]);

			/* Random Order */
			randomOrder(pairArr[i]);
			System.out.print("Random ");
			calculateTotalDistance(pairArr[i]);
			
			/* Personal Heuristic */
			closestToStartingPoint(pairArr[i]);
			System.out.print("Personal ");
			calculateTotalDistance(pairArr[i]);
			
			System.out.println(); //added for clarity of console output
		}
	}
}
