package edu.uky.ai.path;

import java.util.HashSet;

import edu.uky.ai.path.gui.*;

/**
 * This exercise will demonstrate how many different kinds of search algorithms
 * can be implemented using the same basic process with slight variations in
 * how the next state is chosen.
 * 
 * The problem being solved in this example is a simple navigation problem in a
 * 2D grid.  Your goal is to guide a robot from its starting location to its
 * destination (marked with a flag).  After each iteration of the search
 * process, the current state of the search will be drawn on the screen to show
 * which locations have been considered, which have not been considered, and
 * which can be considered next.
 * 
 * @author Stephen G. Ware
 */
public class Main {

	/**
	 * Performs a path search and guides the robot to the flag.
	 * 
	 * @param args ignored
	 */
	public static void main(String[] args) {
		// Choose a map.
		Map map = Map.MAP_1;
		// Step 0: Initialize the set of visited locations and the frontier.
		HashSet<Location> visited = new HashSet<>();
		PriorityQueue<Path> frontier = new PriorityQueue<>();
		
		// Create the visualization.
		GridPanel grid = new GridPanel(map, visited, frontier);
		new GridFrame(grid);
		// Search until the frontier is empty.
		
			// Step 1: Remove a path from the frontier. 
			Path current = null;
			// Draw the path on the screen.
			grid.draw(current);
			// Step 2: If this path is a solution (i.e. a path that ends at the
			// destination), then beak out of the loop.  A path is defined by
			// its last location, so you can check if this path is a solution
			// by comparing it to Map#flag.

			// Step 3: (optional) Add the current location to the set of
			// visited locations so that we don't repeat work later by visiting
			// it again.

			// Step 4: Add each possible next unvisited state to the frontier.
			// You can use Location#neighbors() to a get a set of all the
			// locations that are one step away from the current location and
			// are not walls.  You can add a location to the current path by
			// using Path#addToPath(Location).  Be aware that addToPath does
			// not modify the path itself but returns a new path object.
			// Also, don't forget: you should only add a location to the
			// frontier if it has not yet been visited.
		
		// Follow the path.
		follow(grid, map.robot, grid.getPath());
	}
	
	/**
	 * Manhattan distance is the sum of the difference between the horizontal
	 * and vertical positions of two locations.  It is an admissible
	 * heuristic when navigating on a grid because it never overestimates.
	 * You can think of this as the distance you would need to travel between
	 * two locations if there were no walls in the way.
	 * 
	 * @param l1 the first location
	 * @param l2 the second location
	 * @return the Manhattan distance between the two locations
	 */
	private static int manhattan(Location l1, Location l2) {
		return 0;
	}
	
	/**
	 * A recursive method to guide the robot along a path.
	 * 
	 * @param grid the visualization of the map
	 * @param sprite the object to move along the path
	 * @param path the path to follow
	 */
	private static void follow(GridPanel grid, Sprite sprite, Path path) {
		if(path == null)
			return;
		else {
			follow(grid, sprite, path.rest);
			if(grid.map.getLocation(path.getX(), path.getY()).solid)
				throw new RuntimeException("You ran into a wall!");
			int dx = path.getX() - sprite.getX();
			int dy = path.getY() - sprite.getY();
			if(Math.abs(dx + dy) > 1)
				throw new RuntimeException("Invalid path!");
			sprite.move(dx, dy);
			grid.repaint();
		}
	}
}
