package a6;


import static org.junit.Assert.fail;

import java.lang.reflect.Method;
import java.util.Observable;
import java.util.Observer;

import org.junit.Before;
import org.junit.Test;

public class A6Tester {
	public ChessPlayer p1, p2;
	public ChessGame g;
	public ChessBoard b;

	@Before
	public void runBeforeEachTest() {
		p1 = new ChessPlayer("P1");
		p2 = new ChessPlayer("P2");

		g = new ChessGame(p1, p2);
		b = g.getBoard();
	}

	
	public void templateFunc() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int failCount = 0;
		int x[] = {};
		int y[] = {};
		int isvalid[] = {};
		String pieces = "";
		
		moveSequence(x,y,isvalid);
		// Let's test for either of these 
		//failCount = checkUndo(x,y,isvalid,pieces,2);
		//failCount = checkGetMoves(x,y,isvalid,pieces,3,1,3);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testSimpleUndo(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece		
		int x[] = {0, 0};
		int y[] = {1, 3};
		String pieces = "p ";
		
		moveSequence(x,y);
		failCount = checkUndo(x,y,null,pieces,1,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testMultipleUndo(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {0, 0, 0, 0, 1, 1, 0, 3};
		int y[] = {1, 3, 0, 2, 1, 3, 2, 2};
		int isvalid[] = {1, 1,  1,  1};
		String pieces = "p r p r ";
		moveSequence(x,y,isvalid);
		failCount = checkUndo(x,y,isvalid,pieces,2,failCount);
		//checkUndo(x,y,valid,pieces,2);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testMultipleUndoWithCapture(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {0, 0, 0, 0, 1, 1, 0, 3, 3, 3, 3, 3, 3, 4};
		int y[] = {1, 3, 0, 2, 1, 3, 2, 2, 2, 6, 6, 7, 7, 7};
		int isvalid[] = {1, 1,  1,  1, 1, 1, 1};
		String pieces = "p r p r rPrQrK";
		moveSequence(x,y,isvalid);
		failCount = checkUndo(x,y,isvalid,pieces,5,failCount);
		//checkUndo(x,y,valid,pieces,2);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testMultipleUndoWithInvalidMove(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {0, 0, 0, 0, 1, 1, 0, 3, 3, 6};
		int y[] = {1, 3, 0, 2, 1, 3, 2, 2, 2, 5};
		int isvalid[] = {1, 1,  1,  1, 0};
		String pieces = "p r p r r ";
		moveSequence(x,y,isvalid);
		failCount = checkUndo(x,y,isvalid,pieces,3,failCount);
		//checkUndo(x,y,valid,pieces,2);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testMultipleUndoMixed(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {0, 0, 0, 0, 1, 1, 0, 3, 3, 3, 3, 3,  3, 6,   3, 4};
		int y[] = {1, 3, 0, 2, 1, 3, 2, 2, 2, 6, 6, 7,  7, 4,   7, 7};
		int isvalid[] = {1, 1,  1,  1, 1, 1, 0, 1};
		String pieces = "p r p r rPrQr rK";
		moveSequence(x,y,isvalid);
		failCount = checkUndo(x,y,isvalid,pieces,7,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testUndoMoveUndo(){
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {0, 0, 0, 0, 1, 1, 0, 3};
		int y[] = {1, 3, 0, 2, 1, 3, 2, 2};
		int isvalid[] = {1, 1,  1,  1};
		String pieces = "p r p r ";
		moveSequence(x,y,isvalid);
		failCount = checkUndo(x,y,isvalid,pieces,2,failCount);
		
		int x2[] = { 0, 3, 3, 3, 3, 3, 3, 3};
		int y2[] = { 2, 2, 2, 6, 6, 7, 7, 5};
		int isvalid2[] = { 1, 1, 1, 1};
		String pieces2 = "r rPrQr ";
		moveSequence(x2,y2,isvalid2);
		failCount += checkUndo(x2,y2,isvalid2,pieces2,4,failCount);
		
		failCount += checkUndo(new int[]{0, 0, 0, 0},new int[]{1, 3, 0, 2},null,"p r ",2,failCount);
		
		printFooter(funcName, failCount);
	}
	
	
	@Test
	public void testSimpleGetMoves() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4};
		int y[] = {1, 3}; 
		int isvalid[] = {1};
		String pieces = "p ";
		
		moveSequence(x,y,isvalid); 
		failCount = checkGetMoves(x,y,isvalid,pieces,1,0,2,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testPositiveGetMovesWithoutCapture() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2};
		int y[] = {1, 3, 0, 1, 1, 3}; 
		int isvalid[] = {1, 1, 1};
		String pieces = "p q q ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,3,0,6,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testPositiveGetMovesWithCapture() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 2, 2, 6, 5, 2, 3};
		int y[] = {1, 3, 0, 1, 1, 3, 3, 6, 0, 2, 6, 7}; 
		int isvalid[] = {1, 1, 1, 1, 1, 1};
		String pieces = "p q q qPn qQ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,6,0,12,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testZeroGetMoves() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 6, 5};
		int y[] = {1, 3, 0, 1, 1, 3, 0, 2}; 
		int isvalid[] = {1, 1, 1, 1};
		String pieces = "p q q n ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,0,0,8,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testHugeGetMoves() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 2, 2, 6, 5, 2, 3};
		int y[] = {1, 3, 0, 1, 1, 3, 3, 6, 0, 2, 6, 7}; 
		int isvalid[] = {1, 1, 1, 1, 1, 1};
		String pieces = "p q q qPn qQ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,1000,0,12,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testBigNegativeGetMoves() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 2, 2, 6, 5, 2, 3};
		int y[] = {1, 3, 0, 1, 1, 3, 3, 6, 0, 2, 6, 7}; 
		int isvalid[] = {1, 1, 1, 1, 1, 1};
		String pieces = "p q q qPn qQ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,-1000,0,12,failCount);
		
		printFooter(funcName, failCount);
	}
	

	@Test
	public void testNegativeGetMoves() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 2, 2, 6, 5, 2, 3};
		int y[] = {1, 3, 0, 1, 1, 3, 3, 6, 0, 2, 6, 7}; 
		int isvalid[] = {1, 1, 1, 1, 1, 1};
		String pieces = "p q q qPn qQ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,-2,8,12,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testNegativeGetMovesWithInvalidMove() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 6, 5, 5, 7};
		int y[] = {1, 3, 0, 1, 1, 3, 0, 2, 2, 7}; 
		int isvalid[] = {1, 1, 1, 1, 0};
		String pieces = "p q q n n ";
		
