/*
    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 "tablebase.h"
#include "protector.h"
#include "io.h"
#include <assert.h>

#ifdef INCLUDE_TABLEBASE_ACCESS

#if defined TARGET_WINDOWS && defined SUPPORT_SMP
#include <Windows.h>

volatile long tbAccessCount = 0;
#endif

#if defined TARGET_LINUX && defined SUPPORT_SMP
#include <pthread.h>

volatile int lock;
volatile int tbAccessCount = 0;
#endif

#define MAX_PIECES_PER_SIDE 3
#define NO_EP 127

bool tbAvailable = FALSE;

#ifdef TARGET_WINDOWS
typedef unsigned __int64 INDEX;
#endif
#ifdef TARGET_LINUX
typedef unsigned long long INDEX;
#endif
typedef unsigned int squaret;

char *cache = 0;

#define	TB_FASTCALL             /* __fastcall */

typedef INDEX(*PfnCalcIndex) (squaret *, squaret *, squaret, int fInverse);

extern int IInitializeTb(char *pszPath);
extern int FTbSetCacheSize(void *pv, unsigned long cbSize);
extern void VTbCloseFiles(void);
extern int IDescFindFromCounters(int *piCount);
extern int FRegisteredFun(int iTb, int side);
extern PfnCalcIndex PfnIndCalcFun(int iTb, int side);
extern int TB_FASTCALL L_TbtProbeTable(int iTb, int side, INDEX indOffset);

#define pageL       65536
#define tbbe_ssL    ((pageL-4)/2)
#define bev_broken  (tbbe_ssL+1)        /* illegal or busted */
#define bev_mi1     tbbe_ssL    /* mate in 1 move */
#define bev_mimin   1           /* mate in max moves */
#define bev_draw    0           /* draw */
#define bev_limax   (-1)        /* mated in max moves */
#define bev_li0     (-tbbe_ssL) /* mated in 0 moves */

#define PfnIndCalc PfnIndCalcFun

int setTablebaseCacheSize(unsigned int size)
{
   unsigned long numBytes = (unsigned long) size * 1024 * 1024;

   if (cache != 0)
   {
      free(cache);
   }

   cache = malloc(numBytes);

   if (cache == 0)
   {
      logDebug("### Could not allocate tablebase cache (%lu bytes). ###\n",
               numBytes);

      return -1;
   }

   if (FTbSetCacheSize(cache, numBytes) == 0)
   {
      logDebug("### Could not set tablebase cache size (%lu bytes). ###\n",
               numBytes);

      free(cache);
      cache = 0;

      return -2;
   }

   /* logDebug("table base cache size set to %lu\n", numBytes); */

   return 0;
}

int initializeTablebase(const char *path)
{
   int result = IInitializeTb((char *) path);

   if (result > 0)
   {
      if (setTablebaseCacheSize(4) != 0)
      {
         closeTablebaseFiles();

         return -1;
      }

      /* logDebug("Tablebases found at %s\n", path); */
   }
   else
   {
      logDebug("### Error while looking for tablebases in %s ###\n", path);
   }

   tbAvailable = (bool) (result > 0);

   return (result > 0 ? 0 : -1);
}

void closeTablebaseFiles()
{
   VTbCloseFiles();
}

static void initializePieceData(const Bitboard * pieces, int *pieceCount,
                                unsigned int *pieceLocation)
{
   Bitboard tmp = *pieces;
   Square square;

   *pieceCount = 0;

   ITERATE_BITBOARD(&tmp, square)
   {
      *(pieceLocation++) = square;
      (*pieceCount)++;
   }
}

static int getMateValue(int fullMoves)
{
   if (fullMoves > 0)
   {
      return 1 - VALUE_MATED - 2 * fullMoves;
   }
   else
   {
      return VALUE_MATED - 2 * fullMoves;
   }
}

