// Slow Chess 2.95
// by Jonathan Kreuzer
// copyright 2004

// Includes ----------------------
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <process.h>

#include "data.h"
#include "gui.h"
#include "search.h"
#include "moves.h"
#include "transcript.h"
#include "oBook.h"
#include "evaluate.h"

void wbMainLoop ( );
void wbProcessCommand ( char * sBuf );
void  FAR ThinkingThread( void FAR *parm);

COpeningBook *pNewBook = NULL;

// Globals -------------------------
int hThread; 
int Minutes = 5, Seconds = 5, MaxAhead = 28, ELO = 0;
unsigned int g_nMaxNodes = 0;
int bShowLastMove;
int g_bAlwaysWait = FALSE;
int g_bTimeUp = FALSE, g_bGameStarted = FALSE, g_LastColor = BOTH;

int g_bWinBoard = FALSE;
int bExperiment = FALSE;
int bUseNull = TRUE;
int bPonder = FALSE;
int bDisplayInfo = TRUE;
int bHashing = TRUE;
int bEvalWhite = TRUE;
int bDoNull = TRUE;

char sUserName [66], sOppName [66];

int g_bNetGame = FALSE, g_bDebug = FALSE;
char g_cNetColor = NONE;
int g_GameResult;
clock_t g_startTime;
int g_ponder = 0, g_bAutoFlip = TRUE;
int g_ponderSrc, g_ponderDst;
SBoard g_PonderBoard;

SBoard g_StartBoard;
int flipboard = 0;
char *g_sSlow = "SlowChess 2.96";
int g_bAnalysisMode = 0;

//
// Default Settings struct
//
SwbSettings::SwbSettings ( )
		{
		bResign = TRUE;
		bDraw = TRUE;
		bLog = TRUE;
		bLogEvals = FALSE;
		bLearn = TRUE;
		strcpy (sBook, "TestBook.scb");
		}

void SwbSettings::Save (struct _iobuf *FP)
		{
		if (FP!=NULL)
			{
			fprintf (FP, "%d\n%d\n%d\n%s\n%d\n", bResign, bDraw, bLog, sBook, bLearn);
			}
		}

void SwbSettings::Load (struct _iobuf *FP)
		{
		if (FP!=NULL)
			{
			fscanf (FP, "%d\n%d\n%d\n%s\n%d\n", &bResign, &bDraw, &bLog, sBook, &bLearn);
			}
		}
SwbSettings SCsettings, WBDefaults, GUIDefaults;

//
// Defaults Class
//
struct SDefaults
	{
	SDefaults ()
		{
		 strcpy (sName, "Human");
		 nMinutes = 5;
		 nSeconds = 5;
		 nMaxPly = 28;
		 MaxAhead = 28;
		 nELO = 0;
		 bPonder = FALSE;
		 bDisplayInfo = TRUE;
		 nHashBits = 21;
		 bHashing = TRUE;
		 bExtendChecks = TRUE;
		 bShowLastMove = TRUE;
		 bEvalWhite = TRUE;
		 bDoNull = TRUE;
		 g_bAlwaysWait = FALSE;
		}

	void GetCurDir ()
	{
	GetCurrentDirectory (4195, sCurDir );
	}

	void SetCurDir ()
	{
	 SetCurrentDirectory ( sCurDir );
	}

	void SetFromDefaults ( )
		{
		strcpy (sUserName, sName);
		Minutes = nMinutes;
		Seconds = nSeconds;
		ELO		= nELO;
		}

	void SetTime ( )
		{
		nMinutes = Minutes;
		nSeconds = Seconds;
		}

	char sCurDir[4196];
	char sName[66];
	char sIP[66];
	int nHashBits;
	int nMinutes;
	int nSeconds;
	int nMaxPly;
	int nMaxNodes;
	int nELO;
	int bExtendChecks;
	};

SDefaults Defaults;

// Globals --------------------------------------------
SBoard GameBoard;

int g_nBlackTime = 0, g_nWhiteTime = 0;
int nSecsPerMove = 0;

/* =====================================================================
 * Load defaults, init boards & hash & opening book, then Start the Thinking Thread
   ======================================================================*/
HANDLE hAction, hEngineReady, hEngineInactive;

void InitEngine ( )
{
	// Initialize Search Boards & Square Values
	InitEndgameTables ( );
	InitSquareArray();
    memset (&BoardStack[0], INVALID, MAXDEPTH * sizeof(SBoard)); // invalid squares
    memset (&SearchBoard, INVALID, sizeof(SBoard));

	// Hashing
    Create_HashFunction ( );
	SetHashSizeBits ( Defaults.nHashBits ); 

	// Opening Book (uses hashing)
	pNewBook = new COpeningBook;
	pNewBook->Load ( SCsettings.sBook );

	// Start the thinking Thread
	_beginthread (ThinkingThread, NULL, NULL);
	hAction = CreateEvent(NULL, FALSE, FALSE, NULL);
	hEngineReady = CreateEvent (NULL, TRUE, FALSE, NULL);
	hEngineInactive = CreateEvent (NULL, TRUE, FALSE, NULL);
}