		moveSequence(x,y,isvalid);
		failCount = checkGetMoves(x,y,isvalid,pieces,-2,4,8,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testUndoAndGetMoves1() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 6, 5};
		int y[] = {1, 3, 0, 1, 1, 3, 0, 2}; 
		int isvalid[] = {1, 1, 1, 1};
		String pieces = "p q q n ";
		
		// make 4 moves, undo twice, test getmoves with n=2
		moveSequence(x,y,isvalid);  
		failCount = checkUndo(x,y,isvalid,pieces,2,failCount);
		failCount += checkGetMoves(x,y,isvalid,pieces,2,0,4,failCount);
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testUndoAndGetMoves2() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//below should be read in pairs move (x[1], y[1]) -> (x[2], y[2]) , index += 2
		//pieces[1] is the moved piece and pieces[2] is the captured piece
		int x[] = {4, 4, 3, 4, 4, 2, 6, 5};
		int y[] = {1, 3, 0, 1, 1, 3, 0, 2}; 
		int isvalid[] = {1, 1, 1, 1};
		String pieces = "p q q n ";
		
		// make 4 moves, undo 3 times, test getmoves with n=0
		moveSequence(x,y,isvalid);  
		failCount = checkUndo(x,y,isvalid,pieces,3,failCount);
		failCount += checkGetMoves(x,y,isvalid,pieces,2,0,2,failCount);
		
