/*
    Protector -- a UCI chess engine

    Copyright (C) 2009-2010 Raimund Heid (Raimund_Heid@yahoo.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "io.h"
#include "pgn.h"
#include "bitboard.h"
#include "position.h"
#include "movegeneration.h"
#include "fen.h"

#define BUFSIZE 8192
#define INCREMENT 1024

#define STATE_INIT	0
#define STATE_1		1
#define STATE_2		2
#define STATE_IGNORE    3

static char pieceName[16];
static UINT16 historyValueDummy[HISTORY_SIZE];
static INT16 positionalGainDummy[HISTORY_SIZE];

/*
 ******************************************************************************
 *
 *   File operations
 *
 ******************************************************************************
 */

static int scanForIndex(PGNFile * pgnfile, int state, char buffer[],
                        size_t bufsize, size_t offset)
{
   size_t i = 0;

   while (i < bufsize)
   {
      switch (buffer[i++])
      {
      case '\n':

         if (state < STATE_IGNORE)
         {
            state++;
         }

         break;

      case '\r':
      case ' ':
         continue;

      case '{':

         if (state < STATE_IGNORE)
         {
            state = STATE_IGNORE;
         }
         else
         {
            state++;
         }

         break;

      case '}':

         if (state > STATE_IGNORE)
         {
            state--;
         }
         else
         {
            state = 0;
         }

         break;

      case '[':

         if (state == 2)
         {
            if (pgnfile->numGames + 1 == pgnfile->indexSize)
            {
               pgnfile->indexSize += INCREMENT;
               pgnfile->index = (long *) realloc(pgnfile->index,
                                                 pgnfile->indexSize *
                                                 sizeof(long));
            }

            pgnfile->index[pgnfile->numGames++] = (long) (i + offset - 1L);
            assert(pgnfile->numGames < pgnfile->indexSize);
         }

      default:

         if (state != STATE_IGNORE)
         {
            state = 0;
         }
      }
   }

   return state;
}

static void buildIndex(PGNFile * pgnfile)
{
   char buffer[BUFSIZE];
   int state = STATE_2;
   size_t numRead;

   rewind(pgnfile->file);

   do
   {
      long pos = ftell(pgnfile->file);

      numRead = fread(buffer, 1, BUFSIZE, pgnfile->file);
      state = scanForIndex(pgnfile, state, buffer, numRead, pos);
   }
   while (numRead == BUFSIZE);

   fseek(pgnfile->file, 0, SEEK_END);
   pgnfile->index[pgnfile->numGames] = ftell(pgnfile->file) + 1;
}

int openPGNFile(PGNFile * pgnfile, const char *filename)
{
   pgnfile->index = (long *) malloc(INCREMENT * sizeof(long));

   pgnfile->indexSize = INCREMENT;
   pgnfile->numGames = 0;
   pgnfile->file = fopen(filename, "rb");

   if (pgnfile->index != 0 && pgnfile->file != 0)
   {
      buildIndex(pgnfile);
      return 0;
   }
   else
   {
      return -1;
   }
}

void closePGNFile(PGNFile * pgnfile)
{
   if (pgnfile->index != 0)
   {
      free(pgnfile->index);
   }

   pgnfile->indexSize = 0;
   pgnfile->numGames = 0;

   if (pgnfile->file != 0)
   {
      fclose(pgnfile->file);
      pgnfile->file = 0;
   }
}

static char *getGameText(PGNFile * pgnfile, int number)
{
   long start, end, length;
   char *buffer;
   size_t numBytesRead;

   if (number < 1 || number > pgnfile->numGames)
   {
      return 0;
   }

   start = pgnfile->index[number - 1];
   end = pgnfile->index[number] - 1;
   length = (int) (end - start);

   if ((buffer = malloc(length + 1)) == NULL)
   {
      return 0;
   }

   fseek(pgnfile->file, start, SEEK_SET);
   numBytesRead = fread(buffer, 1, length, pgnfile->file);
   buffer[length] = '\0';
   trim(buffer);

   return buffer;
}

/*
 ******************************************************************************
 *
 *   Object generation and destruction
 *
 ******************************************************************************
 */

static Gamemove *getGamemove(const Position * position,
                             Gamemove * previousMove)
{
   Gamemove *new = (Gamemove *) malloc(sizeof(Gamemove));

   new->from = NO_SQUARE;
   new->to = NO_SQUARE;
   new->newPiece = NO_PIECE;
   new->position = *position;
   new->previousMove = previousMove;
   new->nextMove = new->alternativeMove = 0;
   new->comment = new->glyphs = 0;

   return new;
}

