import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This class defines a puzzle move
 */

public class PuzzleMove implements Cloneable
{
	//---STATICS-------------------------------------------
	
	//Heuristic Definitions
	public final static int HEURISTIC_A = 0;
	public final static int HEURISTIC_B = 1;
	public final static int HEURISTIC_C = 2;
	
	//---VARIABLES-----------------------------------------
	
	/**
	 * distance from start; default 0
	 */
	private int ds	= 0;
	
	/**
	 * distance to destination; default 0
	 */
	private int dd	= 0;					
	
	/**
	 * the distance cost (cost = ds + dd); default 0
	 */
	private int cost = 0; 				
	
	/**
	 * Used Heuristic; Default: HEURISTIC_A
	 * 
	 * Valid values: 0-2
	 */
	private int heuristic = HEURISTIC_A;
	
	/**
	 * the state of the current puzzle move/state
	 */
	private String[][] state;
	
	/**
	 * Predecessor of this Move
	 * 
	 * Is null if predecessor does not exist or is unknown
	 */
	private PuzzleMove predecessorMove = null;
	
	
	//---METHODS-------------------------------------------

	/**
	 * Standard Constructor
	 * 
	 * to make the PuzzleMove valid use
	 * setState();
	 * 
	 * optional:
	 * setHeuristic() - default HEURISTIC_A
	 * setDd() - default 0
	 * setDs() - default 0
	 */
	public PuzzleMove(){}
	
	/**
	 * Constructor 
	 * 
	 * @param state PuzzleState
	 * @param heuristic Used heuristic
	 */
	public PuzzleMove(String[][] state, int heuristic)
	{
		this.setState(state);
		this.setHeuristic(heuristic);
	}
	
	/**
	 * Constructor
	 * 
	 * @param state PuzzleState
	 * @param heuristic Used Heuristic
	 * @param dd Distance to Destination
	 * @param ds Distance to Start
	 */
	public PuzzleMove(String[][] state, int heuristic, int dd, int ds) {
		this.setState(state);
		this.setHeuristic(heuristic);
		this.setDd(dd);
		this.setDs(ds);
	}

	/**
	 * Find Element Position in given state which has given value
	 * 
	 * @param value String
	 * @param state String[][]
	 * @return null if not found else returns a Point
	 */
	private Point findElementPos(String value, String[][] state)
	{
		//Iterate over array
		for(int i=0; i<state.length;i++)
		{
			for(int j=0; j<state[i].length;j++)
			{
				//Check if state[i][j] is searchelement
				if(state[i][j].equals(value))
				{
					return new Point(i,j);
				}
			}
		}
		
		//String not found in array
		return null;
	}
	
	/**
	 * Find Element Position in this.state which has given value
	 * 
	 * @param value String
	 * @return null if not found else returns a Point
	 */
	private Point findElementPos(String value)
	{
		return findElementPos(value,state);
	}
	
	/**
	 * Exchanges the Fields of given state at point a and b
	 * @param state String[][]
	 * @param a Point
	 * @param b Point
	 * @return modified state
	 */
	private String[][] exchangeFields(String[][] state, Point a, Point b)
	{
		//save value of state at point a
		String temp = state[a.x][a.y];
		
		//set value of state at point a to value of point b 
		state[a.x][a.y] = state[b.x][b.y];
		
		//set value of state at point b to saved value of point a
		state[b.x][b.y] = temp;
		
		//return modified state
		return state;
	}
	
	/**
	 * Exchanges the Fields at point a and b of current State
	 * 
	 * @param a Point
	 * @param b Point
	 * @return modified State
	 */
	private String[][] exchangeFields(Point a, Point b)
	{
		return exchangeFields(this.state, a, b);
	}
	
	/**
	 * Calculates on Successor according to the given x/y_mods
	 *	
	 * @param free_field Free field in this.state arrays
	 * @param x_mod Integer defining the movement of the free_field on x-Achsis
	 * @param y_mod Integer defining the movement of the free_field on y-Achsis
	 * @return null if parameters where invalid or x/y-mod would cause ArrayOutOfBounds
	 * 		   or new PuzzleMove if successfull	 
	 */
	private PuzzleMove successorMove(Point free_field, int x_mod, int y_mod)
	{
		//Check if free_field is valid 
		if(	free_field	 == null 				||
			free_field.x < 0					||
			free_field.x >= this.state.length	||
			free_field.y < 0					||
			free_field.y >= this.state.length 	){
			return null;
		}
		
		//create a new Point according to modificators
		Point b = new Point(free_field.x + x_mod, free_field.y + y_mod);
		
		//check new point
		if( b.x < 0						||
			b.x >= this.state.length	||
			b.y < 0						||
			b.y	>= this.state.length 	)
		{
			return null;
		}
		
		//clone this PuzzleMove
		PuzzleMove result = this.clone();
		
		//Exchange the free_field and the Modification-Point
		result.exchangeFields(free_field, b);
		
		//Increase Ds
		result.setDs(this.getDs()+1);
		
		//Set Predecessor
		result.setPredecessorMove(this);
		
		//Return new PuzzleMove
		return result;
	}
	