		// make 3 moves, undo twice, test getmoves with -2
		int x2[] = {5, 2, 3, 7, 7, 7};
		int y2[] = {0, 3, 0, 4, 4, 6}; 
		int isvalid2[] = {1, 1, 1};
		String pieces2 = "b q qP";
		
		moveSequence(x2,y2,isvalid2);  
		failCount += checkUndo(x2,y2,isvalid2,pieces2,2,failCount);
		failCount += checkGetMoves(new int[]{4,4,5,2},new int[]{1,3,0,3},new int[]{1,1},"p b ",-2,0,4,failCount);
		
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testToString() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		//Test if toString is implemented for all pieces
		try{
			Pawn.class.getMethod("toString");
			Knight.class.getMethod("toString");
			Bishop.class.getMethod("toString");
			Rook.class.getMethod("toString");
			Queen.class.getMethod("toString");
			King.class.getMethod("toString");
		}catch(Exception e){
			failCount = 1;		
		}	
			
		
		printFooter(funcName, failCount);
	}
	
	@Test
	public void testIsChessGameObservable() {
		String funcName = new Object() {}.getClass().getEnclosingMethod().getName();
		printHeader(funcName);
		int failCount = 0;
		
		ChessGameObserver game_observer = new ChessGameObserver();
		
		Method addObserverMethod = null;
		try{
			addObserverMethod = g.getClass().getMethod("addObserver", new Class[] { Observer.class });
			addObserverMethod.invoke(g,game_observer);
		} catch (Exception e){
			failCount ++;
			System.out.println("ChessGame is not Observable");
			System.out.println(e.getMessage());
		}
		
		
		g.addObserver(game_observer);
		
		ChessPiece p = b.getPieceAt(new ChessPosition(0,1));
		try{
			p.moveTo(new ChessPosition(0,2));
		} catch (IllegalMove e){
			failCount ++;
			System.out.println("IllegalMove exception while moving pawn from (0,1) -> (0,2)");
		}
		
		if (game_observer.isNotified() == false)
		{
			failCount++;
			System.out.println("Observer of ChessGame is not notified by ChessGame");
		}
			
		printFooter(funcName, failCount);
	}
	
	public class ChessGameObserver implements Observer{
		private boolean notified = false;
		public boolean isNotified(){
			return notified;
		}
		
		public void update(Observable o, Object arg) {
			notified = true;
		}
	}
	
	
	public int checkGetMoves(int[] x, int[] y, int[] isvalid, String pieces, int num, int arrayStart, int arrayEnd,int failCount){
		ChessMove[] moves = g.getMoves(num);
		String correctLog = "";
		
		int k = 0;
		for (int i=arrayStart; i < arrayEnd; i+=2, k++){
			
			if (isvalid != null && isvalid[i/2] ==0){
				k--;
				continue;
			}
				
			
			if ( k >= moves.length){
				System.out.println("Length of array returned from getMoves is invalid"); 
				failCount++;
				break;
			}
				
			ChessMove move = moves[k];
			String errorMsg = "";
			
			ChessPosition from = move.getFrom();
			ChessPosition to = move.getTo();
			
			if (from.getX() != x[i] && from.getY() != y[i])
				errorMsg = "Error in getMoves: From field is not correct";
			
			if (to.getX() != x[i+1] && to.getY() != y[i+1])
				errorMsg = "Error in getMoves: From field is not correct";
			
			if (move.getPiece().getMark() != pieces.charAt(i))
				errorMsg = "Error in getMoves: piece is not correct";
			
			if (move.getCaptured() != null && move.getCaptured().getMark()  != pieces.charAt(i+1))
				errorMsg = "Error in getMoves: captured field is not correct";
		
			if (errorMsg != ""){
				System.out.println(errorMsg);  //plus something
				failCount ++;
			}
			
			//correctLog += pieces.charAt(i) + " moved from " + from.toString() + " to " + to.toString();
			correctLog += (pieces.charAt(i) + " moved from (" + x[i] + "," + y[i] + ") to (" + x[i+1] + ","+ y[i+1] + ")");
			if (pieces.charAt(i+1) != ' ')
				correctLog += "capturing " + pieces.charAt(i+1) + "\n";
			else
				correctLog += "\n";
		}
		
		if ( k != moves.length){
			System.out.println("Length of array returned from getMoves is invalid"); 
			failCount++;
		}
		
		if (failCount > 0){
			System.out.println("================\nCorrect log\n================\n" + correctLog + "\n");
			System.out.println("================\nreturned from getMoves\n================");
			for (int j=0; j<moves.length; j++)
				System.out.println(moves[j]);
		}
		
		
		
		return failCount;
	}
	
	public void moveSequence(int[] x, int[] y){
		moveSequence(x,y,null);
	}
	
	public void moveSequence(int[] x, int[] y, int[] isvalid){
		moveSequence(x,y,isvalid,0,x.length);
	}
	
	//helper function	
	public void moveSequence(int[] x, int[] y,int[] isvalid,int arrayStart,int arrayEnd){
		for(int i = arrayStart; i < arrayEnd; i += 2){
			ChessPosition from = new ChessPosition(x[i], y[i]);
			ChessPosition to = new ChessPosition(x[i+1], y[i+1]);
			ChessPiece p = b.getPieceAt(from);

			try{
				if (p==null)
					System.out.println("No piece at (" + x[i] + "," + y[i] + ")");
				else
					p.moveTo(to);
			} catch (IllegalMove e) {
				//
			}
		}
	}
	

	
	//helper function
	//numUndo is number of "Undo"s excluding invalid moves in x,y move sequence
	public int checkUndo(int[] x, int[] y, int[] isvalid, String pieces, int numUndo,int failCount){
		int k = x.length-2;
		
		for(int i = 0; i < numUndo ; i++, k-=2){
			if (isvalid != null && isvalid[k/2] ==0 ) // skip, if not a valid move  
				continue;
				
			ChessPosition from = new ChessPosition(x[k], y[k]);
			ChessPosition to = new ChessPosition(x[k+1], y[k+1]);
			char captured = pieces.charAt(k+1);
			
			ChessPiece pfrom1 = b.getPieceAt(from); //piece at 'from' before undo
			ChessPiece pto1 = b.getPieceAt(to);		//piece at 'to' before undo
			
			g.undo();
			
			ChessPiece pfrom2 = b.getPieceAt(from); //piece at 'from' after undo
			ChessPiece pto2 = b.getPieceAt(to);		//piece at 'to' after undo
			
			int startX = from.getX();
			int startY = from.getY();
			int endX = to.getX();
			int endY = to.getY();
			String errorMsg = "";
	
			// Error if any of the following 
			// 1 -> "from" is not empty before undo
			// 2 -> "from" is empty after undo
			// 3 -> "from" piece after undo is not the "to" piece after undo
			if (pfrom1 != null || pfrom2 == null || pfrom2 != pto1)
				errorMsg = "Piece is not moved back correctly";
			
			// Error if this is capture move and one of the following is true:  
			// 1. nothing is put back 
			// 2. the piece that is put back is not correct
			if (  captured != ' ' &&   
					(pto2 == null  || pto2.getMark() != captured) )
				errorMsg = "Correct captured piece is not put back after undo";
 	
	
			if (errorMsg != ""){//player +
				System.out.println( "Error for Undo Move (" + startX + ", " + startY
						+ ") -> (" + endX + ", " + endY + ")\n" + errorMsg);
				failCount++;
			}
		}
		return failCount;
	}
	
	
	
	public void printHeader(String funcName) {
		System.out.println("====================================================");
		System.out.println(funcName + " debug log");
		System.out.println("====================================================\n");
	}

	public void printFooter(String funcName, int failCount) {
		if (failCount > 0) {
			System.out.println("\n++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
			fail(funcName + " failed, moves failed = " + failCount);
		} else {
			System.out.println(funcName + " passed");
		}
		// System.out.println("\n++++++++++++++++++++++++++++++++++++++++++++++++++++");
		System.out.println("\n++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
	}
}