
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	Gerbil
//
//	Copyright (c) 2001, Bruce Moreland.  All rights reserved.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	This file is part of the Gerbil chess program project.
//
//	Gerbil 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 2 of the License, or (at your option)
//	any later version.
//
//	Gerbil 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 Gerbil; if not, write to the Free Software Foundation, Inc.,
//	59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "gproto.h"
#include "winboard.h"

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

static char const s_aszModule[] = __FILE__;

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	This is the engine thread.  The following documentation is very important
//	and unless you understand it, you won't understand how any of this works.

//	Commands are sent to the engine thread by the input thread by use of
//	"VSendEngineCommand".  This function sits (maybe for a while) on an event
//	called "s_evars.heventCmdFinished".  If this is not set, it assumes
//	that the engine is working on a command, and waits.

//	The engine thread sits in a loop ("DwEngine"), processing engine commands
//	from the input thread, via use of "VProcessEngineCommand".  This function
//	waits for "s_evars.heventCmdPresent" to be set, and if it finds it,
//	it reads it and processes it, then it sets "s_evars.heventCmdFinished",
//	so the input thread can send another command.

//  If it gets a command that tells it to go think, it sets the command
//	finished event and calls "VPrThink" or "VPrAnalyze".

//	If it calls either of these, it's gone until these functions end, and that
//	could be a very, very long time.  But it is still possible to issue engine
//	commands and have them responded to.

//	How it works is that the engine is required to call "VPrCallback" every
//	once in a while, while it is thinking.

//	And what "VPrCallback" does is call into here ("VProcessEngineCommand")
//	and polls to see if there is an engine command ready, and if so it
//	executes it.

//	Many of the engine commands can be handled while the engine is thinking.
//	For those that can be handled, they are just handled normally.

//	For those that can't be handled, something special needs to be done.  The
//	current state is that we're sitting in "VProcessEngineCommand", with a
//	whole bunch of chess engine thinking context in the backtrace, and then
//	the original called "VProcessEngineComand" further back in the trace.  The
//	input thread is either off doing its own thing or it's blocked, waiting
//	for us to finish this command so it can send another one.

//	What is done is that the engine is told to abort its thinking, and then
//	the "VProcessEngineCommand" function returns.  The current command is
//	still executing, so the input thread can't talk to us.

//	The chess engine will then unwind its stack and return, and we end up
//	sitting in the original "VProcessEngine" function, which then loops back
//	up to the top and grabs another engine command, which is, not by
//	coincidence, guaranteed to be the same one it just tried to process.

//	It then processes it and sets the command finished event.

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

typedef	struct tagEVARS {
	HANDLE	heventCmdPresent;		// These are described above.
	HANDLE	heventCmdFinished;
	char	aszEngineInBuf[1024];	// Command input buffer.
	BOOL	fLegal;					// If we get an illegal "setboard"
									//  command, until we get another one
									//  we respond to every attempt to move
									//  by emitting an error message.
									// It should respond to "undo" commands
									//  and so on by emitting and error
									//  message, but I didn't architect this
									//  properly.
}	EVARS;

EVARS s_evars;

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

//	Wait until the engine is ready, and then say what you want to say.

void VSendToEngine(const char * szFmt, ...)
{
	char	aszBuf[2048];
	va_list	pvaArg;

	WaitForSingleObject(s_evars.heventCmdFinished, INFINITE);
	va_start(pvaArg, szFmt);
	vsprintf(aszBuf, szFmt, pvaArg);
	VStripWb(aszBuf);
	strcpy(s_evars.aszEngineInBuf, aszBuf);
	SetEvent(s_evars.heventCmdPresent);
}

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

