/*
 
    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 "coordination.h"
 
#include "protector.h"
 
#include "search.h"
 
#include "matesearch.h"
 
#include "io.h"
 
#include "hash.h"
 
#include <stdio.h>
 
#include <assert.h>
 
#ifndef _MSC_VER
 
#include <pthread.h>
 
#else // _MSC_VER
 
#include <process.h> // Pierre-Marie Baty -- Win32 threads support
 
__declspec(dllimport) void __stdcall Sleep (unsigned long Timeout); // Pierre-Marie Baty -- for Sleep()
 
#endif // !_MSC_VER
 
#include <time.h>
 
 
 
/* #define DEBUG_COORDINATION */
 
 
 
#ifndef _MSC_VER
 
#define THREAD_RETURN_TYPE void * // Pierre-Marie Baty -- multiplatform threads support
 
#define INVALID_THREAD_ID 0 // Pierre-Marie Baty -- multiplatform threads support
 
pthread_t searchThread[MAX_THREADS];
 
pthread_t timer;
 
static pthread_mutex_t guiSearchMutex = PTHREAD_MUTEX_INITIALIZER;
 
#else // _MSC_VER
 
#define THREAD_RETURN_TYPE unsigned int // Pierre-Marie Baty -- Win32 threads support
 
#define INVALID_THREAD_ID -1 // Pierre-Marie Baty -- Win32 threads support
 
uintptr_t searchThread[MAX_THREADS]; // Pierre-Marie Baty -- Win32 threads support
 
uintptr_t timer; // Pierre-Marie Baty -- Win32 threads support
 
static bool guiSearchMutex = false; // Pierre-Marie Baty -- Win32 threads support
 
bool terminate_timer = false; // Pierre-Marie Baty -- Win32 threads support
 
#endif // !_MSC_VER
 
long searchThreadId[MAX_THREADS];
 
 
 
static int numThreads = 1;
 
static SearchTask dummyTask;
 
static SearchTask *currentTask = &dummyTask;
 
static Variation variations[MAX_THREADS];
 
static Hashtable sharedHashtable;
 
static PawnHashInfo pawnHashtable[MAX_THREADS][PAWN_HASHTABLE_SIZE];
 
 
 
Hashtable *getSharedHashtable()
 
{
 
   return &sharedHashtable;
 
}
 
 
 
int setNumberOfThreads(int _numThreads)
 
{
 
   numThreads = max(1, min(MAX_THREADS, _numThreads));
 
 
 
   return numThreads;
 
}
 
 
 
int getNumberOfThreads()
 
{
 
   return numThreads;
 
}
 
 
 
UINT64 getNodeCount(void)
 
{
 
   int threadCount;
 
   UINT64 sum = 0;
 
 
 
   for (threadCount = 0; threadCount < numThreads; threadCount++)
 
   {
 
      sum += variations[threadCount].nodes;
 
   }
 
 
 
   return sum;
 
}
 
 
 
Variation *getCurrentVariation()
 
{
 
   return &variations[0];
 
}
 
 
 
void getGuiSearchMutex(void)
 
{
 
#ifdef DEBUG_COORDINATION
 
   logDebug("aquiring search lock...\n");
 
#endif
 
 
 
#ifndef _MSC_VER
 
   pthread_mutex_lock(&guiSearchMutex);
 
#else // _MSC_VER
 
   while (guiSearchMutex)
 
      Sleep (10); // allow context switching
 
   guiSearchMutex = true; // Pierre-Marie Baty -- mutex support on Win32
 
#endif // !_MSC_VER
 
 
 
#ifdef DEBUG_COORDINATION
 
   logDebug("search lock aquired...\n");
 
#endif
 
}
 
 
 
void releaseGuiSearchMutex(void)
 
{
 
#ifndef _MSC_VER
 
   pthread_mutex_unlock(&guiSearchMutex);
 
#else // _MSC_VER
 
   guiSearchMutex = false; // Pierre-Marie Baty -- mutex support on Win32
 
#endif // !_MSC_VER
 
 
 
#ifdef DEBUG_COORDINATION
 
   logDebug("search lock released...\n");
 
#endif
 
}
 
 
 
static int startSearch(Variation * currentVariation)
 
