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

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

public class PuzzleMove {

	//TODO: uses PuzzleMove dad = null and set this value while cloning
	// DAD:   id des vorgaengerknotens
	int dad;
	
	// DS:   distance from start
	private int ds;					
	/**
	 * @return the ds
	 */
	public int getDs() {
		return ds;
	}
	/**
	 * @param ds the ds to set
	 */
	public void setDs(int ds) {
		this.ds = ds;
	}
	
	// DD:   distance to destination
	private int dd;	
	/**
	 * getDd()
	 * @return the dd
	 */
	public int getDd() {
		return dd;
	}
	/**
	 * setDd()
	 * @param dd the dd to set
	 */
	public void setDd(int dd) {
		this.dd = dd;
	}
	
	// COST:   the distance cost (cost = ds + dd)
	private int cost; 	
	/**
	 * getCost()
	 * @return the cost
	 */
	public int getCost() {
		return cost;
	}
	/**
	 * setCost()
	 * @param cost the cost to set
	 */
	public void setCost(int cost) {
		this.cost = cost;
	}
	
	// HEURISTIC:   used to set the heuristic to be applied
	private int heuristic;	  		
		// 0 - methodOne()
		// 1 - methodTwo()
		// 2 - methodThree()
	/**
	 * getHeuristic()
	 * @return the heuristic
	 */
	public int getHeuristic() {
		return heuristic;
	}
	/**
	 * setHeuristic()
	 * @param heuristic the heuristic to set
	 */
	public void setHeuristic(int heuristic) {
		this.heuristic = heuristic;
		//TODO: check if valid value?
	}
	
	// STATE:   the state of the current puzzle move/state
	private String[][] state;
	/**
	 * setState()
	 * @param state the state to set
	 */
	public void setState(String[][] state) {
		this.state = state;
		setSize();
		setRauteChords();
	}
	/**
	 * getState()
	 * @return the state
	 */
	public String[][] getState() {
		return state;
	}
	
	// SIZE:   Die Größe des Arrays
	private int size;
	/**
	 * setSize()
	 * Liest die Größe des Arrays und setzt sie auf die Variable size
	 */
	public void setSize() {
		this.size = PuzzleInputOutput.getArraySize();
	}
	/**
	 * getSize()
	 * gibt die size zurueck
	 */
	public int getSize() {
		return size;
	}
	
