package com.christophdietze.jack.shared.pgn;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.christophdietze.jack.shared.board.GameResult;
import com.christophdietze.jack.shared.board.IllegalPositionException;
import com.christophdietze.jack.shared.board.Move;
import com.christophdietze.jack.shared.board.Position;
import com.christophdietze.jack.shared.board.PositionUtils;
import com.christophdietze.jack.shared.board.SanParser;
import com.christophdietze.jack.shared.board.SanParsingException;
import com.christophdietze.jack.shared.pgn.PgnToken.TokenType;
public class PgnParser {
private PgnLexer lexer = new PgnLexer();
private SanParser sanParser = new SanParser();
private Iterator<PgnToken> tokenIterator;
private PgnToken curToken;
private Position replayBoard;
private Position initialPosition;
private List<Move> moves = new ArrayList<Move>();
private String whitePlayerName;
private String blackPlayerName;
private Map<String, String> additionalTags = new HashMap<String, String>();
private GameResult gameResult;
public PgnParser() {
}
public void parse(String pgnString) throws PgnParsingException {
clear();
lexer.lex(pgnString);
tokenIterator = lexer.getTokens().iterator();
nextToken();
parsePgnDatabase();
}
public Position getInitialPosition() {
return initialPosition;
}
public List<Move> getMoves() {
return moves;
}
/**
* @return null if no name
*/
public String getWhitePlayerName() {
return whitePlayerName;
}
/**
* @return null if no name
*/
public String getBlackPlayerName() {
return blackPlayerName;
}
public GameResult getGameResult() {
return gameResult;
}
public Map<String, String> getAdditionalTags() {
return additionalTags;
}
private void clear() {
moves.clear();
initialPosition = Position.STARTING_POSITION;
replayBoard = Position.STARTING_POSITION;
whitePlayerName = null;
blackPlayerName = null;
gameResult = null;
additionalTags.clear();
}
private void nextToken() {
if (tokenIterator.hasNext()) {
curToken = tokenIterator.next();
} else {
curToken = null;
}
}
private void expectTokenType(TokenType type) throws PgnParsingException {
if (curToken.getType() != type) {
throw new PgnParsingException("Expected a token of type " + type + ", found " + curToken);
}
}
private void parsePgnDatabase() throws PgnParsingException {
while (curToken != null) {
parsePgnGame();
}
}
private void parsePgnGame() throws PgnParsingException {
parseTagSection();
parseMovetextSection();
}
private void parseTagSection() throws PgnParsingException {
while (curToken != null && curToken.getType() == TokenType.LEFT_BRACKET) {
nextToken();
expectTokenType(TokenType.SYMBOL);
String tagName = curToken.getValue();
nextToken();
expectTokenType(TokenType.STRING);
String tagValue = curToken.getValue();
nextToken();
expectTokenType(TokenType.RIGHT_BRACKET);
nextToken();
if (tagName.equals(PgnStandardTagName.White.name())) {
parsePlayerName(tagValue, true);
} else if (tagName.equals(PgnStandardTagName.Black.name())) {
parsePlayerName(tagValue, false);
} else if (tagName.equals(PgnStandardTagName.Result.name())) {
GameResult result = GameResult.parseSymbol(tagValue);
if (result == null) {
throw new PgnParsingException("Failed to parse Result tag with value '" + tagValue + "'");
}
this.gameResult = result;
} else if (tagName.equals("FEN")) {
try {
String fenString = tagValue;
FenParser fenParser = new FenParser();
try {
fenParser.parse(fenString);
} catch (IllegalPositionException ex) {
throw new PgnParsingException(ex);
}
initialPosition = fenParser.getPosition();
replayBoard = initialPosition;
} catch (FenParsingException ex) {
throw new PgnParsingException(ex);
}
} else {
additionalTags.put(tagName, tagValue);
}
}
}
private void parsePlayerName(String name, boolean isWhite) {
String result = (name == null || name.equals("") || name.equals("?") ? null : name);
if (isWhite) {
whitePlayerName = result;
} else {
blackPlayerName = result;
}
}
private void parseMovetextSection() throws PgnParsingException {
while (curToken != null) {
if (curToken.getType() == TokenType.LEFT_PARENTHESIS) {
throw new PgnParsingException("Parsing of recursive variation not supported");
} else if (curToken.getType() == TokenType.ASTERISK) {
this.gameResult = GameResult.UNDECIDED;
nextToken();
return;
} else if (curToken.getType() == TokenType.SYMBOL) {
GameResult result = GameResult.parseSymbol(curToken.getValue());
if (result != null) {
if (this.gameResult != null && this.gameResult != result) {
throw new PgnParsingException("Conflicting specifications of game result, found " + this.gameResult
+ " and " + result);
}
this.gameResult = result;
nextToken();
return;
}
}
parseElement();
}
}
private void parseElement() throws PgnParsingException {
if (curToken.getType() == TokenType.COMMENT) {
// TODO save comments
nextToken();
return;
}
expectTokenType(TokenType.SYMBOL);
String value = curToken.getValue();
if (Character.isDigit(value.charAt(0))) {
// move number
parseMoveNumberIndication();
return;
}
if (value.charAt(0) == '$') {
throw new PgnParsingException("Parsing of NAG not supported");
} else {
// SAN-move
String sanString = curToken.getValue();
try {
Move move = sanParser.parse(sanString, replayBoard);
replayBoard = PositionUtils.makeMove(replayBoard, move);
moves.add(move);
} catch (SanParsingException ex) {
throw new PgnParsingException("Parsing of move '" + sanString + "' failed", ex);
}
nextToken();
}
}
private void parseMoveNumberIndication() throws PgnParsingException {
expectTokenType(TokenType.SYMBOL);
Integer.parseInt(curToken.getValue());
nextToken();
while (curToken.getType() == TokenType.PERIOD) {
nextToken();
}
}
}