{
 
   currentVariation->searchStatus = SEARCH_STATUS_RUNNING;
 
 
 
#ifdef DEBUG_COORDINATION
 
   logDebug("Search with thread #%d started.\n",
 
            currentVariation->threadNumber);
 
#endif
 
 
 
   switch (currentTask->type)
 
   {
 
   case TASKTYPE_BEST_MOVE:
 
      currentTask->bestMove = search(currentVariation, NULL);
 
      break;
 
 
 
   case TASKTYPE_TEST_BEST_MOVE:
 
      currentTask->bestMove =
 
         search(currentVariation, ¤tTask->solutions);
 
      break;
 
 
 
   case TASKTYPE_MATE_IN_N:
 
      searchForMate(currentVariation,
 
                    ¤tTask->calculatedSolutions,
 
                    currentTask->numberOfMoves);
 
      break;
 
 
 
   case TASKTYPE_TEST_MATE_IN_N:
 
      searchForMate(currentVariation,
 
                    ¤tTask->calculatedSolutions,
 
                    currentTask->numberOfMoves);
 
      break;
 
 
 
   default:
 
      break;
 
   }
 
 
 
   currentTask->nodes = getNodeCount();
 
 
 
   if (currentVariation->threadNumber == 0)
 
   {
 
      int threadCount;
 
 
 
      for (threadCount = 1; threadCount < numThreads; threadCount++)
 
      {
 
         variations[threadCount].terminate = TRUE;
 
      }
 
 
 
#ifndef _MSC_VER
 
      pthread_cancel(timer);
 
#else // _MSC_VER
 
      terminate_timer = true; // Pierre-Marie Baty -- Win32 threads support
 
#endif // !_MSC_VER
 
   }
 
 
 
#ifdef DEBUG_COORDINATION
 
   logDebug("Search thread #%d terminated.\n",
 
            currentVariation->threadNumber);
 
#endif
 
 
 
   currentVariation->searchStatus = SEARCH_STATUS_FINISHED;
 
 
 
   return 0;
 
}
 
 
 
long getElapsedTime()
 
{
 
   return getTimestamp() - variations[0].startTime;
 
}
 
 
 
static THREAD_RETURN_TYPE executeSearch(void *arg)
 
{
 
   Variation *currentVariation = arg;
 
 
 
#ifdef _MSC_VER
 
   currentVariation->finished = false; // Pierre-Marie Baty -- Win32 thread control
 
#endif // _MSC_VER
 
 
 
   startSearch(currentVariation);
 
 
 
#ifdef _MSC_VER
 
   currentVariation->finished = true; // Pierre-Marie Baty -- Win32 thread control
 
#endif // _MSC_VER
 
   return 0;
 
}
 
 
 
static THREAD_RETURN_TYPE watchTime(void *arg)
 
{
 
   Variation *currentVariation = arg;
 
   long timeLimit = currentVariation->timeLimit;
 
   struct timespec requested;// , remaining; // Pierre-Marie Baty -- unused variable
 
   int result;
 
 
 
   requested.tv_sec = timeLimit / 1000;
 
   requested.tv_nsec = 1000000 * (timeLimit - 1000 * (long)requested.tv_sec); // Pierre-Marie Baty -- added type cast
 
 
 
   /* logReport("### Timer thread working sec=%ld nsec=%ld ###\n",
 
      requested.tv_sec, requested.tv_nsec); */
 
#ifndef _MSC_VER
 
   result = nanosleep(&requested, &remaining);
 
   if (result != -1)
 
#else // _MSC_VER
 
   result 
= (clock () * 1000 / CLOCKS_PER_SEC
); // in milliseconds 
   while (!terminate_timer 
&& ((clock () * 1000 / CLOCKS_PER_SEC
) < result 
+ timeLimit
))  
      Sleep (10); // allow context switching if necessary
 
   if (!terminate_timer)
 
#endif // !_MSC_VER
 
   {
 
      getGuiSearchMutex();
 
      prepareSearchAbort();
 
      releaseGuiSearchMutex();
 
   }
 
 
 
   return 0;
 
}
 
 
 
void startTimerThread(SearchTask * task)
 
{
 
   if (task->variation->timeLimit > 0 && task->variation->ponderMode == FALSE)
 
   {
 
#ifndef _MSC_VER
 
      if (pthread_create(&timer, NULL, &watchTime, task->variation) == 0)
 
#else // _MSC_VER
 
      terminate_timer = false; // Pierre-Marie Baty -- Win32 threads support
 
      if ((timer = _beginthread (watchTime, 0, task->variation)) != -1) // Pierre-Marie Baty -- Win32 threads support
 
#endif // !_MSC_VER
 
      {
 
#ifdef DEBUG_COORDINATION
 
         logDebug("Timer thread started.\n");
 
#endif
 
      }
 
      else
 
      {
 
         logDebug("### Timer thread could not be started. ###\n");
 
 
 
      }
 
   }
 
}
 
 
 
void scheduleTask(SearchTask * task)
 