	// RAUTECHORDS:   Die Koordinaten der freien Stelle (#)
	private int xChord;
	private int yChord;
	/**
	 * setRauteChords()
	 * Findet die Raute im Array und initialisiert die rauteChords
	 */
	public void setRauteChords() {
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				if (state[i][j].equals("#")) {
					xChord = j;
					yChord = i;
				}
			}
		}
	}
	/**
	 * getXChord()
	 * @return int
	 * Gibt die X-Koordinate der Raute zurueck
	 */
	public int getXChord() {
		return xChord;
	}
	/**
	 * getYChord()
	 * @return int
	 * Gibt die X-Koordinate der Raute zurueck
	 */
	public int getYChord() {
		return yChord;
	}

	// SUCCESSORLIST:   Liste mit den moeglichen 
	public List<PuzzleMove> successorlist;
	/**
	 * setSuccessorlist()
	 * @return List<PuzzleMove>
	 * Initialisiert die successorlist
	 */
	public void setSuccessorlist() {
		this.successorlist = successorMoves();
	}
	/**
	 * getSuccessorlist()
	 * gibt die successorlist zurueck
	 */
	public List<PuzzleMove> getSuccessorlist() {
		return successorlist;
	}

	/**
	 * switchLeft(), switchUp(), switchRight(), switchDown()
	 * repräsentieren jeweils den Spielzug, der...
	 * ... den linken Stein auf die freie Stelle schiebt, ...
	 * ... den oberen Stein auf die freue Stelle schiebt, ...
	 * ... den rechten Stein auf die freie Stelle schiebt, ...
	 * ... den unteren Stein auf die freie Stelle schiebt.
	 * 
	 * @return String[][] ist das Spielfeld nach dem Zug 
	 */
	//  1. LEFT: Diese Spielzugvariante schiebt den linken Stein auf die freie Stelle
	public String[][] moveLeft() {
		String[][] successor = new String[size][size];
			for (int zeile = 0; zeile < size; zeile++) {
				for (int spalte = 0; spalte < size; spalte++) {
					if (spalte == yChord && zeile == xChord -1) {
						System.arraycopy(state[spalte], zeile + 1, successor[spalte], zeile, 1);
					} else if (spalte == yChord && zeile == xChord) {
						System.arraycopy(state[spalte], zeile - 1, successor[spalte], zeile, 1);
					} else {
						System.arraycopy(state[spalte], zeile, successor[spalte], zeile, 1);
					}
				}
			}
		return successor;
	}
	
	// 2. UP: Diese Spielzugvariante schiebt den rechten Stein auf die freie Stelle
	public String[][] moveUp() {
		String[][] successor = new String[size][size];

		for (int zeile = 0; zeile < size; zeile++) {
			for (int spalte = 0; spalte < size; spalte++) {
				if (spalte == yChord -1 && zeile == xChord) {
					System.arraycopy(state[spalte +1], zeile, successor[spalte], zeile, 1);
				} else if (spalte == yChord && zeile == xChord) {
					System.arraycopy(state[spalte -1], zeile, successor[spalte], zeile, 1);
				} else {
					System.arraycopy(state[spalte], zeile, successor[spalte], zeile, 1);
				}
			}
		}
		return successor;
	}
		
	// 3. RIGHT: Diese Spielzugvariante schiebt den rechten Stein auf die freie Stelle
	public String[][] moveRight() {
		String[][] successor = new String[size][size];

		for (int zeile = 0; zeile < size; zeile++) {
			for (int spalte = 0; spalte < size; spalte++) {
				if (spalte == yChord && zeile == xChord) {
					System.arraycopy(state[spalte], zeile +1, successor[spalte], zeile, 1);
				} else if (spalte == yChord && zeile == xChord +1) {
					System.arraycopy(state[spalte], zeile -1, successor[spalte], zeile, 1);
				} else {
					System.arraycopy(state[spalte], zeile, successor[spalte], zeile, 1);
				}
			}
		}
		return successor;
	}

	// 4. DOWN: Diese Spielzugvariante schiebt den unteren Stein auf die freie Stelle
	public String[][] moveDown() {
		String[][] successor = new String[size][size];

		for (int zeile = 0; zeile < size; zeile++) {
			for (int spalte = 0; spalte < size; spalte++) {
				if (spalte == yChord && zeile == xChord) {
					System.arraycopy(state[spalte +1], zeile, successor[spalte], zeile, 1);
				} else if (spalte == yChord +1 && zeile == xChord) {
					System.arraycopy(state[spalte -1], zeile, successor[spalte], zeile, 1);
				} else {
					System.arraycopy(state[spalte], zeile, successor[spalte], zeile, 1);
				}
			}
		}
		return successor;
	}
	
	/**
	 * leftCheck(), upCheck(), rightCheck(), downCheck()
	 * @return boolean
	 * Ueperueft welche Zugvarianten moeglich sind
	 */	
	// leftCheck:   Ist der zug moveLeft() moeglich?
	public boolean leftCheck() {
		if (xChord -1 < 0){
			return false;
		}
		return true;
	}
	
	// upCheck:   Ist der zug moveUp() moeglich?
	public boolean upCheck() {
		if (yChord -1 < 0){
			return false;
		}
		return true;
	}

	// rightCheck:   Ist der zug moveRight() moeglich?
	public boolean rightCheck() {
		if (xChord +1 > size -1){
			return false;
		}
		return true;
	}
	
	// downCheck:   Ist der zug moveDown() moeglich?
	public boolean downCheck() {
		if (yChord +1 > size -1){
			return false;
		}
		return true;
	}
	
	
	/**
	 * Calculates the possible successor moves/states
	 * @return a list with the successor moves/states
	 */
	public List<PuzzleMove> successorMoves() {
		List<PuzzleMove> successorlist = new ArrayList<PuzzleMove>();
		
		if (leftCheck()) {
			PuzzleMove leftSuccessor = new PuzzleMove();
			leftSuccessor.setState(moveLeft());
			
			successorlist.add(leftSuccessor);
		}
		if (upCheck()) {
			PuzzleMove upSuccessor = new PuzzleMove();
			upSuccessor.setState(moveUp());
			
			successorlist.add(upSuccessor);
		}
		if (rightCheck()) {
			PuzzleMove rightSuccessor = new PuzzleMove();
			rightSuccessor.setState(moveRight());
			
			successorlist.add(rightSuccessor);
		}
		if (downCheck()) {
			PuzzleMove downSuccessor = new PuzzleMove();
			downSuccessor.setState(moveDown());
			
			successorlist.add(downSuccessor);
		}
		return successorlist;
	}
	
	/**
	 * calculateDistance()
	 * 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) {
		int result = 0;
		
		switch(getHeuristic()) {
		case(0): {
			result = methodOne(p);
			break;
			}
		case(1): {
			result = methodTwo(p);
			break;
		}
//		case (2): {
//			result = methodThree(p);
//		}
		}
		setDd(result);
		return result;
	}
	
	/**
	 * Heuristic 0
	 * 
	 * @param p the puzzle state
	 * @return  the destination cost according to this heuristic
	 */
	private int methodOne(PuzzleMove p) {
		int result = 0;
		String actual = new String();
		String toCompare = new String();
		
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				actual = this.getState()[i][j];
				
				for (int k = 0; k < size; k++) {
					for (int l = 0; l < size; l++) {
						toCompare = p.getState()[k][l];
						if (actual.equals(toCompare)) {
							result = result + Math.abs((i - k)) + Math.abs((j - l));
						}
					}
				}
			}
		}
		return result;
	}
	
	/**
	 * Heuristic 1
	 * 
	 * @param p the puzzle state
	 * @return  the destination cost according to this heuristic
	 */
	private int methodTwo(PuzzleMove p) {
		int result = 0;
		
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				String actual = this.getState()[i][j];
				String toCompare = p.getState()[i][j];
					if (!actual.equals(toCompare)) {
						result++;
					}
			}
		}
		return result;
	}
	