int probeTablebase(const Position * position)
{
   int pieceCount[10];
   unsigned int whitePieces[MAX_PIECES_PER_SIDE * 5 + 1];
   unsigned int blackPieces[MAX_PIECES_PER_SIDE * 5 + 1];
   unsigned int *pwhite, *pblack;
   int tableNr, fInvert, tableValue, fullMoves;
   Color color;
   INDEX index;
   Square enPassantSquare = (Square) NO_EP;

   initializePieceData(&position->piecesOfType[WHITE_PAWN], &pieceCount[0],
                       &whitePieces[0 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[WHITE_KNIGHT], &pieceCount[1],
                       &whitePieces[1 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[WHITE_BISHOP], &pieceCount[2],
                       &whitePieces[2 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[WHITE_ROOK], &pieceCount[3],
                       &whitePieces[3 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[WHITE_QUEEN], &pieceCount[4],
                       &whitePieces[4 * MAX_PIECES_PER_SIDE]);

   initializePieceData(&position->piecesOfType[BLACK_PAWN], &pieceCount[5],
                       &blackPieces[0 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[BLACK_KNIGHT], &pieceCount[6],
                       &blackPieces[1 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[BLACK_BISHOP], &pieceCount[7],
                       &blackPieces[2 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[BLACK_ROOK], &pieceCount[8],
                       &blackPieces[3 * MAX_PIECES_PER_SIDE]);
   initializePieceData(&position->piecesOfType[BLACK_QUEEN], &pieceCount[9],
                       &blackPieces[4 * MAX_PIECES_PER_SIDE]);

   tableNr = IDescFindFromCounters(pieceCount);

   if (tableNr == 0)
   {
      return TABLEBASE_ERROR;
   }

   whitePieces[5 * MAX_PIECES_PER_SIDE] = position->king[WHITE];
   blackPieces[5 * MAX_PIECES_PER_SIDE] = position->king[BLACK];

   if (tableNr > 0)
   {
      color = position->activeColor;
      fInvert = 0;
      pwhite = whitePieces;
      pblack = blackPieces;
   }
   else
   {
      color = opponent(position->activeColor);
      fInvert = 1;
      pwhite = blackPieces;
      pblack = whitePieces;
      tableNr = -tableNr;
   }

#ifdef SUPPORT_SMP
#if defined TARGET_WINDOWS
   while (InterlockedIncrementAcquire(&tbAccessCount) > 1)
   {
      InterlockedDecrement(&tbAccessCount);
   }
#endif

#if defined TARGET_LINUX
   pthread_spin_lock(&lock);
#endif
#endif

   if (FRegisteredFun(tableNr, color) == FALSE)
   {
#ifdef SUPPORT_SMP
#if defined TARGET_WINDOWS
      InterlockedDecrement(&tbAccessCount);
#endif

#if defined TARGET_LINUX
      pthread_spin_unlock(&lock);
#endif
#endif

      return TABLEBASE_ERROR;
   }

   if (position->enPassantSquare != NO_SQUARE)
   {
      const Bitboard attackers =
         position->piecesOfType[PAWN | position->activeColor] &
         generalMoves[PAWN | opponent(position->activeColor)]
         [position->enPassantSquare];

      if (attackers != EMPTY_BITBOARD)
      {
         /*
            dumpSquare(position->enPassantSquare);
            dumpPosition(position);
          */

         enPassantSquare = position->enPassantSquare;
      }
   }

   index = PfnIndCalcFun(tableNr, color)
      (pwhite, pblack, enPassantSquare, fInvert);

   tableValue = L_TbtProbeTable(tableNr, color, index);

#ifdef SUPPORT_SMP
#if defined TARGET_WINDOWS
   InterlockedDecrement(&tbAccessCount);
#endif

#if defined TARGET_LINUX
   pthread_spin_unlock(&lock);
#endif
#endif

   if (tableValue == bev_broken)
   {
      return TABLEBASE_ERROR;
   }

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

   fullMoves = (tableValue > 0 ?
                1 + tbbe_ssL - tableValue : bev_li0 - tableValue);

   return getMateValue(fullMoves);
}

int initializeModuleTablebase()
{
#if defined TARGET_LINUX && defined SUPPORT_SMP
   pthread_spin_init(&lock, tbAccessCount);
#endif

   if (commandlineOptions.tablebasePath != 0)
   {
      return initializeTablebase(commandlineOptions.tablebasePath);
   }
   else
   {
      return 0;
   }
}

static int testTableFinder()
{
   Variation variation;
   int tableValue;

   initializeVariation(&variation, "B6k/8/8/8/8/8/8/K6N w - - 0 1");
   tableValue = probeTablebase(&variation.singlePosition);
   assert(tableValue == 29941);

   initializeVariation(&variation, "B6k/8/8/8/8/8/8/K6N b - - 0 1");
   tableValue = probeTablebase(&variation.singlePosition);
   assert(tableValue == -29940);

   return 0;
}

static int testMateValues()
{
   Variation variation;
   int tableValue;

   initializeVariation(&variation, "R6k/8/6K1/8/8/8/8/8 b - - 0 1");
   tableValue = probeTablebase(&variation.singlePosition);
   assert(tableValue == VALUE_MATED);

   initializeVariation(&variation, "7k/8/6K1/8/8/8/8/R7 w - - 0 1");
   tableValue = probeTablebase(&variation.singlePosition);
   assert(tableValue == -VALUE_MATED - 1);

   return 0;
}

int testModuleTablebase()
{
   int result;

   if (tbAvailable == FALSE)
   {
      return 0;
   }

   if (commandlineOptions.tablebasePath == 0)
   {
      return 0;
   }

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

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

   return 0;
}

#endif