Move gameMove2Move(const Gamemove * gamemove)
{
   return getPackedMove(gamemove->from, gamemove->to, gamemove->newPiece);
}

static void freeGamemove(Gamemove * move)
{
   if (move->nextMove != 0)
   {
      freeGamemove(move->nextMove);
   }

   if (move->alternativeMove != 0)
   {
      freeGamemove(move->alternativeMove);
   }

   if (move->comment != 0)
   {
      free(move->comment);
   }

   if (move->glyphs != 0)
   {
      free(move->glyphs);
   }

   free(move);
}

void initializePGNGame(PGNGame * game)
{
   game->setup[0] = game->fen[0] = '\0';
   game->moveText = 0;
   game->firstMove = game->lastMove = 0;
}

void resetPGNGame(PGNGame * game)
{
   if (game->moveText != 0)
   {
      free(game->moveText);
   }

   if (game->firstMove != 0)
   {
      freeGamemove(game->firstMove);
   }

   initializePGNGame(game);
}

void freePgnGame(PGNGame * game)
{
   resetPGNGame(game);
   free(game);
}

/*
 ******************************************************************************
 *
 *   Parser operations
 *
 ******************************************************************************
 */

static Move parsePawnMove(const Position * position, const char *moveText)
{
   Square from = NO_SQUARE, to = NO_SQUARE;
   Piece newPiece = NO_PIECE;
   int pawnStep = (position->activeColor == WHITE ? 8 : -8);
   size_t textLength = strlen(moveText);

   if (textLength < 2)
   {
      return NO_MOVE;
   }

   assert(textLength <= 6);

   /* 
    * capture or push? 
    */
   if (textLength >= 4 && moveText[1] == 'x')
   {
      to = getSquare(moveText[2] - 'a', moveText[3] - '1');
      from = (Square)
         (getSquare(moveText[0] - 'a', moveText[3] - '1') - pawnStep);
   }
   else
   {
      to = getSquare(moveText[0] - 'a', moveText[1] - '1');
      from = (Square) (to - pawnStep);

      if (position->piece[from] == NO_PIECE)
      {
         from = (Square) (from - pawnStep);
      }
   }

   /* 
    * promotion? 
    */
   if (textLength >= 4 && moveText[textLength - 2] == '=')
   {
      switch (moveText[textLength - 1])
      {
      case 'R':
         newPiece = WHITE_ROOK;
         break;
      case 'B':
         newPiece = WHITE_BISHOP;
         break;
      case 'N':
         newPiece = WHITE_KNIGHT;
         break;
      default:
         newPiece = WHITE_QUEEN;
      }
   }

   return getPackedMove(from, to, newPiece);
}

static Move parseCastlingMove(const Position * position, const char *moveText)
{
   const Rank rank = (position->activeColor == WHITE ? RANK_1 : RANK_8);
   const File file = (strcmp(moveText, "O-O") == 0 ? FILE_G : FILE_C);

   assert(strlen(moveText) >= 3 && strlen(moveText) <= 5);

   return getPackedMove(getSquare(FILE_E, rank), getSquare(file, rank),
                        NO_PIECE);
}

static Move parsePieceMove(const Position * position,
                           const char *moveText, const PieceType pieceType)
{
   Square from = NO_SQUARE, to = NO_SQUARE;
   Piece newPiece = NO_PIECE;
   size_t textLength = strlen(moveText);
   int fromFile = -1, fromRank = -1;
   size_t pointer = 1;
   char currentChar;
   Bitboard candidates;

   if (textLength < 3)
   {
      return NO_MOVE;
   }

   assert(strlen(moveText) <= 6);

   to = getSquare(moveText[textLength - 2] - 'a',
                  moveText[textLength - 1] - '1');

   while (pointer < textLength - 2)
   {
      currentChar = moveText[pointer++];

      if (currentChar >= 'a' && currentChar <= 'h')
      {
         fromFile = currentChar - 'a';
      }

      if (currentChar >= '1' && currentChar <= '8')
      {
         fromRank = currentChar - '1';
      }
   }

   if (fromFile != -1 && fromRank != -1)
   {
      from = getSquare(fromFile, fromRank);
   }
   else
   {
      candidates =
         getDirectAttackers(position, to, position->activeColor,
                            position->allPieces) &
         position->piecesOfType[pieceType | position->activeColor];

      if (fromFile != -1)
      {
         candidates &= getSquaresOfFile((File) fromFile);
      }
      else if (fromRank != -1)
      {
         candidates &= getSquaresOfRank((Rank) fromRank);
      }

      if (candidates == EMPTY_BITBOARD)
      {
         return NO_MOVE;
      }

      from = getLastSquare(&candidates);

      while (candidates != EMPTY_BITBOARD)
      {
         const Move move = getPackedMove(from, to, newPiece);

         if (moveIsLegal(position, move))
         {
            return move;
         }

         from = getLastSquare(&candidates);
      }
   }

   return getPackedMove(from, to, newPiece);
}