	/**
	 * Calculates the possible successor moves/states
	 * @return a list with the successor moves/states
	 */
	public List<PuzzleMove> successorMoves() {
		
		//Create Result List
		List<PuzzleMove> result = new ArrayList<PuzzleMove>();
		
		//Find the free Field in state
		Point free_field = findElementPos(PuzzleInputOutput.FORMAT_FREEFIELD);
		
		//Temporary Variable for new Moves
		PuzzleMove newMove;
		
		//top-Move
		newMove = successorMove(free_field,-1,0);
		if(newMove != null)
		{
			result.add(newMove);
		}
		
		//bottom-Move
		newMove = successorMove(free_field,+1,0);
		if(newMove != null)
		{
			result.add(newMove);
		}
		
		//left-Move
		newMove = successorMove(free_field,0,-1);
		if(newMove != null)
		{
			result.add(newMove);
		}
		
		//right-Move
		newMove = successorMove(free_field,0,+1);
		if(newMove != null)
		{
			result.add(newMove);
		}
		
		return result;
	}
	
	/**
	 * Calculates the distance to the destination p (according to the currently chosen heuristic)
	 * @param p the destination state
	 * @return the distance of the current state to destination
	 */
	public int calculateDistance(PuzzleMove p) {
		
		//Simple switch and link to methodOne-Three
		switch(this.heuristic)
		{
			case HEURISTIC_A:
				return methodOne(p);
				
			case HEURISTIC_B:
				return methodTwo(p);
				
			case HEURISTIC_C:
				return methodThree(p);
		
			default:
				return -1;
		}
	}
	
	/**
	 * Heuristic 0
	 * 
	 * How many Elements are not in the right place
	 * 
	 * @param p the puzzle state
	 * @return  the destination cost according to this heuristic
	 */
	private int methodOne(PuzzleMove p) {
		
		//Check if p is valid
		if(	p == null){
			return -1;
		}
		
		int dist = 0;
		
		//Iterate over state 
		for(int i=0; i<this.state.length; i++)
		{			
			for(int j=0; j<this.state[i].length; j++)
			{
				//Check if Element is in right place, if not increase dist-counter
				if(!this.state[i][j].equals(p.getState()[i][j]))
				{
					dist++;
				}
			}
		}
		
		//Set new calculated dd value
		setDd(dist);
		
		return dist;
	}
	
	/**
	 * Heuristic 1
	 * 
	 * Calculate the distance between Elements in state
	 * and its target-destination and summ it up
	 * 
	 * @param p the puzzle state
	 * @return  the destination cost according to this heuristic
	 */
	private int methodTwo(PuzzleMove p) {
		
		//Check if p is valid
		if(	p == null){
			return -1;
		}
		
		int dist = 0;
		
		//Iterate over state
		for(int i=0; i<this.state.length; i++)
		{			
			for(int j=0; j<this.state[i].length; j++)
			{
				//Find Element of state in destination-state (O(n^2))
				Point point = findElementPos(this.state[i][j],p.getState());
				
				//Element not found
				if(point == null){
					return -1;
				}
				
				//Calculate the distance between Element and its destination
				dist += Math.abs(point.x-i) + Math.abs(point.y-j); 		
			}
		}
		
		//Set new calculated dd value
		setDd(dist);
		
		return dist;
	}
	
	/**
	 * Heuristic 2
	 * 
	 * @param p the puzzle state
	 * @return  the destination cost according to this heuristic
	 */
	private int methodThree(PuzzleMove p) {
		
		//Check if p is valid
		if(	p == null){
			return -1;
		}
		
		int dist = 0;
		
		//Iterate over state
		for(int i=0; i<this.state.length; i++)
		{			
			for(int j=0; j<this.state[i].length; j++)
			{
				//Find Element of state in destination-state (O(n^2))
				Point point = findElementPos(this.state[i][j],p.getState());
				
				//Element not found
				if(point == null){
					return -1;
				}
				
				//Calculate the distance between Element and its destination (diagonally using sqrt)
				dist += Math.sqrt(Math.pow(Math.abs(point.x-i),2) + Math.pow(Math.abs(point.y-j),2)); 		
			}
		}
		
		//Set new calculated dd value
		setDd(dist);
		
		return dist;
	}
	
