text: "use strict";
  
  /**
   * Parser for chess moves in algebraic notation, e.g. "1. e4 c5 (comment here) 2. Nf3 d6".
   * The idea is to parse a list of moves starting from the initial state, yielding a valid chess position.
   * @constructor
   * @see http://goo.gl/B39TC (Algebraic notation)
   */
  Chess.Parser = function() {
  };
  
  /**
   * Cleans uninteresting parts of a move string
   * @param {string} text
   * @return {string}
   * @see http://goo.gl/uAijB (Dashes and hyphens)
   */
  Chess.Parser.clean = function(text) {
  	text = text.replace(/[\r\n\t]/gm, " "); // normalize whitespace to spaces
  	text = text.replace(/[\u002D\u05BE\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u207B\u208B\u2212\u2E3A\u2E3B\uFE58\uFE63\uFF0D]/g, "-"); // normalize dashes
  	while (true) { // remove comments, i.e. (nested) parentheses and characters between them
  		var replaced = text.replace(/\([^()]*\)/g, "");
  		if (replaced === text) {
  			break;
  		}
  		text = replaced;
  	}
  	text = text.replace(/[^ a-z0-9.=:\u00BD-]/gi, " "); // only keep interesting characters
  	text = text.replace(/  +/g, " "); // normalize whitespace to one space
  	return text;
  };
  
  /**
   * @param {!Chess.Position} chessPosition
   * @param {string} text
   * @return {?Array.<!Chess.Move>}
   */
  Chess.Parser.parseOneMove = function(chessPosition, text) {
  	var legalMoves = chessPosition.getMoves();
  
  	var castling = text.match(/0-0(?:-0)?|O-O(?:-O)?/i);
  	if (castling) {
  		var kind = (castling[0].length === 3) ? Chess.Move.Kind.KING_CASTLE : Chess.Move.Kind.QUEEN_CASTLE;
  		return legalMoves.filter(/** @param {!Chess.Move} move */function(move) { return move.getKind() === kind; });
  	}
  
  	var move = text.match(/([NBRQK])?([a-h])?([1-8])?-?([x:])?([a-h])([1-8])?(?:[=(]([NBRQ]))?/);
  	if (move) {
  		var piece = move[1];
  		var fromFile = move[2];
  		var fromRank = move[3];
  		var capture = move[4];
  		var toFile = move[5];
  		var toRank = move[6];
  		var promotedPiece = move[7];
  		return legalMoves.filter(/** @param {!Chess.Move} move */function(move) {
  			if (piece !== undefined && Chess.PIECE_ALGEBRAIC_NAMES[move.getPiece()] !== piece) {
  				return false;
  			}
  
  			if (piece === undefined && move.getPiece() !== Chess.Piece.PAWN) {
  				return false;
  			}
  
  			if (fromFile !== undefined && Chess.FILE_CHARACTERS[Chess.getFile(move.getFrom())] !== fromFile) {
  				return false;
  			}
  
  			if (fromRank !== undefined && Chess.RANK_CHARACTERS[Chess.getRank(move.getFrom())] !== fromRank) {
  				return false;
  			}
  
  			if (capture !== undefined && !move.isCapture()) {
  				return false;
  			}
  
  			if (toFile !== undefined && Chess.FILE_CHARACTERS[Chess.getFile(move.getTo())] !== toFile) {
  				return false;
  			}
  
  			if (toRank !== undefined && Chess.RANK_CHARACTERS[Chess.getRank(move.getTo())] !== toRank) {
  				return false;
  			}
  
  			if (promotedPiece !== undefined && Chess.PIECE_ALGEBRAIC_NAMES[move.getPromotedPiece()] !== promotedPiece) {
  				return false;
  			}
  
  			return true;
  		});
  	}
  
  	return null;
  };
  
  /**
   * @param {string} text
   * @return {!Chess.Position}
   * @throws {Error}
   */
  Chess.Parser.parseMoves = function(text) {
  	var chessPosition = new Chess.Position;
  
  	Chess.Parser.clean(text).split(" ").every(/** @param {string} moveText */function(moveText) {
  		var moveNumber = moveText.match(/\d+\./);
  		if (moveNumber) {
  			return true;
  		}
  
  		var gameOver = moveText.match(/1-0|0-1|\u00BD-\u00BD/);
  		if (gameOver) {
  			return false;
  		}
  
  		var moves = Chess.Parser.parseOneMove(chessPosition, moveText);
  		if (!moves || moves.length !== 1) {
  			throw new Error("Parse error in '" + moveText + "'");
  		}
  		chessPosition.makeMove(moves[0]);
  
  		return true;
  	});
  
  	return chessPosition;
  };
  