static Move parsePGNMove(const char *moveText, Position * position)
{
   char moveTextBuffer[16];
   int i = 0;

   while (i < 15 && moveText[i] != 0 && moveText[i] != '+' &&
          moveText[i] != '#' && moveText[i] != '!' &&
          moveText[i] != '?' && moveText[i] != ')' &&
          !isspace((int) moveText[i]))
   {
      moveTextBuffer[i] = moveText[i];
      i++;
   }

   moveTextBuffer[i] = 0;

   switch (moveTextBuffer[0])
   {
   case 'a':
   case 'b':
   case 'c':
   case 'd':
   case 'e':
   case 'f':
   case 'g':
   case 'h':
      return parsePawnMove(position, moveTextBuffer);
      break;
   case 'K':
      return parsePieceMove(position, moveTextBuffer, KING);
      break;
   case 'Q':
      return parsePieceMove(position, moveTextBuffer, QUEEN);
      break;
   case 'R':
      return parsePieceMove(position, moveTextBuffer, ROOK);
      break;
   case 'B':
      return parsePieceMove(position, moveTextBuffer, BISHOP);
      break;
   case 'N':
      return parsePieceMove(position, moveTextBuffer, KNIGHT);
      break;
   case 'O':
      return parseCastlingMove(position, moveTextBuffer);
      break;
   default:

      return NO_MOVE;
   }
}

Move interpretPGNMove(const char *moveText, PGNGame * game)
{
   Variation variation;

   initializeVariationFromGame(&variation, game);

   return parsePGNMove(moveText, &variation.singlePosition);
}

int appendMove(PGNGame * game, const Move move)
{
   Variation variation;

   initializeVariationFromGame(&variation, game);

   if (moveIsLegal(&variation.singlePosition, move))
   {
      Gamemove *newMove =
         getGamemove(&variation.singlePosition, game->lastMove);

      newMove->from = getFromSquare(move);
      newMove->to = getToSquare(move);
      newMove->newPiece = getNewPiece(move);

      if (game->lastMove == 0)
      {
         game->firstMove = game->lastMove = newMove;
      }
      else
      {
         game->lastMove->nextMove = newMove;
         game->lastMove = newMove;
      }

      return 0;
   }
   else
   {
      return -1;
   }
}

void takebackLastMove(PGNGame * game)
{
   if (game->lastMove != 0)
   {
      if (game->lastMove == game->firstMove)
      {
         game->lastMove = 0;
      }
      else
      {
         game->lastMove = game->lastMove->previousMove;
      }
   }
}

static void addAlternativeMove(Gamemove * move, Gamemove * alternative)
{
   if (move->alternativeMove != 0)
   {
      addAlternativeMove(move->alternativeMove, alternative);
   }
   else
   {
      move->alternativeMove = alternative;
      alternative->previousMove = move->previousMove;
   }
}

