package com.christophdietze.jack.shared.board;

public class PositionUtils {

	public static Position makeMoveVerified(Position position, Move move) throws IllegalMoveException {
		MoveLegality legality = MoveChecker.isPseudoLegalMove(position, move);
		if (!legality.isLegal()) {
			throw new IllegalMoveException(legality.getMessage());
		}
		Position trialPosition = makeMove(position, move);
		if (MoveChecker.canCaptureKing(trialPosition)) {
			throw new IllegalMoveException();
		}
		return trialPosition;
	}

	public static Position makeMoveVerified(Position position, String algebraicMove) throws IllegalMoveException {
		return makeMoveVerified(position, ChessUtils.toMoveFromAlgebraic(algebraicMove));
	}

	public static Position makeMove(Position position, Move move) {
		if (isPseudoLegalCastleMove(position, move)) {
			// castle move
			Position.Builder builder = new Position.Builder(position);
			builder.clearEnPassantPawn().clearPhantomKings().switchPlayerToMove();
			if (builder.isWhiteToMove()) {
				builder.fullmoveNumber(builder.getFullmoveNumber() + 1);
			}
			makeCastleMove(builder, move);
			return builder.build();
		} else {
			// normal move
			Position.Builder builder = new Position.Builder(position);
			builder.clearEnPassantPawn().clearPhantomKings();
			Piece fromPiece = position.getPiece(move.getFrom());
			Piece toPiece = position.getPiece(move.getTo());
			builder.clearPhantomKings();
			// adjust castling availability
			if (fromPiece == Piece.WHITE_KING) {
				builder.canWhiteCastleKingside(false).canWhiteCastleQueenside(false);
			} else if (fromPiece == Piece.BLACK_KING) {
				builder.canBlackCastleKingside(false).canBlackCastleQueenside(false);
			}
			if (move.getFrom() == 0) {
				builder.canWhiteCastleQueenside(false);
			} else if (move.getFrom() == 7) {
				builder.canWhiteCastleKingside(false);
			} else if (move.getFrom() == 56) {
				builder.canBlackCastleQueenside(false);
			} else if (move.getFrom() == 63) {
				builder.canBlackCastleKingside(false);
			}

			// if we're capturing an en passant pawn, remove the real pawn, too
			if (toPiece == Piece.WHITE_EN_PASSANT_PAWN) {
				builder.piece(move.getTo() + 8, Piece.EMPTY);
			} else if (toPiece == Piece.BLACK_EN_PASSANT_PAWN) {
				builder.piece(move.getTo() - 8, Piece.EMPTY);
			}

			builder.clearEnPassantPawn();

			// create en passant pawns if necessary
			if (fromPiece == Piece.WHITE_PAWN && move.getFrom() / 8 == 1 && move.getTo() / 8 == 3) {
				int enPassantPawnIndex = move.getFrom() + 8;
				builder.enPassantPawnIndex(enPassantPawnIndex, true);
			} else if (fromPiece == Piece.BLACK_PAWN && move.getFrom() / 8 == 6 && move.getTo() / 8 == 4) {
				int enPassantPawnIndex = move.getFrom() - 8;
				builder.enPassantPawnIndex(enPassantPawnIndex, false);
			}

			boolean isPromotionMove = isPseudoPromotionMove(position, move);

			// actually move the selected piece
			builder.piece(move.getTo(), fromPiece);
			builder.piece(move.getFrom(), Piece.EMPTY);

			// if necessary, perform a promotion
			if (isPromotionMove) {
				if (move.getPromotionPiece() == null) {
					throw new RuntimeException("Made a promotion move, but no promotion piece selected");
				}
				Piece promoPiece = Piece.getFromColorAndPieceType(builder.isWhiteToMove(), move.getPromotionPiece());
				builder.piece(move.getTo(), promoPiece);
			}
			builder.switchPlayerToMove();
			if (builder.isWhiteToMove()) {
				builder.fullmoveNumber(builder.getFullmoveNumber() + 1);
			}
			return builder.build();
		}
	}

	public static Position makeMove(Position position, String algebraicMove) {
		return makeMove(position, ChessUtils.toMoveFromAlgebraic(algebraicMove));
	}