/* ------------------------------------
 * Console Mode, comment out WinMain and put this in to make this a console .exe for winboard.
 * ------------------------------------- */
int main(int argc, char **argv)
{
	Defaults.SetFromDefaults ( );
	Defaults.GetCurDir ();

	// If it's winboard, we just go to the winboard loop and exit when done
	SCsettings = WBDefaults;
	g_bWinBoard = TRUE;
	InitEngine ( );

	for (int i = 0; i < argc; i++)
		{
		char *sHash = strstr (argv[ i ], "-H");
		if (sHash) 
			{
			 unsigned int nMegs;
			 sscanf (&sHash[2], "%d", &nMegs);
			 SetHashMB (nMegs);
			}
		}

	wbMainLoop ( );
	return (0);
} 

//
//
void ResetClock ()
	{
	g_nBlackTime = 0;
	g_nWhiteTime = 0;
	g_startTime = clock ();
	g_bTimeUp = FALSE;
	}

// -------------------------
// Reset everything needed to begin a new game
// -------------------------
void NewGame (  )
{
	StopThinking ();
	Setup_StartBoard (GameBoard, 1);
	ClearBoard (g_PonderBoard);
	g_StartBoard = GameBoard;

	ClearTranscript ( NULL );
	g_GameResult = NONE;
	ResetClock ();
}

// -----------------------------------------------------
// Set time to think for this move from time left and how many moves have been made
// nMoves = nMoves in a time period   nSecs = Increment per move
// fCompTm = Time remaining in seconds
//  ----------------------------------------------------
void SetThinkTime (float fCompTm, int nMoves, int nSecs )
{
	fCompTm -= .5f;
	int nMovesPlayed = GameBoard.GameTime/2;

	if (nMoves > 0) 
		{
		nMovesPlayed = nMovesPlayed % nMoves;
		float fMovesLeft = float (nMoves - nMovesPlayed);
		Thinktime = fCompTm / ( fMovesLeft+1.0f );
		if (GameBoard.GameTime < 44 && fMovesLeft > 18) 
			{
			fMovesLeft -= 10;
			Thinktime = (Thinktime + fCompTm / ( fMovesLeft+1.0f )) *.5f;
			}
		}
	else
		{
		if (nSecs == 0 && fCompTm < 20.0f) fCompTm-=.5f;
		if (GameBoard.GameTime < 24) Thinktime = fCompTm / (45 - nMovesPlayed);
		else if (GameBoard.GameTime < 60) Thinktime = fCompTm / (56 - nMovesPlayed);
		else if (GameBoard.GameTime < 132) Thinktime = fCompTm / (88 - nMovesPlayed);
		else if (GameBoard.GameTime < 200) Thinktime = fCompTm / (118 - nMovesPlayed);
		else Thinktime = fCompTm / 35;
		Thinktime += (float)nSecs;
		}

	SetPanicTime ( fCompTm );
}

//
// Ponder Helper function
//
int GetPonderMove (int &nMove )
{
 HashEntry TEntry;
 if (GetTTEntry ( GameBoard, TEntry))
	{
	 nMove = TEntry.bestmove;
	 return TRUE;
	}
 if (PV[0][0] > 1) 
	{nMove = PV [0][ 2 ];
	 return TRUE;
	}
 return FALSE;
}

// ------------
// Do the next move from the PV,
// if it's a legal move, store the board as the ponder board and start thinking on that board.
// (After doing the real move, the board is compared to the ponder board to see if it's a ponder hit.
// and g_ponder is set accordingly.)
// ------------
void PonderNextMove(  ) 
	{
	 int nTemp, nTemp2;
	 int nMove = NO_BEST;
	 SBoard PonderBoard = GameBoard;

	 GetPonderMove ( nMove );

	 int src = MSrc( nMove);
	 int dst = MDst( nMove);

	 g_ponderSrc = src;
	 g_ponderDst = dst;

	 upgrade = WQUEEN;

	 if ( nMove!=NO_BEST && DoMoveXY ( PonderBoard, (src-22)%10, (src-22)/10, (dst-22)%10, (dst-22)/10, PonderBoard.SideToMove, NULL, FALSE, NULL, nTemp, nTemp2) == 1)
		{
		g_PonderBoard = PonderBoard;
		g_ponder = 1;
		thinking = 2;
        CompMove (PonderBoard,  2, MaxAhead);

		if (g_ponder == 1 || stopthinking > 0) 
			{
			g_ponder = -1;
			}
		else if (g_ponder == 2) // Opponent played pondered move
			{
			g_ponder = 0;
			GameBoard = PonderBoard;
			SetEvent (hAction);
			}
		else if (g_ponder == 3) // Pondered wrong Move
			{	
			 SetEvent (hAction);
			}
       	}
	 else 
		{
		 g_ponder = -1;
		}
	
	g_ponder = 0;
	thinking = 0;
	ClearBoard (g_PonderBoard);
    } 