{
 
   const unsigned long startTime = getTimestamp();
 
   int threadCount;
 
 
 
   sharedHashtable.entriesUsed = 0;
 
 
 
   startTimerThread(task);
 
 
 
   for (threadCount = 0; threadCount < numThreads; threadCount++)
 
   {
 
      Variation *currentVariation = &variations[threadCount];
 
 
 
      currentTask = task;
 
      *currentVariation = *(currentTask->variation);
 
      currentVariation->searchStatus = SEARCH_STATUS_TERMINATE;
 
      currentVariation->bestBaseMove = NO_MOVE;
 
      currentVariation->terminate = FALSE;
 
      currentVariation->pawnHashtable = &(pawnHashtable[threadCount][0]);
 
      currentVariation->kingsafetyHashtable =
 
         &(kingSafetyHashtable[threadCount][0]);
 
      currentVariation->threadNumber = threadCount;
 
      currentVariation->startTime = startTime;
 
 
 
#ifndef _MSC_VER
 
      if (pthread_create(&searchThread[threadCount], NULL,
 
                         &executeSearch, currentVariation) == 0)
 
#else // _MSC_VER
 
      if ((searchThread[threadCount] = _beginthread(executeSearch, 0, currentVariation)) != -1) // Pierre-Marie Baty -- Win32 threads support
 
#endif // !_MSC_VER
 
      {
 
#ifdef DEBUG_COORDINATION
 
         logDebug("Search thread #%d created.\n", threadCount);
 
#endif
 
      }
 
      else
 
      {
 
         logDebug("### Search thread #%d could not be started. ###\n",
 
                  threadCount);
 
 
 
      }
 
   }
 
}
 
 
 
void waitForSearchTermination(void)
 
{
 
   int threadCount;
 
   bool finished;
 
   int count = 0;
 
 
 
   do
 
   {
 
      finished = TRUE;
 
 
 
      if (count > 1000)
 
      {
 
         logDebug("waiting for search termination.\n");
 
         count = 0;
 
      }
 
 
 
      for (threadCount = 0; threadCount < numThreads; threadCount++)
 
      {
 
         Variation *currentVariation = &variations[threadCount];
 
 
 
         if (currentVariation->searchStatus != SEARCH_STATUS_FINISHED)
 
         {
 
            if (searchThread[threadCount] != INVALID_THREAD_ID)
 
            {
 
#ifndef _MSC_VER
 
               const int result = pthread_join (searchThread[threadCount], 0);
 
 
 
               if (result == 0)
 
               {
 
                  searchThread[threadCount] = INVALID_THREAD_ID;
 
               }
 
               else
 
               {
 
                  finished = FALSE;
 
               }
 
#else // _MSC_VER
 
               while (!currentVariation->finished)
 
                  Sleep (10); // allow context switching
 
 
 
               searchThread[threadCount] = INVALID_THREAD_ID;
 
#endif // !_MSC_VER
 
            }
 
         }
 
         else
 
         {
 
            searchThread[threadCount] = INVALID_THREAD_ID;
 
         }
 
 
 
#ifdef DEBUG_COORDINATION
 
         logDebug("Task %d finished.\n", threadCount);
 
#endif
 
      }
 
 
 
      count++;
 
   }
 
   while (finished == FALSE);
 
}
 
 
 
void completeTask(SearchTask * task)
 
{
 
   scheduleTask(task);
 
 
 
#ifdef DEBUG_COORDINATION
 
   logDebug("Task scheduled. Waiting for completion.\n");
 
#endif
 
 
 
   waitForSearchTermination();
 
}
 
 
 
void prepareSearchAbort(void)
 
{
 
   int threadCount;
 
 
 
   for (threadCount = 0; threadCount < numThreads; threadCount++)
 
   {
 
      variations[threadCount].terminate = TRUE;
 
   }
 
}
 
 
 
void unsetPonderMode(void)
 
{
 
   int threadCount;
 
 
 
   for (threadCount = 0; threadCount < numThreads; threadCount++)
 
   {
 
      Variation *currentVariation = &variations[threadCount];
 
 
 
      currentVariation->ponderMode = FALSE;
 
   }
 
}
 
 
 
/*void setTimeLimit(unsigned long timeTarget, unsigned long timeLimit)
 
{
 
   int threadCount;
 
 
 
   for (threadCount = 0; threadCount < numThreads; threadCount++)
 
   {
 
      Variation *currentVariation = &variations[threadCount];
 
 
 
      currentVariation->timeTarget = timeTarget;
 
      currentVariation->timeLimit = timeLimit;
 
   }
 
}*/ // Pierre-Marie Baty -- unused function?
 
 
 
void setHashtableSizeInMb(unsigned int size)
 
{
 
   UINT64 tablesize = 1024 * 1024 * (UINT64) size;
 
 
 
   setHashtableSize(&sharedHashtable, tablesize);
 
   resetHashtable(&sharedHashtable);
 
}
 
 
 
int initializeModuleCoordination()
 
{
 
   int threadCount;
 
 
 
   initializeHashtable(&sharedHashtable);
 
   setHashtableSize(&sharedHashtable, 16 * 1024 * 1024);
 
   resetHashtable(&sharedHashtable);
 
 
 
   for (threadCount = 0; threadCount < MAX_THREADS; threadCount++)
 
   {
 
      Variation *currentVariation = &variations[threadCount];
 
 
 
      currentVariation->searchStatus = SEARCH_STATUS_FINISHED;
 
   }
 
 
 
   return 0;
 
}
 
 
 
int testModuleCoordination()
 
{
 
   return 0;
 
}