	/**
	 * HashCode to make equals work.
	 * 
	 * Only uses state to calculate hashcode
	 */
	@Override public int hashCode() {
		int hashcode = 0;
		
		for(int i= 0; i < state.length; i++){
			hashcode += Arrays.hashCode(state[i]);
		}
		
		return hashcode;
	}

	/**
	 * Checks if two puzzle moves are equal
	 * @param Object to be compared
	 * 
	 * Checks only state -> does not compare
	 * -heuristic
	 * -dd
	 * -ds
	 */
	@Override public boolean equals(Object obj)
	{
		//Checks if obj is a PuzzleMove
		if(!(obj instanceof PuzzleMove)){
			return false; //return false - its not a puzzlemove
		}
		
		//cast to Puzzlemove
		PuzzleMove p = (PuzzleMove)obj;
		
		//Check state-length
		if(	p.getState().length != this.state.length){
			return false;
		}
		
		//iterate over array
		for(int i=0; i<this.state.length;i++)
		{
			//Check subarray-length of state
			if(p.getState()[i].length != this.state[i].length){
				return false;
			}
			
			for(int j=0; j<this.state[i].length;j++)
			{
				//Check if state elements are equal
				if(!p.getState()[i][j].equals(this.state[i][j])){
					return false;
				}
			}
		}
		
		//All Checks done return true
		return true;
	}
	
	/**
	 * @return Returns a String-Representation of the PuzzleMove
	 */
	@Override public String toString()
	{
		String result = "Step: " + ds +
						": cost = " + cost +
						", ds = " + ds +
						", dd = " + ds +
						"\n--------------------------------\n";
		
		for(int i=0;i<state.length; i++){
			for(int j=0;j<state[i].length; j++){
				result += state[i][j] + "\t";
			}
			result += "\n"; 
		}
		
		return result;
	}
	
	
	/**
	 * Clone this PuzleMove
	 * 
	 * clones
	 * -state
	 * -heuristic
	 * -dd
	 * -ds
	 */	
	@Override public PuzzleMove clone()
	{
		//new array
		String[][] aarrayclone = new String[this.state.length][this.state.length];
		
		//clone every subarray
		for(int i=0; i<aarrayclone.length;i++)
		{
			System.arraycopy(this.state[i],0,aarrayclone[i],0,aarrayclone.length);
		}
		
		//create a new puzzle, set array, heuristic, dd, ds
		PuzzleMove aclone = new PuzzleMove(aarrayclone,getHeuristic(),dd,ds);
		
		//return the cloned element
		return aclone;
	}
	
	/**
	 * @param state the state to set
	 */
	public void setState(String[][] state) {
		this.state = state;
	}
	
	
	/**
	 * @return the state
	 */
	public String[][] getState() {
		return state;
	}
	

	/**
	 * Sets the Heuristic used
	 * 
	 * @param heuristic HEURISTIC_A/B/C
	 * @return true if heuristic value was valid
	 */
	public boolean setHeuristic(int heuristic) {
		if(	heuristic >= HEURISTIC_A &&
			heuristic <= HEURISTIC_C)
		{
			this.heuristic = heuristic;
			return true;
		}
		
		return false;
	}

	/**
	 * Returns the Heuristic used
	 * 
	 * @return integer between 0-2
	 */
	public int getHeuristic() {
		return heuristic;
	}
	

	/**
	 * Returns the calculated Costs for this Move
	 * 
	 * @return calculated Costs
	 */
	public int getCost() {
		return cost;
	}
	
	/**
	 * Calculate new Costs, save them to cost
	 * @return new costs
	 */
	private int calculateCosts()
	{
		return (cost = ds + dd);
	}
	
	/**
	 * @return Distance to Start
	 */
	public int getDs() {
		return ds;
	}

	/**
	 * Sets Distance to Start and Calculates new Costs
	 * 
	 * @param ds
	 */
	public void setDs(int ds) {
		this.ds = ds;
		calculateCosts();
	}

	/**
	 * @return Distance to Destination
	 */
	public int getDd() {
		return dd;
	}
	
	/**
	 * Sets Distance to Destination and Calculates new Costs
	 * 
	 * @param dd
	 */
	public void setDd(int dd) {
		this.dd = dd;
		calculateCosts();
	}

	/**
	 * Sets PredecessorMove 
	 * @param predecessorMove PuzzleMove
	 */
	public void setPredecessorMove(PuzzleMove predecessorMove) {
		this.predecessorMove = predecessorMove;
	}

	/**
	 * @return PredecessorMove; can be null
	 */
	public PuzzleMove getPredecessorMove() {
		return predecessorMove;
	}
}
