import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.SimpleDirectedWeightedGraph;

/**
 * This class represents the implementation of the A* algorithm 
 */

public abstract class PuzzleAStar {

	private DirectedGraph<PuzzleMove, Integer> graph; 	// vertex: PuzzleMove 
														//   edge: Integer (use an int value as a unique ID)
	private PuzzleMove startState;						// the start state of the puzzle
	private PuzzleMove destinationState;				// the destination state of the puzzle
	private PuzzleMove currentState;					// the current explored puzzle state	
	private List<PuzzleMove> movesToProcess;			// the number of moves not taken yet
	
	private long startPuzzleTime;   					// Timing variables
	private long endPuzzleTime;
	
	/**
	 * Initializes PuzzleAStar
	 */
	void init() {
		
		//Create ArrayList for moves
		movesToProcess = new ArrayList<PuzzleMove>();
		
		//Create graph
		graph = new SimpleDirectedWeightedGraph<PuzzleMove, Integer>(Integer.class);
		
		//Set currentState to startState
		this.currentState = startState;
		
		//Add StartState to graph
		graph.addVertex(startState);
	}
	
	/**
	 * This method implements the A* algorithm for the puzzle
	 */
	public void findDestination() {
		
		//check input data
		if(currentState == null){
			return;
		}
		
		//save the start time
		startPuzzleTime = System.currentTimeMillis();
		
		//integer identification for edges
		int edgecount = 0;
		
		//Add all possible Moves of startState to movesToProcess
		movesToProcess.addAll(currentState.successorMoves());
		
		//Do as long destination is not reached
		while(!currentState.equals(destinationState))
		{	
			PuzzleMove nextstep = null;
			
			for(int i=0; i< movesToProcess.size(); i++)
			{
				//calculate distance of Move - need to be done here cuz PuzzleMove has no
				//access to destinationState
				movesToProcess.get(i).calculateDistance(this.destinationState);
				
				//identify best move in movesToProcess
				if(nextstep == null)
				{
					//no move found till now
					nextstep = movesToProcess.get(i);
				} else
				{
					//is move better then last identified?
					if(nextstep.getCost() > movesToProcess.get(i).getCost())
					{
						nextstep = movesToProcess.get(i);
					}
				}
			}
			
			//movesToProcess is empty. There is no connection from start to destination
			if(nextstep == null)
			{
				//no way found
				break;
			}
			
			//try to add identified step. If not possible it is already in the graph
			if(graph.addVertex(nextstep))
			{
				//Add an edge from identified step's predecessor to identified step
				graph.addEdge(nextstep.getPredecessorMove(), nextstep,edgecount++);
				
				//now continue with identified step
				currentState = nextstep;
		
				//add all successorMoves to movesToProcess
				movesToProcess.addAll(currentState.successorMoves());	
			}
			
			//remove the identified step from movesToProcess
			movesToProcess.remove(nextstep);
			
		}
		
		//save end time
		endPuzzleTime = System.currentTimeMillis();
	}

	/**
	 * This method prints a summary of the solution
	 * 
	 * @param state The final puzzle state (Destination)
	 */
	public void printSolutionSummary(PuzzleMove state) {
		System.out.println("Time needed to find Destination (ms): " + (this.endPuzzleTime-this.startPuzzleTime));
		System.out.println("Current cost at final state (Destination): " + state.getCost());
		System.out.println("States processed so far: " + numberOfProcessedStates());
		System.out.println("States identified for processing (not being processed): " + numberOfStatesToProcess());
	  } 
	
	/**
	 * This method prints the path to the solution
	 * 
	 * @param graph
	 */
	public void printPathToSolution(DirectedGraph<PuzzleMove, Integer> graph) {
		System.out.println("\nThe steps/path needed to reach the destination:\n");
		
		//Check if A* was successful
		if(!currentState.equals(destinationState)){
			System.out.println("No Path to Solution found");
			return;
		}

		//Add String representation of destinationState
		String result = currentState.toString();
		
		PuzzleMove curMove = destinationState;
		
		//Move from destination to start
		while(!curMove.equals(startState))
		{	
			//get incoming edges
			Set<Integer> s = graph.incomingEdgesOf(curMove);
			
			//No or more then one edge found - something is wrong
			if(s.size() != 1)
			{
				System.out.println("An Error occured: Graph destination has no or to many connections to start");
				return;
			}
		
			//only look at first edge
			Iterator<Integer> it = s.iterator();
			curMove = graph.getEdgeSource(it.next());
			
			//Add String representation before last steps
			result = curMove.toString() + result;
			
		}
		
		//print the result
		System.out.println(result);
	  }
	
	/**
	 * The number of states identified for processing but not processed yet
	 * @return the number of moves not taken yet
	 */
	int numberOfStatesToProcess() {
		return this.movesToProcess.size();
	}	
	
	/**
	 * The number of states/puzzle moves processed so far
	 * @return the number of edges
	 */
	int numberOfProcessedStates() {
		return this.graph.vertexSet().size();
	}
	
	/**
	 * @return the graph
	 */
	public DirectedGraph<PuzzleMove, Integer> getGraph() {
		return this.graph;
	}

	/**
	 * @return the currentState
	 */
	public PuzzleMove getCurrentState() {
		return this.currentState;
	}

	/**
	 * @param destinationState the destinationState to set
	 */
	public void setDestinationState(PuzzleMove destinationState) {
		this.destinationState = destinationState;
	}

	/**
	 * @param startState the startState to set
	 */
	public void setStartState(PuzzleMove startState) {
		this.startState = startState;
	}

	/**
	 * @return the startState
	 */
	public PuzzleMove getStartState() {
		return this.startState;
	}

	/**
	 * @return the destinationState
	 */
	public PuzzleMove getDestinationState() {
		return this.destinationState;
	}
	/**
	 * @return the movesToProcess
	 */
	public List<PuzzleMove> getMovesToProcess() {
		return this.movesToProcess;
	}
}