	/**
	 * TODO fix redundancy to MoveChecker.isPseudoLegalCastleMove
	 */
	private static boolean isPseudoLegalCastleMove(Position position, Move move) {
		if (position.isWhiteToMove() && move.getFrom() == 4 && move.getTo() == 6) {
			return (position.canWhiteCastleKingside() && position.getPiece(4) == Piece.WHITE_KING
					&& position.getPiece(5) == Piece.EMPTY && position.getPiece(6) == Piece.EMPTY && position.getPiece(7) == Piece.WHITE_ROOK);
		}
		if (position.isWhiteToMove() && move.getFrom() == 4 && move.getTo() == 2) {
			return (position.canWhiteCastleQueenside() && position.getPiece(0) == Piece.WHITE_ROOK
					&& position.getPiece(1) == Piece.EMPTY && position.getPiece(2) == Piece.EMPTY
					&& position.getPiece(3) == Piece.EMPTY && position.getPiece(4) == Piece.WHITE_KING);
		}

		if (!position.isWhiteToMove() && move.getFrom() == 60 && move.getTo() == 62) {
			return (position.canBlackCastleKingside() && position.getPiece(60) == Piece.BLACK_KING
					&& position.getPiece(61) == Piece.EMPTY && position.getPiece(62) == Piece.EMPTY && position.getPiece(63) == Piece.BLACK_ROOK);
		}
		if (!position.isWhiteToMove() && move.getFrom() == 60 && move.getTo() == 58) {
			return (position.canBlackCastleQueenside() && position.getPiece(56) == Piece.BLACK_ROOK
					&& position.getPiece(57) == Piece.EMPTY && position.getPiece(58) == Piece.EMPTY
					&& position.getPiece(59) == Piece.EMPTY && position.getPiece(60) == Piece.BLACK_KING);
		}
		return false;
	}

	public static boolean isPseudoPromotionMove(Position position, Move move) {
		if (position.getPiece(move.getFrom()) == Piece.WHITE_PAWN && move.getFrom() / 8 == 6) {
			return true;
		}
		if (position.getPiece(move.getFrom()) == Piece.BLACK_PAWN && move.getFrom() / 8 == 1) {
			return true;
		}
		return false;
	}

	public static enum GameState {
		// TODO: detect draw by 50 move rule
		// TODO: detect draw by threefold repetition
		ACTIVE, MATE, STALEMATE;
	}

	public static GameState getGameState(Position position) {
		if (MoveChecker.hasLegalMove(position)) {
			return GameState.ACTIVE;
		}
		Position trialPosition = new Position.Builder(position).whiteToMove(!position.isWhiteToMove()).build();
		if (MoveChecker.canCaptureKing(trialPosition)) {
			return GameState.MATE;
		} else {
			return GameState.STALEMATE;
		}
	}

	/**
	 * The specified move must already be checked for pseudo legality, this function does not check.
	 */
	private static Position.Builder makeCastleMove(Position.Builder builder, Move move) {
		assert (builder.getPiece(move.getFrom()) == Piece.WHITE_KING)
				|| (builder.getPiece(move.getFrom()) == Piece.BLACK_KING);
		if (move.getFrom() == 4 && move.getTo() == 6) {
			// white O-O
			builder.piece(4, Piece.EMPTY);
			builder.piece(5, Piece.WHITE_ROOK);
			builder.piece(6, Piece.WHITE_KING);
			builder.piece(7, Piece.EMPTY);
			builder.setPhantomKings(4, 5);
		} else if (move.getFrom() == 4 && move.getTo() == 2) {
			// white O-O-O
			builder.piece(0, Piece.EMPTY);
			builder.piece(1, Piece.EMPTY);
			builder.piece(2, Piece.WHITE_KING);
			builder.piece(3, Piece.WHITE_ROOK);
			builder.piece(4, Piece.EMPTY);
			builder.setPhantomKings(3, 4);
		} else if (move.getFrom() == 60 && move.getTo() == 62) {
			// black O-O
			builder.piece(60, Piece.EMPTY);
			builder.piece(61, Piece.BLACK_ROOK);
			builder.piece(62, Piece.BLACK_KING);
			builder.piece(63, Piece.EMPTY);
			builder.setPhantomKings(60, 61);
		} else if (move.getFrom() == 60 && move.getTo() == 58) {
			// black O-O-O
			builder.piece(56, Piece.EMPTY);
			builder.piece(57, Piece.EMPTY);
			builder.piece(58, Piece.BLACK_KING);
			builder.piece(59, Piece.BLACK_ROOK);
			builder.piece(60, Piece.EMPTY);
			builder.setPhantomKings(59, 60);
		} else {
			throw new AssertionError();
		}
		return builder;
	}

	public static String toDiagramString(Position pos) {
		StringBuilder sb = new StringBuilder();
		for (int y = 7; y >= 0; --y) {
			if (y == 7) {
				sb.append("\n");
			} else {
				sb.append("|\n");
			}
			sb.append("--------------------------------\n");
			for (int x = 0; x < 8; ++x) {
				int index = y * 8 + x;
				sb.append("| ");
				sb.append(pos.getPiece(index).getSymbol());
				sb.append(" ");
			}
		}
		sb.append("|\n--------------------------------\n");
		return sb.toString();
	}
}