static Gamemove *parseMoveText(const char *pgnMoveText,
                               size_t * offset, const Position * position)
{
   bool variationTerminated = FALSE;
   Gamemove *baseMove = 0, *lastMove = 0;
   Variation variation;
   char currentChar;
   const char *token;
   const bool debugOutput = FALSE;

   setBasePosition(&variation, position);
   prepareSearch(&variation);

   if (debugOutput)
   {
      logDebug("Starting pgn parsing at: >%s<\n", &pgnMoveText[*offset]);
   }

   while (variationTerminated == FALSE &&
          (currentChar = pgnMoveText[(*offset)++]) != '\0')
   {
      if (strchr(" \r\n0123456789.", currentChar) != 0)
      {
         continue;
      }

      token = &(pgnMoveText[*offset - 1]);

      if (debugOutput)
      {
         logDebug("\nCurrent token: >%s<\n", token);
      }

      if (currentChar == '{')
      {
         char *comment = getToken(token + 1, "}");

         if (debugOutput)
         {
            logDebug("Starting annotation: >%s<\n", comment);
         }

         (*offset) += strlen(comment);

         if (lastMove != 0)
         {
            lastMove->comment = comment;
         }
         else
         {
            free(comment);
         }

         continue;
      }

      if (currentChar == '(')
      {
         if (debugOutput)
         {
            logDebug("Starting subvariation.\n");
         }

         if (lastMove != 0)
         {
            addAlternativeMove(lastMove,
                               parseMoveText(pgnMoveText, offset,
                                             &lastMove->position));
         }
      }

      if (currentChar == ')')
      {
         variationTerminated = TRUE;

         if (debugOutput)
         {
            logDebug("Subvariation terminated.\n");
         }
      }

      if (currentChar == '$')
      {
         char *glyph = getToken(token, " )\r\n");

         if (lastMove != 0)
         {
            if (lastMove->glyphs != 0)
            {
               if (debugOutput)
               {
                  logDebug("Existing glyphs: >%s<\n", lastMove->glyphs);
               }

               lastMove->glyphs = realloc(lastMove->glyphs,
                                          strlen(lastMove->glyphs) +
                                          strlen(glyph) + 2);
               strcat(lastMove->glyphs, " ");
               strcat(lastMove->glyphs, glyph);
               free(glyph);
            }
            else
            {
               if (debugOutput)
               {
                  logDebug("Initializing glyphs...\n");
               }

               lastMove->glyphs = glyph;
            }

            if (debugOutput)
            {
               logDebug("glyphs: >%s<\n", lastMove->glyphs);
            }
         }
         else
         {
            free(glyph);
         }
      }

      if (strchr("KQRBNOabcdefgh", currentChar) != 0)
      {
         Move move;

         /*dumpPosition(variation->currentPosition); */

         if (debugOutput)
         {
            logDebug("Move token: >%s<\n", token);
         }

         move = parsePGNMove(token, &variation.singlePosition);

         if (moveIsLegal(&variation.singlePosition, move))
         {
            Gamemove *newMove =
               getGamemove(&variation.singlePosition, lastMove);

            newMove->from = getFromSquare(move);
            newMove->to = getToSquare(move);
            newMove->newPiece = getNewPiece(move);

            if (lastMove == 0)
            {
               baseMove = newMove;
            }
            else
            {
               lastMove->nextMove = newMove;
            }

            lastMove = newMove;
            makeMove(&variation, move);
            setBasePosition(&variation, &variation.singlePosition);
         }
         else
         {
            logDebug("### Illegal move: %s\n", token);
            dumpMove(move);
            dumpPosition(&variation.singlePosition);
            exit(-1);

            break;
         }

         while (isspace((int) pgnMoveText[*offset]) == FALSE &&
                pgnMoveText[*offset] != ')')
         {
            (*offset)++;
         }
      }
   }

   return baseMove;
}

static Gamemove *parseMoveSection(const char *pgnMoveText,
                                  const Position * position)
{
   size_t offset = 0;

   return parseMoveText(pgnMoveText, &offset, position);
}

static void parseRoasterValue(const char *pgn, const char *name,
                              char value[PGN_ROASTERLINE_SIZE])
{
   char *nameBegin, *valueBegin, *valueEnd;
   char buffer[2 * PGN_ROASTERLINE_SIZE];
   size_t valueLength;

   strcpy(value, "");
   strcpy(buffer, "[");
   strcat(buffer, name);

   if ((nameBegin = strstr(pgn, buffer)) == 0)
   {
      return;
   }

   if ((valueBegin = strstr(nameBegin, "\"")) == 0)
   {
      return;
   }

   if ((valueEnd = strstr(valueBegin, "\"]")) == 0)
   {
      return;
   }

   valueLength = min(PGN_ROASTERLINE_SIZE, valueEnd - valueBegin - 1);
   memcpy(value, valueBegin + 1, valueLength);
   value[valueLength] = '\0';
}

