package chess4j.moves;

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

import chess4j.Color;
import chess4j.board.Board;
import chess4j.board.CastlingRights;
import chess4j.board.File;
import chess4j.board.KingSquares;
import chess4j.board.KnightSquares;
import chess4j.board.Rank;
import chess4j.board.Square;
import chess4j.board.directions.Direction;
import chess4j.board.directions.North;
import chess4j.board.directions.NorthEast;
import chess4j.board.directions.NorthWest;
import chess4j.board.directions.South;
import chess4j.board.directions.SouthEast;
import chess4j.board.directions.SouthWest;
import chess4j.pieces.Bishop;
import chess4j.pieces.Knight;
import chess4j.pieces.Pawn;
import chess4j.pieces.Piece;
import chess4j.pieces.Queen;
import chess4j.pieces.Rook;
import chess4j.pieces.SlidingPiece;


public class MoveGenImpl implements MoveGen {

	private void addMoves(List<Move> moves,Board board,Square fromSq,List<Square> toSquares) {
		assert(fromSq != null);
		Piece fromPiece = board.getPiece(fromSq);
		assert(fromPiece != null);
		Color oppColor = Color.swap(fromPiece.getColor()); 
		for (Square toSq : toSquares) {
			Piece toPiece = board.getPiece(toSq);
			if (toPiece==null || toPiece.getColor().equals(oppColor))
				moves.add(new Move(fromSq,toSq,toPiece));
		}
	}

	private void genBishopMoves(Board board,List<Move> moves) {
		SlidingPiece p = board.getPlayerToMove().isWhite()?Bishop.WHITE_BISHOP:Bishop.BLACK_BISHOP; 
		genSliderMoves(p,board,moves);
	}
	
	private void genBlackPawnAttacks(Direction dir,Board board,Square fromSq,List<Move> moves) {
		Square toSq = dir.next(fromSq);
		if (toSq != null) {
			Piece p = board.getPiece(toSq);
			if (p != null && p.getColor().isWhite()) {
				if (toSq.rank().equals(Rank.RANK_1)) {
					moves.add(new Move(fromSq,toSq,p,Queen.BLACK_QUEEN));
					moves.add(new Move(fromSq,toSq,p,Rook.BLACK_ROOK));
					moves.add(new Move(fromSq,toSq,p,Bishop.BLACK_BISHOP));
					moves.add(new Move(fromSq,toSq,p,Knight.BLACK_KNIGHT));
				} else {
					moves.add(new Move(fromSq,toSq,p));
				}
			} else if (toSq.equals(board.getEPSquare())) {
				Square s = Square.valueOf(board.getEPSquare().file(),board.getEPSquare().rank().north());
				moves.add(new Move(fromSq,toSq,board.getPiece(s)));
			}
		}
	}

	private void genBlackPawnPushes(Board board,Square fromSq,List<Move> moves) {
		Square toSq = South.getInstance().next(fromSq);
		// pawn push
		if (board.isEmpty(toSq)) {
			if (toSq.rank().equals(Rank.RANK_1)) {
				moves.add(new Move(fromSq,toSq,null,Queen.BLACK_QUEEN));
				moves.add(new Move(fromSq,toSq,null,Rook.BLACK_ROOK));
				moves.add(new Move(fromSq,toSq,null,Bishop.BLACK_BISHOP));
				moves.add(new Move(fromSq,toSq,null,Knight.BLACK_KNIGHT));
			} else {
				moves.add(new Move(fromSq,toSq,null));
				// double push
				if (fromSq.rank().equals(Rank.RANK_7)) {
					toSq = South.getInstance().next(toSq);
					if (board.isEmpty(toSq))
						moves.add(new Move(fromSq,toSq,null));
				}
			}
		}		
	}
	
	private void genCastlingMoves(Board board,List<Move> moves) {
		Color player = board.getPlayerToMove();
		Square kingSq = board.getKingSquare(player);

		if (player.isWhite()) {
			if (board.canCastle(CastlingRights.WHITE_KINGSIDE)) {
				moves.add(new Move(kingSq,Square.valueOf(File.FILE_G, Rank.RANK_1),null));
			}
			if (board.canCastle(CastlingRights.WHITE_QUEENSIDE)) {
				moves.add(new Move(kingSq,Square.valueOf(File.FILE_C,Rank.RANK_1),null));
			}
		} else {
			if (board.canCastle(CastlingRights.BLACK_KINGSIDE)) {
				moves.add(new Move(kingSq,Square.valueOf(File.FILE_G, Rank.RANK_8),null));
			}
			if (board.canCastle(CastlingRights.BLACK_QUEENSIDE)) {
				moves.add(new Move(kingSq,Square.valueOf(File.FILE_C,Rank.RANK_8),null));
			}
		}
	}
	
	private void genKingMoves(Board board,List<Move> moves) {
		Square kingSq = board.getKingSquare(board.getPlayerToMove()); 
		assert(kingSq != null);
		addMoves(moves,board,kingSq,KingSquares.getSquares(kingSq));
		genCastlingMoves(board,moves);
	}
	
	private void genKnightMoves(Board board,List<Move> moves) {
		List<Square> knights = board.getSquares(
			board.getPlayerToMove().isWhite()?Knight.WHITE_KNIGHT:Knight.BLACK_KNIGHT);
		for (Square knightSq : knights) {
			addMoves(moves,board,knightSq,KnightSquares.getSquares(knightSq));
		}
	}

