package chess4j.io;

import java.text.ParseException;
import java.util.List;

import chess4j.App;
import chess4j.Color;
import chess4j.board.Board;
import chess4j.board.File;
import chess4j.board.Rank;
import chess4j.board.Square;
import chess4j.exceptions.IllegalMoveException;
import chess4j.moves.Move;
import chess4j.pieces.Bishop;
import chess4j.pieces.King;
import chess4j.pieces.Knight;
import chess4j.pieces.Pawn;
import chess4j.pieces.Piece;
import chess4j.pieces.Queen;
import chess4j.pieces.Rook;

public class MoveParser {

	private MoveParser() {}
	
	public static Move parseLegalMove(String strMove,Board board) throws ParseException, IllegalMoveException {
		Move move = parsePseudoMove(strMove,board);
		if (!isLegalMove(move,board)) {
			throw new IllegalMoveException(move);
		}
		return move;
	}	

	private static Move parsePseudoMove(String strMove,Board board) throws ParseException {
		Piece piece=null,promotion=null;
		File srcFile=null;
		Rank srcRank=null;
		Square dst=null;
		boolean wtm = board.getPlayerToMove().equals(Color.WHITE);
		strMove = strMove.trim();
		
		// get rid of any #, +, or = characters.
		strMove=strMove.replace("#","");
		strMove=strMove.replace("+","");
		strMove=strMove.replace("=","");

		// castling move?
		if (strMove.equalsIgnoreCase("O-O") || strMove.equals("0-0")) {
			if (board.getPlayerToMove().equals(Color.WHITE)) {
				return new Move(Square.valueOf(File.FILE_E, Rank.RANK_1),Square.valueOf(File.FILE_G, Rank.RANK_1));
			} else {
				return new Move(Square.valueOf(File.FILE_E, Rank.RANK_8),Square.valueOf(File.FILE_G, Rank.RANK_8));
			}
		} else if (strMove.equalsIgnoreCase("O-O-O") || strMove.equals("0-0-0")) {
			if (board.getPlayerToMove().equals(Color.WHITE)) {
				return new Move(Square.valueOf(File.FILE_E, Rank.RANK_1),Square.valueOf(File.FILE_C, Rank.RANK_1));
			} else {
				return new Move(Square.valueOf(File.FILE_E, Rank.RANK_8),Square.valueOf(File.FILE_C, Rank.RANK_8));
			}
		}
		
		// remove promotion piece if present
		char p=strMove.charAt(strMove.length()-1);
		if (p=='r' || p=='R') {
			promotion = wtm?Rook.WHITE_ROOK:Rook.BLACK_ROOK;
		} else if (p=='b' || p=='B') {
			promotion = wtm?Bishop.WHITE_BISHOP:Bishop.BLACK_BISHOP;
		} else if (p=='n' || p=='N') {
			promotion = wtm?Knight.WHITE_KNIGHT:Knight.BLACK_KNIGHT;
		} else if (p=='q' || p=='Q') {
			promotion = wtm?Queen.WHITE_QUEEN:Queen.BLACK_QUEEN;
		}
		if (promotion != null) {
			strMove = strMove.substring(0, strMove.length()-1);
		}
		// translate the destination
		if (strMove.length()<2) {
			throw new ParseException("couldn't translate destination (too short).",0);
		}
		File dstFile = File.file(strMove.substring(strMove.length()-2,strMove.length()-1));
		Rank dstRank = Rank.rank(strMove.substring(strMove.length()-1,strMove.length()));
		dst = Square.valueOf(dstFile, dstRank);
		strMove = strMove.substring(0,strMove.length()-2);

		// try pawn moves like "e4"
		if (strMove.length()==0) {
			if (wtm) {
				if (dstRank.equals(Rank.RANK_8) && promotion==null) {
					throw new ParseException("white pawn promotion with no promotion piece.",0);
				}
				Square sq = Square.valueOf(dstFile, dstRank.south());
				if (Pawn.WHITE_PAWN.equals(board.getPiece(sq))) {
					return new Move(sq,dst,null,promotion);
				}
				sq = Square.valueOf(dstFile, dstRank.south().south());
				if (Pawn.WHITE_PAWN.equals(board.getPiece(sq))) {
					return new Move(sq,dst);
				}					
			} else {
				if (dstRank.equals(Rank.RANK_1) && promotion==null) {
					throw new ParseException("black pawn promotion with no promotion piece.",0);
				}
				Square sq = Square.valueOf(dstFile, dstRank.north());
				if (Pawn.BLACK_PAWN.equals(board.getPiece(sq))) {
					return new Move(sq,dst,null,promotion);
				}
				sq = Square.valueOf(dstFile, dstRank.north().north());
				if (Pawn.BLACK_PAWN.equals(board.getPiece(sq))) {
					return new Move(sq,dst);
				}					
			}
			throw new ParseException("length==0 and no source is set.",0);
		}
		
		// set the piece
		p=strMove.charAt(0);
		if (p=='R') {
			piece = wtm?Rook.WHITE_ROOK:Rook.BLACK_ROOK;
		} else if (p=='B') {
			piece = wtm?Bishop.WHITE_BISHOP:Bishop.BLACK_BISHOP;
		} else if (p=='N') {
			piece = wtm?Knight.WHITE_KNIGHT:Knight.BLACK_KNIGHT;
		} else if (p=='Q') {
			piece = wtm?Queen.WHITE_QUEEN:Queen.BLACK_QUEEN;
		} else if (p=='K') {
			piece = wtm?King.WHITE_KING:King.BLACK_KING;
		} else if (p=='P') {
			piece = wtm?Pawn.WHITE_PAWN:Pawn.BLACK_PAWN;
		}
		if (piece != null) {
			strMove = strMove.substring(1);
		}
		
		// remove the capture designator
		strMove = strMove.replace("x", "");
		
		// if anything is left, it must be the source
		if (strMove.length()>0) {
			p = strMove.charAt(0);
			if (p >= 'a' && p <= 'h') {
				srcFile = File.file(String.valueOf(p));
				strMove = strMove.substring(1);
			}
		}
		if (strMove.length()>0) {
			p = strMove.charAt(0);
			if (p >= '1' && p <= '8') {
				srcRank = Rank.rank(String.valueOf(p));
				strMove = strMove.substring(0, strMove.length()-1);
			}
		}
		
		// if the piece didn't get set earlier, do it now.
		if (piece == null) {
			if (srcFile != null && srcRank != null) {
				Square srcSq = Square.valueOf(srcFile, srcRank);
				piece = board.getPiece(srcSq);
			} else {
				piece = wtm?Pawn.WHITE_PAWN:Pawn.BLACK_PAWN;
			}
		}
		return getMatchingMove(srcFile,srcRank,dst,piece,promotion,board);
	}
	
	private static Move getMatchingMove(File srcFile,Rank srcRank,Square dstSquare,Piece piece,Piece promo,Board board) throws ParseException {
		Move move=null;
		int nMatches=0;
		List<Move> legalMoves = App.getMoveGen().genLegalMoves(board);
		for (Move legalMove : legalMoves) {
			if (srcFile != null && !srcFile.equals(legalMove.from().file()))
				continue;
			if (srcRank != null && !srcRank.equals(legalMove.from().rank()))
				continue;
			if (!dstSquare.equals(legalMove.to()))
				continue;
			if (!piece.equals(board.getPiece(legalMove.from())))
				continue;
			if (promo==null) {
				if (legalMove.promotion()!=null)
					continue;
			} else {
				if (!promo.equals(legalMove.promotion()))
					continue;
			}
			nMatches++;
			move = legalMove;
		}
		if (nMatches != 1) {
			throw new ParseException("matches: " + nMatches,0);
		}
		return move;
	}
	
	private static boolean isLegalMove(Move move,Board board) {
		List<Move> moves = App.getMoveGen().genLegalMoves(board);
		return moves.contains(move);
	}

}