static void parseRoasterValues(const char *pgn, PGNGame * pgngame)
{
   char *moves;
   Variation variation;

   parseRoasterValue(pgn, "Event", pgngame->event);
   parseRoasterValue(pgn, "Site", pgngame->site);
   parseRoasterValue(pgn, "Date", pgngame->date);
   parseRoasterValue(pgn, "Round", pgngame->round);
   parseRoasterValue(pgn, "White", pgngame->white);
   parseRoasterValue(pgn, "Black", pgngame->black);
   parseRoasterValue(pgn, "Result", pgngame->result);
   parseRoasterValue(pgn, "SetUp", pgngame->setup);
   parseRoasterValue(pgn, "FEN", pgngame->fen);
   parseRoasterValue(pgn, "WhiteTitle", pgngame->whiteTitle);
   parseRoasterValue(pgn, "BlackTitle", pgngame->blackTitle);
   parseRoasterValue(pgn, "WhiteElo", pgngame->whiteElo);
   parseRoasterValue(pgn, "BlackElo", pgngame->blackElo);
   parseRoasterValue(pgn, "ECO", pgngame->eco);
   parseRoasterValue(pgn, "NIC", pgngame->nic);
   parseRoasterValue(pgn, "TimeControl", pgngame->timeControl);
   parseRoasterValue(pgn, "Termination", pgngame->termination);
   pgngame->moveText = 0;
   pgngame->firstMove = 0;

   initializeVariationFromGame(&variation, pgngame);

   if ((moves = strstr(pgn, "\n\n")) == 0)
   {
      moves = strstr(pgn, "\r\n\r\n");
   }

   if (moves != 0)
   {
      if ((pgngame->moveText = malloc(strlen(moves) + 1)) != 0)
      {
         strcpy(pgngame->moveText, moves);
         trim(pgngame->moveText);

         pgngame->firstMove =
            parseMoveSection(pgngame->moveText, &variation.singlePosition);
      }
   }
}

PGNGame *getGame(PGNFile * pgnfile, int number)
{
   PGNGame *pgngame;
   char *gameText = getGameText(pgnfile, number);

   if (gameText == 0)
   {
      return 0;
   }

   if ((pgngame = (PGNGame *) malloc(sizeof(PGNGame))) == NULL)
   {
      return 0;
   }

   initializePGNGame(pgngame);
   parseRoasterValues(gameText, pgngame);
   free(gameText);

   return pgngame;
}

/*
 ******************************************************************************
 *
 *   Generator operations
 *
 ******************************************************************************
 */

#define SAME_PIECE 1
#define SAME_FILE 2
#define SAME_RANK 4

static int examineAlternativeMoves(Variation * variation, const Move move)
{
   const Square from = getFromSquare(move);
   const Square to = getToSquare(move);
   const Piece newPiece = getNewPiece(move);
   int result = 0;
   Position *position = &variation->singlePosition;
   Piece piece = position->piece[from];
   Square square;
   Bitboard candidates =
      getDirectAttackers(position, to, position->activeColor,
                         position->allPieces) & position->piecesOfType[piece];

   clearSquare(candidates, from);

   ITERATE_BITBOARD(&candidates, square)
   {
      const Move tmp = getPackedMove(square, to, newPiece);

      if (moveIsLegal(position, tmp))
      {
         result |= SAME_PIECE;

         if (file(square) == file(from))
         {
            result |= SAME_FILE;
         }

         if (rank(square) == rank(from))
         {
            result |= SAME_RANK;
         }
      }
   }

   return result;
}

void generateMoveText(Variation * variation, const Move move, char *pgnMove)
{
   Position *position = &variation->singlePosition;
   const char *castlings[] = { "O-O", "O-O-O" };
   const Square from = getFromSquare(move);
   const Square to = getToSquare(move);
   const Piece newPiece = getNewPiece(move);

   Piece movingPiece = position->piece[from];
   char pieceSign[2], origin[3], destination[6], captureSign[2];
   char checkSign[2], promotionSign[3];
   int ambiguityCheckResult;

   getSquareName(to, destination);
   pieceSign[0] = origin[0] = captureSign[0] = checkSign[0] = 0;
   promotionSign[0] = 0;

   if (!moveIsLegal(position, move))
   {
      getSquareName(from, origin);
      sprintf(pgnMove, "(illegal move: %s-%s)", origin, destination);

      return;
   }
   else
   {
      makeMove(variation, move);

      if (activeKingIsSafe(&variation->singlePosition) == FALSE)
      {
         Movelist legalMoves;

         getLegalMoves(variation, &legalMoves);

         if (legalMoves.numberOfMoves == 0)
         {
            strcpy(checkSign, "#");
         }
         else
         {
            strcpy(checkSign, "+");
         }
      }

      unmakeLastMove(variation);
   }

   if (pieceType(movingPiece) == PAWN)
   {
      if (file(from) != file(to))
      {
         strcpy(captureSign, "x");
         sprintf(origin, "%c", fileName(file(from)));
      }

      if (newPiece != NO_PIECE)
      {
         sprintf(promotionSign, "=%c", pieceName[newPiece]);
      }
   }
   else
   {
      if (pieceType(movingPiece) == KING && _distance[from][to] > 1)
      {
         strcpy(destination, castlings[file(to) == FILE_G ? 0 : 1]);
      }
      else
      {
         sprintf(pieceSign, "%c", pieceName[pieceType(movingPiece)]);

         if (position->piece[to] != NO_PIECE)
         {
            strcpy(captureSign, "x");
         }

         ambiguityCheckResult = examineAlternativeMoves(variation, move);

         if (ambiguityCheckResult & SAME_PIECE)
         {
            switch (ambiguityCheckResult)
            {
            case SAME_PIECE | SAME_FILE | SAME_RANK:
               getSquareName(from, origin);
               break;

            case SAME_PIECE | SAME_FILE:
               sprintf(origin, "%c", rankName(rank(from)));
               break;

            default:
               sprintf(origin, "%c", fileName(file(from)));
            }
         }
      }
   }

   sprintf(pgnMove, "%s%s%s%s%s%s", pieceSign, origin, captureSign,
           destination, promotionSign, checkSign);
}