// =================================================================================\
// Threaded Function (for multitasking while the computer thinks)
// =================================================================================/
int g_nFlags = 0;

void  FAR ThinkingThread( void FAR *parm) 
{
parm = parm;

	// Think of Move
     while (1==1)
	 {
	 SetEvent (hEngineReady);
	 SetEvent (hEngineInactive);
	 WaitForSingleObject (hAction, INFINITE);
	 ResetEvent ( hEngineInactive);
	 ResetEvent ( hEngineReady ); //Don't take input until everything is ready

	 switch (g_nFlags)
		{
		// Move & Ponder
		case 0:
		if (g_ponder == 0)
			{
			thinking = 1;
			while ( (PlayerCOLOR == NONE || PlayerCOLOR!=GameBoard.SideToMove) && g_GameResult == NONE && PlayerCOLOR !=BOTH )
				{
				CompMove (GameBoard, 0, MaxAhead); 
				}

			thinking = 0;
			g_ponder = 0;
			if (PlayerCOLOR == NONE) PlayerCOLOR = BOTH;
			}	

		if ( g_ponder == 0 && bPonder == TRUE && stopthinking != 3 && thinking == 0)
			{
			// Start to Ponder?
			if ((PlayerCOLOR == WHITE || PlayerCOLOR == BLACK) && g_GameResult == NONE ) 
				PonderNextMove ( );
			}
		break;

		// Analyze
		case 1:
		thinking = 1;
		g_ponder = 1;
        CompMove (GameBoard, 1, 34);
		thinking = 0;
        g_ponder = 0;
		break;

		// UCI pondering
		case 4: 
		g_PonderBoard = GameBoard;
		g_ponder = 1;
		thinking = 2;
        CompMove (GameBoard,  2, MaxAhead);
		thinking = 0;
        g_ponder = 0;
		break;
		}
	}
	 
_endthread();
} 

// ----------------
//
// ----------------
void ThinkOfMove ( int nFlags )
{
  if (g_ponder > 0) return;
  if (thinking != 0 || g_bNetGame == TRUE)  return;

  if (nFlags == 0 && PlayerCOLOR != BOTH) 
	{ 
	  if (g_bGameStarted == FALSE)
		{
		if (g_bWinBoard == FALSE && g_bNetGame == FALSE) ResetClock ();
		g_bGameStarted = TRUE;
		}
	}

  g_nFlags = nFlags;
  SetEvent (hAction);

}

void AnalyzeBoard ( )
{
	WaitForSingleObject ( hEngineReady, 1000 );
	stopthinking = 3;
	g_nFlags = 1;
	SetEvent (hAction);
}

void StopThinking ( )
{
 WaitForSingleObject ( hEngineReady, 1000 );
 stopthinking = 3;
 WaitForSingleObject ( hEngineInactive, 1000 );
}

/* ===============================================================
 * The Player has entered a move? move this to engine thread.

 ================================================================ */ 
int PlayerMove (int sx, int sy, int dx, int dy, int piece )
{
 int src=0, dst=0;

 // Is engine ready to receive a move?
 WaitForSingleObject ( hEngineReady, 1000 );

 if (DoMoveXY( GameBoard, sx, sy, dx , dy , GameBoard.SideToMove, piece, TRUE, NULL, src, dst) == 1)
         {
		 // GUI Stuff
		  if (g_bGameStarted == FALSE)
			{
			if (g_bWinBoard == FALSE && g_bNetGame == FALSE) ResetClock (); 
			g_bGameStarted = TRUE;
			}
		  g_GameResult = NONE;
		
		  // Move Stuff
		  if (g_ponder == 1)
				{
				if (memcmp ( &GameBoard, &g_PonderBoard, sizeof (SBoard)) == 0)
					{
					 g_ponder = 2; // Did the move pondered
					 ExtendThinkTime ( );
					}
				else g_ponder = 3; // Didn't do the move pondered
				}

		  if ( CheckGameOver ( GameBoard, FALSE, g_GameResult) == FALSE)
				{
			    if (g_bNetGame == FALSE)
					{
					if (g_bAnalysisMode == 2) AnalyzeBoard ( );
						else ThinkOfMove ( 0 );
					}
				}
		  else
			{
			wbTestSendResult ( GameBoard );			
			}

		  return TRUE;
          }
 return FALSE;
}