BOOL FEcmdSetboard(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	char	asz[256];

	Assert(csz == 7);
	if (fThinking)
		return fTRUE;
	sprintf(asz, "%s %s %s %s %s %s",
		rgsz[1], rgsz[2], rgsz[3], rgsz[4], rgsz[5], rgsz[6]);
	if (!(s_evars.fLegal = FPrSetboard(pv, asz)))
		VSendToWinboard("tellusererror Illegal position");
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdHint(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		VPrHint(pv);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdBk(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (!fThinking)
		VPrBk(pv);
	VPrSendBookLine("");
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdThink(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz >= 3);
	Assert(csz <= 4);
	if (fThinking) {
		if ((csz < 4) || (!FPrPonderHit(pv,
			rgsz[3], atol(rgsz[1]), atol(rgsz[2]))))
			return fTRUE;
		SetEvent(s_evars.heventCmdFinished);
		return fFALSE;
	}
	if (csz > 3)
		if (!s_evars.fLegal)
			VSendToWinboard("Illegal move: %s", rgsz[3]);
		else if (!FPrAdvance(pv, rgsz[3])) {
			VSendToWinboard("Illegal move: %s", rgsz[3]);
			SetEvent(s_evars.heventCmdFinished);
			return fFALSE;
		}
	SetEvent(s_evars.heventCmdFinished);
	if (s_evars.fLegal)
		VPrThink(pv, atol(rgsz[1]), atol(rgsz[2]));
	return fFALSE;
}

BOOL FEcmdAnalyze(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz >= 0);
	if (fThinking)
		return fTRUE;
	if (csz > 1)
		if (!s_evars.fLegal)
			VSendToWinboard("Illegal move: %s", rgsz[1]);
		else if (!FPrAdvance(pv, rgsz[1])) {
			VSendToWinboard("Illegal move: %s", rgsz[1]);
			SetEvent(s_evars.heventCmdFinished);
			return fFALSE;
		}
	SetEvent(s_evars.heventCmdFinished);
	if (s_evars.fLegal)
		VPrAnalyze(pv);
	return fFALSE;
}

BOOL FEcmdAbort(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdPost(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	VPrSetPost(pv, fTRUE);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdNoPost(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	VPrSetPost(pv, fFALSE);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdMove(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz == 2);
	if (fThinking)
		return fTRUE;
	if (!s_evars.fLegal)
		VSendToWinboard("Illegal move: %s", rgsz[1]);
	else if (!FPrAdvance(pv, rgsz[1])) {
		VSendToWinboard("Illegal move: %s", rgsz[1]);
		SetEvent(s_evars.heventCmdFinished);
		return fFALSE;
	}
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdLevel(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz == 4);
	if (fThinking)
		return fTRUE;
	VPrSetTimeControl(pv, atoi(rgsz[1]), atol(rgsz[2]), atol(rgsz[3]));
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdSt(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz >= 1);
	if (fThinking)
		return fTRUE;
	VPrSetTimeControl(pv, 1, atol(rgsz[1]), 0);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdSd(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz >= 1);
	if (fThinking)
		return fTRUE;
	VPrSetFixedDepthTimeControl(pv, atoi(rgsz[1]));
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdStatus(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz >= 1);
	if (fThinking) {
		STAT_REC	sr;

		if (FPrStatus(pv, &sr))
			VSendToWinboard("stat01: %lu %I64u %d %d %d %s",
				sr.tmElapsed / 10, sr.nodes, sr.plyDepth,
				sr.cLegalMoves - sr.iMoveSearching,
				sr.cLegalMoves, sr.aszSearching);
	}
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdComputer(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	//	This is not supposed to be called while the engine is thinking, so
	//	just ignore it if the engine is thinking.
	//
	if (!fThinking)
		VPrOpponentComputer(pv);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdOppName(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	//	This is not supposed to be called while the engine is thinking, so
	//	just ignore it if the engine is thinking.
	//
	if (!fThinking) {
		while (*szCmd == ' ')
			szCmd++;
		while ((*szCmd != ' ') && (*szCmd != '\0'))
			szCmd++;
		while (*szCmd == ' ')
			szCmd++;
		VPrOpponentName(pv, szCmd);
	}
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdRating(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz == 3);
	//
	//	This is not supposed to be called while the engine is thinking, so
	//	just ignore it if the engine is thinking.
	//
	if (!fThinking)
		VPrRating(pv, atol(rgsz[1]), atol(rgsz[2]));
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdIcs(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	Assert(csz == 2);
	//
	//	This is not supposed to be called while the engine is thinking, so
	//	just ignore it if the engine is thinking.
	//
	if ((!fThinking) && (strcmp(rgsz[1], "-")))
		VPrIcs(pv, rgsz[1]);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdMovenow(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		VPrMoveNow(pv);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdDraw(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	VPrDrawOffered(pv);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdUndo(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	if (s_evars.fLegal)
		VPrUndoMove(pv);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdRemove(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	if (s_evars.fLegal) {
		VPrUndoMove(pv);
		VPrUndoMove(pv);
	}
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdPonder(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	VPrSetPonder(pv, fTRUE);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdNoponder(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	VPrSetPonder(pv, fFALSE);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

BOOL FEcmdResult(void * pv, char * szCmd,
	char * rgsz[], int csz, BOOL fThinking)
{
	if (fThinking)
		return fTRUE;
	//
	//	Skip "result".
	//
	while (*szCmd == ' ')
		szCmd++;
	while ((*szCmd != ' ') && (*szCmd != '\0'))
		szCmd++;
	//
	//	Skip 1-0 or whatever.
	//
	while (*szCmd == ' ')
		szCmd++;
	while ((*szCmd != ' ') && (*szCmd != '\0'))
		szCmd++;
	//
	//	Skip leading blanks to get to the real result
	//
	while (*szCmd == ' ')
		szCmd++;
	VPrResult(rgsz[1], szCmd);
	SetEvent(s_evars.heventCmdFinished);
	return fFALSE;
}

typedef	struct	tagECMD {
	char * sz;
	int	cszMin;
	int	cszMax;
	int (* pfn)(void * pv, char * szCmd,
		char * rgsz[], int csz, BOOL fThinking);
}	ECMD, * PECMD;

ECMD const c_argecmdEngine[] = {
	"setboard",		7,	7,		FEcmdSetboard,
	"think",		3,	4,		FEcmdThink,
	"move",			2,	2,		FEcmdMove,
	"remove",		1,	1,		FEcmdRemove,
	"abort",		1,	1,		FEcmdAbort,
	"hint",			1,	1,		FEcmdHint,
	"bk",			1,	1,		FEcmdBk,
	"analyze",		1,	2,		FEcmdAnalyze,
	"post",			1,	1,		FEcmdPost,
	"nopost",		1,	1,		FEcmdNoPost,
	"level",		4,	4,		FEcmdLevel,
	"movenow",		1,	1,		FEcmdMovenow,
	"draw",			1,	1,		FEcmdDraw,
	"undo",			1,	1,		FEcmdUndo,
	"ponder",		1,	1,		FEcmdPonder,
	"noponder",		1,	1,		FEcmdNoponder,
	"result",		1,	256,	FEcmdResult,
	"st",			2,	2,		FEcmdSt,
	"sd",			2,	2,		FEcmdSd,
	"status",		1,	1,		FEcmdStatus,
	"computer",		1,	1,		FEcmdComputer,
	"name",			2,	256,	FEcmdOppName,
	"rating",		3,	3,		FEcmdRating,
	"ics",			2,	2,		FEcmdIcs,
	NULL,
};

//	If this is called from "DwEngine", "fThinking" will be FALSE.  If it's
//	called from "VPrCallback", it will be TRUE.

void VProcessEngineCommand(void * pv, BOOL fThinking)
{
	char	aszVec[1024];
	char	aszBuf[1024];
	char *	argsz[256];
	ECMD const * pcmd;
	int	csz;

	if (WaitForSingleObject(s_evars.heventCmdPresent,
		(fThinking) ? 0 : INFINITE) == WAIT_TIMEOUT)
		return;
	//
	//	The buffer "s_evars.aszEngineInBuf" is mine, but if I send an ACK by
	//	setting "s_evars.heventCmdFinished", others are free to write all over
	//	that buffer.  There are some cases where I send an ack, then fiddle
	//	around with things some more.  So I'm going to copy the string out of
	//	the input buffer and then never deal with it again.
	//
	//	I don't know if I had bugs previously due to this, but I caught a
	//	potential new bug, and that's good enough.
	//
	strcpy(aszBuf, s_evars.aszEngineInBuf);
	strcpy(aszVec, s_evars.aszEngineInBuf);
	//
	//	Break the command into pieces.  It should not be possible to get a
	//	null command (csz == 0), but if I do, ignore it.
	//
	if (!(csz = CszVectorizeWb(aszVec, argsz)))
		return;
	//
	//	The command is broken out.  Groot through the command table and figure
	//	out which one to call.
	//
	for (pcmd = c_argecmdEngine; pcmd->sz != NULL; pcmd++) {
		if (strcmp(pcmd->sz, argsz[0]))
			continue;
		if (csz < pcmd->cszMin) {
			VErrorWb("internal error #1", aszBuf);
			break;
		}
		if (csz > pcmd->cszMax) {
			VErrorWb("internal error #2", aszBuf);
			break;
		}
		if ((*pcmd->pfn)(pv, aszBuf, argsz, csz, fThinking)) {
			VPrAbort(pv);
			SetEvent(s_evars.heventCmdPresent);
			return;
		}
		break;
	}
	if (pcmd->sz == NULL)
		VErrorWb("internal error #3", aszBuf);
}

DWORD WINAPI DwEngine(void * pv)
{
	SetEvent(s_evars.heventCmdFinished);
	for (;;)
		VProcessEngineCommand(pv, fFALSE);
	return 0;
}

BOOL FInitEngineThread(int argc, char * argv[])
{
	void * pv;
	DWORD	dw;
	HANDLE	hthread;

	s_evars.fLegal = fTRUE;
	s_evars.heventCmdPresent = CreateEvent(NULL, FALSE, FALSE, NULL);
	s_evars.heventCmdFinished = CreateEvent(NULL, FALSE, FALSE, NULL);
	if ((pv = PvPrEngineInit(argc, argv)) == NULL)
		return FALSE;
	hthread = CreateThread(NULL, 0, DwEngine, pv, 0, &dw);
	if (hthread == NULL)
		return fFALSE;
	return fTRUE;
}