static char *generateMoveSection(Gamemove * gamemove, const char *result)
{
   String moveText = getEmptyString();
   char moveBuffer[64], *temp;
   Variation variation;
   bool restart = TRUE;

   initializeVariation(&variation, FEN_GAMESTART);
   variation.historyValue = &(historyValueDummy[0]);
   variation.positionalGain = &(positionalGainDummy[0]);

   while (gamemove != 0)
   {
      if (restart == TRUE && gamemove->position.activeColor == BLACK)
      {
         appendToString(&moveText, " %d...", gamemove->position.moveNumber);
      }

      restart = FALSE;

      setBasePosition(&variation, &gamemove->position);
      generateMoveText(&variation,
                       getPackedMove(gamemove->from, gamemove->to,
                                     gamemove->newPiece), moveBuffer);

      if (gamemove->position.activeColor == WHITE)
      {
         appendToString(&moveText, " %d. %s", gamemove->position.moveNumber,
                        moveBuffer);
      }
      else
      {
         appendToString(&moveText, " %s", moveBuffer);
      }

      if (gamemove->glyphs != 0)
      {
         appendToString(&moveText, " %s", gamemove->glyphs);
      }

      if (gamemove->comment != 0)
      {
         appendToString(&moveText, " {%s}", gamemove->comment);
      }

      if (gamemove->alternativeMove != 0)
      {
         temp = generateMoveSection(gamemove->alternativeMove, "");
         appendToString(&moveText, " (%s)", temp);
         free(temp);

         restart = TRUE;
      }

      gamemove = gamemove->nextMove;
   }

   if (result[0] != '\0')
   {
      appendToString(&moveText, " %s", result);
   }

   trim(moveText.buffer);
   breakLines(moveText.buffer, 79);

   return moveText.buffer;
}

static char *generateRoasterLine(const char *tag, const char *tagValue,
                                 char buffer[PGN_ROASTERLINE_SIZE])
{
   if (tagValue[0] != '\0')
   {
      sprintf(buffer, "[%s \"%s\"]\n", tag, tagValue);
   }
   else
   {
      buffer[0] = '\0';
   }

   return buffer;
}