//	/**
//	 * Heuristic 2
//	 * 
//	 * @param p the puzzle state
//	 * @return  the destination cost according to this heuristic
//	 */
//	private int methodThree(PuzzleMove p) {
//		// TODO: Implement the method
//		return -1;
//	}
	
  	 /**
	 * equals()
	 * EQUALS:   Checks if two puzzle moves are equal
	 */
	@Override public boolean equals(Object obj) {
		
		if(!(obj instanceof PuzzleMove)){
			return false;
		}
		
		PuzzleMove p = (PuzzleMove)obj;
		
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				if (!this.getState()[i][j].equals(p.getState()[i][j])) {
					return false;
				}
			}
		}
		return true;
	}
	
	@Override
	public int hashCode() {
		int result = 0;
		
		for(int i=0; i<state.length;i++)
		{
			result += Arrays.hashCode(state[i]);
		}
		
		return result;
	}

//	public static void main(String[] args) throws IOException {
//		PuzzleInputOutput pio = new PuzzleInputOutput("test_02.txt");
//	
//		PuzzleMove start = new PuzzleMove();
//		PuzzleMove end = new PuzzleMove();
//		
//		start.setState(pio.getPuzzleStart());
//		start.setSuccessorlist();
//		end.setState(pio.getPuzzleDestination());
//		
//		System.out.println("Der Zielstand: ");
//		pio.printPuzzle(end.getState());
//		
//		System.out.println("Der aktuelle state: ");
//		pio.printPuzzle(start.getState());
//		
//		System.out.println("Die moeglichen Nachfolgezuege: ");		
//		for (int i = 0; i < start.successorlist.size(); i++) {
//			pio.printPuzzle(start.successorlist.get(i).getState());
//			System.out.println();
//		}
//		
//		System.out.println("Die Distanz zwischen state und destination nach methodOne()");
//		start.setHeuristic(0);
//		System.out.println(start.calculateDistance(end));
//		System.out.println();
//		
//		System.out.println("Die Distanz zwischen state und destination nach methodTwo()");
//		start.setHeuristic(1);
//		System.out.println(start.methodTwo(end));
//		System.out.println();
//	}
}
