context: using System;
  using System.Collections.Generic;
  using System.IO;
  using Antlr4.Runtime;
  using Redchess.Pgn;
  using RedChess.ChessCommon;
  using RedChess.ChessCommon.Enumerations;
  using RedChess.ChessCommon.Interfaces;
  using RedChess.EngineFactory;
  
  namespace RedChess.PgnProcessor
  {
      class PgnProcessor : IPgnProcessor
      {
          private readonly DateTime m_Start;
          private int m_MoveCount;
          private static readonly Dictionary<char, PieceType> Lookup;
          private IBoard m_Board;
          private readonly Action<string, string, ChessMove> m_OnMoveAction;
          private readonly Action m_OnGameOverAction;
  
          static PgnProcessor()
          {
              Lookup = new Dictionary<char, PieceType>
              {
                  ['Q'] = PieceType.Queen,
                  ['K'] = PieceType.King,
                  ['N'] = PieceType.Knight,
                  ['R'] = PieceType.Rook,
                  ['B'] = PieceType.Bishop,
                  ['P'] = PieceType.Pawn
              };
          }
  
          internal PgnProcessor(Action<string, string, ChessMove> onMoveAction, Action onGameOverAction)
          {
              m_OnMoveAction = onMoveAction;
              m_OnGameOverAction = onGameOverAction;
              m_Board = BoardFactory.CreateInstance();
              m_Start = DateTime.UtcNow;
              m_MoveCount = 0;
  
              m_OnMoveAction?.Invoke(m_Board.ToFen(), String.Empty, null);
          }
  
          public void DoFen(string fen)
          {
              m_Board.FromFen(fen);
          }
  
          internal string Stats()
          {
              var timeTaken = DateTime.UtcNow - m_Start;
              return $"{m_MoveCount} moves in {timeTaken} ({m_MoveCount / timeTaken.TotalSeconds} moves per second)";
          }
  
          public void ResetGame()
          {
              m_Board = BoardFactory.CreateInstance();
              m_OnGameOverAction?.Invoke();
          }
  
          private static void SymbolToBasicPieceType(char symbol, out PieceType answer)
          {
              // This mimics the PGN rules, if a piece isn't specified it's a pawn.
              if (!Lookup.TryGetValue(symbol, out answer))
                  answer = PieceType.Pawn;
          }
  
          private ChessMove MakePieceMove(PieceType pieceType, Location targetLocation, string disambiguation, string promotedPiece)
          {
              m_MoveCount++;
  
              foreach (var location in m_Board.FindPieces(pieceType))
              {
                  // Disambiguator can be a column, a row, or a whole square (which would require four pieces of the same type on the board...)
                  if (!String.IsNullOrEmpty(disambiguation))
                  {
                      if (disambiguation.Length == 1)
                      {
                          if (Char.IsLetter(disambiguation[0]))
                          {
                              if (!location.ToString().ToLower().StartsWith(disambiguation))
                                  continue;
                          }
                          else if (Char.IsDigit(disambiguation[0]))
                          {
                              if (!location.ToString().EndsWith(disambiguation))
                                  continue;
                          }
                      }
                      else
                      {
                          if (location.ToString().ToLower() != disambiguation)
                              continue;
                      }
                  }
  
                  if (m_Board.Move(location, targetLocation))
                      return new ChessMove(location, targetLocation, promotedPiece);
              }
  
              throw new InvalidDataException();
          }
          
          private PieceType CalculatePieceTypeFromSymbol(char symbol)
          {
              PieceType basicPieceType;
  
              SymbolToBasicPieceType(symbol, out basicPieceType);
  
              if (m_Board.CurrentTurn == PieceColor.Black)
                  basicPieceType |= PieceType.Black;
              else
                  basicPieceType |= PieceType.White;
  
              return basicPieceType;
          }
  
          private static Location LowerCaseSquareToLocation(string square)
          {
              return (Location)Enum.Parse(typeof(Location), square.ToUpper());
          }
  
          public void ProcessMove(IToken token, string promotedPiece, string checkOrMate, string annotationGlyph)
          {
              string tokenText = token.Text;
              Location targetLocation;
              PieceType movingPiece;
              string disambiguationToken = null;
  
              switch (token.Type)
              {
                  case PgnParser.PIECE_TO_SQUARE:
                  {
                      if (tokenText.Length == 2)
                      {
                          targetLocation = LowerCaseSquareToLocation(tokenText);
                          movingPiece = CalculatePieceTypeFromSymbol('P');
                      }
                      else
                      {
                          disambiguationToken = tokenText.Substring(1, tokenText.Length - 3);
                          targetLocation = LowerCaseSquareToLocation(tokenText.Substring(tokenText.Length - 2));
                          movingPiece = CalculatePieceTypeFromSymbol(tokenText[0]);
                      }
                      break;
                  }
                  case PgnParser.CAPTURE:
                  {
                      disambiguationToken = tokenText.Substring(1, tokenText.IndexOf('x') - 1);
                      movingPiece = CalculatePieceTypeFromSymbol(tokenText[0]);
                      if (movingPiece.IsOfType(PieceType.Pawn))
                          disambiguationToken = tokenText[0].ToString(); // Pawn capture looks like dxc6
                      targetLocation = LowerCaseSquareToLocation(token.Text.Substring(tokenText.Length - 2));
                      break;
                  }
                  case PgnParser.CASTLE_KINGSIDE:
                  {
                      if (m_Board.CurrentTurn == PieceColor.Black)
                      {
                          targetLocation = Location.G8;
                          movingPiece = PieceType.BlackKing;
                      }
                      else
                      {
                          targetLocation = Location.G1;
                          movingPiece = PieceType.WhiteKing;
                      }
                      break;
                  }
                  case PgnParser.CASTLE_QUEENSIDE:
                  {
                      if (m_Board.CurrentTurn == PieceColor.Black)
                      {
                          targetLocation = Location.C8;
                          movingPiece = PieceType.BlackKing;
                      }
                      else
                      {
                          targetLocation = Location.C1;
                          movingPiece = PieceType.WhiteKing;
                      }
                      break;
                  }
                  default:
                  {
                      throw new NotImplementedException();
                  }
              }
  
              try
              {
                  var moveMade = MakePieceMove(movingPiece, targetLocation, disambiguationToken, promotedPiece);
  
                  if (m_Board.IsAwaitingPromotionDecision())
                  {
                      m_Board.PromotePiece(promotedPiece);
                      tokenText += "=" + promotedPiece;
                  }
  
  
gt:                 tokenText += checkOrMate;