static char *generateRoaster(const PGNGame * pgngame)
{
   char tagbuffer[PGN_ROASTERLINE_SIZE];
   String string = getEmptyString();

   appendToString(&string,
                  generateRoasterLine("Event", pgngame->event, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Site", pgngame->site, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Date", pgngame->date, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Round", pgngame->round, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("White", pgngame->white, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Black", pgngame->black, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Result", pgngame->result, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("SetUp", pgngame->setup, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("FEN", pgngame->fen, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("WhiteTitle", pgngame->whiteTitle,
                                      tagbuffer));
   appendToString(&string,
                  generateRoasterLine("BlackTitle", pgngame->blackTitle,
                                      tagbuffer));
   appendToString(&string,
                  generateRoasterLine("WhiteElo", pgngame->whiteElo,
                                      tagbuffer));
   appendToString(&string,
                  generateRoasterLine("BlackElo", pgngame->blackElo,
                                      tagbuffer));
   appendToString(&string,
                  generateRoasterLine("ECO", pgngame->eco, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("NIC", pgngame->nic, tagbuffer));
   appendToString(&string,
                  generateRoasterLine("TimeControl", pgngame->timeControl,
                                      tagbuffer));
   appendToString(&string,
                  generateRoasterLine("Termination", pgngame->termination,
                                      tagbuffer));

   return string.buffer;
}

char *generatePgn(PGNGame * game)
{
   String string = getEmptyString();
   char *roaster = generateRoaster(game);
   char *moveText = generateMoveSection(game->firstMove, game->result);

   appendToString(&string, "%s\n%s", roaster, moveText);
   free(roaster);
   free(moveText);

   return string.buffer;
}

void initializeVariationFromGame(Variation * variation, PGNGame * game)
{
   if (game->lastMove != 0)
   {
      Gamemove *currentMove = game->lastMove;
      int i = POSITION_HISTORY_OFFSET - 1;

      setBasePosition(variation, &(game->lastMove->position));
      makeMove(variation, gameMove2Move(game->lastMove));

      if (variation->singlePosition.activeColor == WHITE)
      {
         variation->singlePosition.moveNumber++;
      }

      setBasePosition(variation, &variation->singlePosition);
      prepareSearch(variation);

      while (i >= 0 && currentMove != 0)
      {
         variation->positionHistory[i--] = currentMove->position.hashValue;
         currentMove = currentMove->previousMove;
      }
   }
   else
   {
      if (strcmp(game->setup, "") != 0)
      {
         initializeVariation(variation, game->fen);
      }
      else
      {
         initializeVariation(variation, FEN_GAMESTART);
      }
   }
}

void initializeGameFromVariation(const Variation * variation, PGNGame * game,
                                 bool copyPv)
{
   resetPGNGame(game);
   getFen(&variation->startPosition, game->fen);
   strcpy(game->setup, "1");

   if (copyPv != FALSE)
   {
      int i = 0;

      for (i = 0; i < min(8, variation->pv.length); i++)
      {
         Move move = (Move) variation->pv.move[i];

         if (appendMove(game, move) != 0)
         {
            break;
         }
      }
   }
}

char *getPrincipalVariation(const Variation * variation)
{
   PGNGame game;
   char *pv;

   initializePGNGame(&game);
   initializeGameFromVariation(variation, &game, TRUE);
   pv = generateMoveSection(game.firstMove, "");
   trim(pv);
   resetPGNGame(&game);

   return pv;
}

/*
 ******************************************************************************
 *
 *   Module initialization and testing
 *
 ******************************************************************************
 */

int initializeModulePgn()
{
   pieceName[WHITE_KING] = 'K';
   pieceName[WHITE_QUEEN] = 'Q';
   pieceName[WHITE_ROOK] = 'R';
   pieceName[WHITE_BISHOP] = 'B';
   pieceName[WHITE_KNIGHT] = 'N';
   pieceName[WHITE_PAWN] = 'P';
   pieceName[BLACK_KING] = 'K';
   pieceName[BLACK_QUEEN] = 'Q';
   pieceName[BLACK_ROOK] = 'R';
   pieceName[BLACK_BISHOP] = 'B';
   pieceName[BLACK_KNIGHT] = 'N';
   pieceName[BLACK_PAWN] = 'P';

   return 0;
}

static int testGeneration()
{
   char *p1 = "r2qkb1r/ppp2ppp/3p4/5b2/3PN3/n4N2/PPP1QPPP/R3K2R w KQkq - 0 1";
   char *p2 = "r3k2r/n3nppp/3n2P1/8/1q1q4/2B5/1q1pRPPP/5RK1 b kq - 0 1";
   Variation variation;
   char pgnMove[64];

   initializeVariation(&variation, p1);

   generateMoveText(&variation, getPackedMove(D4, D5, NO_PIECE), pgnMove);
   assert(strcmp("d5", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(E1, G1, NO_PIECE), pgnMove);
   assert(strcmp("O-O", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(E1, C1, NO_PIECE), pgnMove);
   assert(strcmp("O-O-O", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(E4, D6, NO_PIECE), pgnMove);
   assert(strcmp("Nxd6+", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(E4, F6, NO_PIECE), pgnMove);
   assert(strcmp("Nf6#", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(F3, G5, NO_PIECE), pgnMove);
   assert(strcmp("Nfg5", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(E4, G5, NO_PIECE), pgnMove);
   assert(strcmp("Neg5+", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(B2, A3, NO_PIECE), pgnMove);
   assert(strcmp("bxa3", pgnMove) == 0);

   initializeVariation(&variation, p2);

   generateMoveText(&variation, getPackedMove(E7, F5, NO_PIECE), pgnMove);
   assert(strcmp("(illegal move: e7-f5)", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(D2, D1, WHITE_BISHOP), pgnMove);
   assert(strcmp("d1=B", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(B4, C3, NO_PIECE), pgnMove);
   assert(strcmp("Qb4xc3", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(B2, C3, NO_PIECE), pgnMove);
   assert(strcmp("Q2xc3", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(D4, C3, NO_PIECE), pgnMove);
   assert(strcmp("Qdxc3", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(D6, F5, NO_PIECE), pgnMove);
   assert(strcmp("Nf5", pgnMove) == 0);

   generateMoveText(&variation, getPackedMove(D6, C8, NO_PIECE), pgnMove);
   assert(strcmp("Ndc8", pgnMove) == 0);

   return 0;
}

static int testParsing()
{
   char *p1 = "r2qkb1r/ppp2ppp/3p4/5b2/3PN3/n4N2/PPP1QPPP/R3K2R w KQkq - 0 1";
   char *p2 = "r3k2r/n3nppp/3n2P1/8/1q1q4/2B5/1q1pRPPP/5RK1 b kq - 0 1";
   Variation variation;

   initializeVariation(&variation, p1);

   assert(parsePGNMove("d5", &variation.singlePosition) ==
          getPackedMove(D4, D5, NO_PIECE));

   assert(parsePGNMove("b4", &variation.singlePosition) ==
          getPackedMove(B2, B4, NO_PIECE));

   assert(parsePGNMove("bxa3", &variation.singlePosition) ==
          getPackedMove(B2, A3, NO_PIECE));

   assert(parsePGNMove("O-O", &variation.singlePosition) ==
          getPackedMove(E1, G1, NO_PIECE));

   assert(parsePGNMove("O-O-O", &variation.singlePosition) ==
          getPackedMove(E1, C1, NO_PIECE));

   assert(parsePGNMove("Ng3+", &variation.singlePosition) ==
          getPackedMove(E4, G3, NO_PIECE));

   assert(parsePGNMove("Nf6#", &variation.singlePosition) ==
          getPackedMove(E4, F6, NO_PIECE));

   assert(parsePGNMove("Nfg5", &variation.singlePosition) ==
          getPackedMove(F3, G5, NO_PIECE));

   initializeVariation(&variation, p2);

   assert(parsePGNMove("h6", &variation.singlePosition) ==
          getPackedMove(H7, H6, NO_PIECE));

   assert(parsePGNMove("f5", &variation.singlePosition) ==
          getPackedMove(F7, F5, NO_PIECE));

   assert(parsePGNMove("fxg6", &variation.singlePosition) ==
          getPackedMove(F7, G6, NO_PIECE));

   assert(parsePGNMove("d1=B", &variation.singlePosition) ==
          getPackedMove(D2, D1, BISHOP));

   assert(parsePGNMove("O-O", &variation.singlePosition) ==
          getPackedMove(E8, G8, NO_PIECE));

   assert(parsePGNMove("O-O-O", &variation.singlePosition) ==
          getPackedMove(E8, C8, NO_PIECE));

   assert(parsePGNMove("Nf5", &variation.singlePosition) ==
          getPackedMove(D6, F5, NO_PIECE));

   assert(parsePGNMove("Q2xc3", &variation.singlePosition) ==
          getPackedMove(B2, C3, NO_PIECE));

   assert(parsePGNMove("Qdxc3", &variation.singlePosition) ==
          getPackedMove(D4, C3, NO_PIECE));

   assert(parsePGNMove("Qb4xc3", &variation.singlePosition) ==
          getPackedMove(B4, C3, NO_PIECE));

   return 0;
}

static int testLoading()
{
   PGNFile pgnfile;
   PGNGame *game, game2;
   char *gameText, *generatedGameText;

   pgnfile.numGames = 0;
   pgnfile.index = 0;
   pgnfile.file = 0;

   assert(openPGNFile(&pgnfile, "test.pgn") == 0);
   game = getGame(&pgnfile, 1);
   assert(game != 0);

   gameText = generatePgn(game);
   initializePGNGame(&game2);
   parseRoasterValues(gameText, &game2);
   generatedGameText = generatePgn(&game2);
   /* logDebug("\n>%s<\n", generatedGameText); */
   assert(strcmp(gameText, generatedGameText) == 0);

   free(generatedGameText);
   freePgnGame(game);
   resetPGNGame(&game2);
   free(gameText);
   closePGNFile(&pgnfile);

   return 0;
}

int testModulePgn()
{
   int result;

   if ((result = testLoading()) != 0)
   {
      return result;
   }

   if ((result = testGeneration()) != 0)
   {
      return result;
   }

   if ((result = testParsing()) != 0)
   {
      return result;
   }

   return 0;
}