	public List<Move> genLegalMoves(Board board) {
		List<Move> moves = genPseudoLegalMoves(board);
		Iterator<Move> moveIter = moves.iterator();
		while (moveIter.hasNext()) {
			Move m = moveIter.next();
			board.applyMove(m);
			if (board.isOpponentInCheck()) {
				moveIter.remove();
			}
			board.undoLastMove();
		}
		return moves;
	}

	private void genPawnMoves(Board board,List<Move> moves) {
		if (board.getPlayerToMove().isWhite()) {
			List<Square> pawns = board.getSquares(Pawn.WHITE_PAWN);
			for (Square pawnSq : pawns) {
				genWhitePawnPushes(board,pawnSq,moves);
				genWhitePawnAttacks(NorthWest.getInstance(),board,pawnSq,moves);
				genWhitePawnAttacks(NorthEast.getInstance(),board,pawnSq,moves);
			}
		} else {
			List<Square> pawns = board.getSquares(Pawn.BLACK_PAWN);
			for (Square pawnSq : pawns) {
				genBlackPawnPushes(board,pawnSq,moves);
				genBlackPawnAttacks(SouthWest.getInstance(),board,pawnSq,moves);
				genBlackPawnAttacks(SouthEast.getInstance(),board,pawnSq,moves);
			}
		}
	}

	public List<Move> genPseudoLegalMoves(Board board) {
		List<Move> moves = new ArrayList<Move>();
		genPawnMoves(board,moves);
		genKnightMoves(board,moves);
		genBishopMoves(board,moves);
		genRookMoves(board,moves);
		genQueenMoves(board,moves);
		genKingMoves(board,moves);
		return moves;
	}
	
	private void genQueenMoves(Board board,List<Move> moves) {
		SlidingPiece p = board.getPlayerToMove().isWhite()?Queen.WHITE_QUEEN:Queen.BLACK_QUEEN; 
		genSliderMoves(p,board,moves);
	}

	private void genRookMoves(Board board,List<Move> moves) {
		SlidingPiece p = board.getPlayerToMove().isWhite()?Rook.WHITE_ROOK:Rook.BLACK_ROOK; 
		genSliderMoves(p,board,moves);
	}

	private void genSliderMoves(Direction direction,Board board,Square fromSq,List<Move> moves) {
		Color player = board.getPiece(fromSq).getColor();
		Color opponent = Color.swap(player);
		
		Square toSq = direction.next(fromSq);
		while (toSq != null) {
			Piece p = board.getPiece(toSq);
			if (p==null || p.getColor().equals(opponent)) 
				moves.add(new Move(fromSq,toSq,p));
			if (p!=null)
				break;
			toSq = direction.next(toSq);
		}
	}

	private void genSliderMoves(SlidingPiece piece,Board board,List<Move> moves) {
		List<Square> fromSqs = board.getSquares(piece);
		List<Direction> dirs = piece.getDirections();
		for (Square fromSq : fromSqs) {
			for (Direction dir : dirs) {
				genSliderMoves(dir,board,fromSq,moves);
			}
		}
	}

	private void genWhitePawnAttacks(Direction dir,Board board,Square fromSq,List<Move> moves) {
		Square toSq = dir.next(fromSq);
		if (toSq != null) {
			Piece p = board.getPiece(toSq);
			if (p != null && p.getColor().isBlack()) {
				if (toSq.rank().equals(Rank.RANK_8)) {
					moves.add(new Move(fromSq,toSq,p,Queen.WHITE_QUEEN));
					moves.add(new Move(fromSq,toSq,p,Rook.WHITE_ROOK));
					moves.add(new Move(fromSq,toSq,p,Bishop.WHITE_BISHOP));
					moves.add(new Move(fromSq,toSq,p,Knight.WHITE_KNIGHT));
				} else {
					moves.add(new Move(fromSq,toSq,p));
				}
			} else if (toSq.equals(board.getEPSquare())) {
				Square s = Square.valueOf(board.getEPSquare().file(),board.getEPSquare().rank().south());
				moves.add(new Move(fromSq,toSq,board.getPiece(s)));
			}
		}
	}
	
	private void genWhitePawnPushes(Board board,Square fromSq,List<Move> moves) {
		Square toSq = North.getInstance().next(fromSq);
		if (board.isEmpty(toSq)) {
			if (toSq.rank().equals(Rank.RANK_8)) {
				moves.add(new Move(fromSq,toSq,null,Queen.WHITE_QUEEN));
				moves.add(new Move(fromSq,toSq,null,Rook.WHITE_ROOK));
				moves.add(new Move(fromSq,toSq,null,Bishop.WHITE_BISHOP));
				moves.add(new Move(fromSq,toSq,null,Knight.WHITE_KNIGHT));
			} else {
				moves.add(new Move(fromSq,toSq,null));
				// double push
				if (fromSq.rank().equals(Rank.RANK_2)) {
					toSq = North.getInstance().next(toSq);
					if (board.isEmpty(toSq))
						moves.add(new Move(fromSq,toSq,null));
				}
			}
		}
	}
	
}
