
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	Epd2Wb
//
//	Copyright (c) 2001, Bruce Moreland.  All rights reserved.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	This file is part of the Epd2Wb EPD/Winboard harness project.
//
//	Epd2Wb 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.
//
//	Epd2Wb 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 Epd2Wb; if not, write to the Free Software Foundation, Inc.,
//	59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	Parts of this module have been adapted from Tim Mann's Winboard project,
//	and are used with permission.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

#include "epd2wb.h"
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>

//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	POSIX port (Linux, macOS, *BSD) additions.  The original code talked
//	to the engine subprocess and its own worker thread entirely through
//	Win32 calls (CreateProcess/CreatePipe/ReadFile/WriteFile/CreateThread/
//	GetTickCount/FormatMessage).  Those are replaced below with the
//	equivalent POSIX facilities (fork+exec+pipes, pthreads,
//	clock_gettime, strerror).  Event-based synchronization
//	(CreateEvent/SetEvent/WaitForSingleObject) is emulated in
//	"win32compat.c" and left untouched everywhere else in this file.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-

#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>

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

//	Here's how this thing works.

//	INITIALIZATION
//
//	The engine is started, a few commands are sent ("xboard" and
//	"protover 2"), and this harness drops into "FEATURE_TIMEOUT" mode.
//	During this period, the harness is waiting for "feature" commands from the
//	engine.  It will wait a total of five seconds, while listening to whatever
//	the engine has to say.
//
//	After two seconds, the harness assumes the engine is done sending feature
//	commands, drops into "MODE_NORMAL" and sends the engine a few more initial
//	commands ("VPrepEngine").
//
//	It's possible that during the FEATURE_TIMEOUT period, the engine could
//	send a "feature done=0" command.  This is used to indicate that feature
//	commands will be coming from the engine until further notice.  If this
//	happens, the harness goes into "FEATURE_NOTIMEOUT" mode, which is like
//	the normal timeout mode except that it will stay in this mode until it
//	gets a "feature done=1" command, which is handled exactly like a normal
//	timeout.
//
//	"Feature" commands can be received at any time during execution, but will
//	most likely be received at boot.  Some of these have major influence upon
//	how the program operates.
//
//	If I am testing an old engine, it won't send any feature commands, and
//	might ignore me or send random nonsense during the intial wait period.
//	The program can handle old engines, or so I hope.
//
//	HANDLING AN EPD STRING
//
//	Once the harness has entered "NORMAL" mode, either explicitly or via
//	timeout, and has sent the initial commands to the engine, the first EPD
//	string is read from the EPD file.  The harness will process the string,
//	send a "new", put the engine into "force" mode, and depending upon
//	whether or not the engine has told us it can handle "ping":
//
//	1)	If the engine cannot handle "ping", it will sit there for a few
//		seconds (configurable from the command line via "-w"), ignoring all
//		engine output.  This gives the engine time to stop sending analysis.
//		It will then enter "TESTING" mode.  This wait is undesirable but
//		difficult to avoid.
//
//	2)	If the engine can handle "ping", we will send a ping and enter
//		"WAITING" mode.  We are going to ignore everything sent by the engine
//		until it sends a corresponding "pong", which if common sense dictates,
//		will be after the previously sent "new" command takes effect, so the
//		engine won't be sending anymore analysis.
//
//	Now, the task is to send the position.  We will use "setboard" if the
//	engine has told us it can handle it, otherwise we will send the position
//	in pieces via use of the "edit" command.  Note that in this case, castling
//	flags and en-passant square are not sent, and the engine can set them how
//	it likes.
//
//	There are enhancements to "edit" specified by Chessbase, but I won't
//	handle these for now.  These would let us set the en-passant square and
//	castling flags, which are otherwise simply set to what are hoped to be
//	sensible defaults.
//
//	If the engine can handle "analyze", we will now send that command.  If it
//	can't, we'll tell it that it has a really long time think, and put it into
//	normal search mode (with "go").
//
//	If we sent a ping, we'll now ignore all output from the engine until we
//	receive the corresponding "pong".  Once it has received the "pong", it
//	will enter "TESTING" mode and start collecting analysis.
//
//	Once the test period is complete, I'll output some results and move on to
//	the next test.
//
//	If I wasn't able to use "analyze" mode, it's possible that the engine
//	might make a move.  That's fine, although weird things might happen if
//	the engine tries to ponder, which is why "easy" is sent so often.
//
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	I've added another "desperation" switch.  If the "-t" switch is passed,
//	"s_ivars.fUseSt" will be set to TRUE.  If this is the case, the program
//	will try to use "st" and "time" to get the engine to move.  This will be
//	the case even if the engine has "analyze" -- "-t" will override "analyze"
//	and turn it off.
//
//	This works kind of vaguely.  If you want more information, look in
//	EPD2WB.TXT.

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

#define	modeFEATURE_TIMEOUT		0
#define	modeFEATURE_NOTIMEOUT	1
#define	modeWAITING				2
#define	modeTESTING				3

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

//	A struct containing info about a child process, in particular the engine.
//	This stuff was adapted (with permission) from similar code in Tim Mann's
//	Winboard.

typedef struct tagCHILDPROC {
	pid_t pid;		// PID of the engine process.
	int hTo;		// Write end of the pipe feeding the engine's stdin.
	int hFrom;		// Read end of the pipe reading the engine's stdout.
}	CHILDPROC, * PCHILDPROC;

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

typedef	unsigned long	TM;

#define	isolMAX		1000
#define	cbINPUT_BUF	4096

//	Global variables.

typedef	struct	tagIVARS {
	HANDLE	heventStdinReady;	// Events used by the asynchronous engine
	HANDLE	heventStdinAck;		//  input system.
	char	aszEngine[256];		// Engine being used or the test.
	char	aszSuite[256];		// Engine being used or the test.
	char	aszFen[256];		// EPD FEN for current test.
	char	aszId[256];			// EPD "id" for current test.
	char	aszAnswer[256];		// EPD "bm" for current test, this is double
								//  null-terminated:
								//  <answer1></0><answer2></0></0>
	char	aszAvoid[256];		// EPD "am" for current test, this is double
								//  null-terminated:
								//  <answer1></0><answer2></0></0>
	int	movStart;				// EPD doesn't return the full FEN, it drops
								//  the last two fields.  EPD defines fields
								//  specifically for these, and I have
								//  implemented those fields.  If they aren't
								//  present I use sensible defaults ("0 1").
	int	plyFifty;				// EPD fifty-move counter, see my comments
								//  about "movStart".
	int	mode;					// Harness mode, see definitiions above.
	CHILDPROC	cp;				// The engine.
	FILE *	pfI;				// File pointer of the EPD file.
	int	cPing;					// Ping counter.  I'll increment this, ping
								//  the engine with it, then wait for a
								//  corresponding pong with the same argument.
	BOOL	fCorrectAnswer;		// TRUE if the last analysis line I got for
								//  this test indicated a correct answer.
	int	cSolved;				// Number of positions solved.
	int	cFailed;				// Number of positions not solved.
	int	cError;					// Number of bogus FEN's found.
	int	argsol[isolMAX];		// This is an array of the number of answers
								//  found in array index seconds, rounded
								//  down.  So element zero is the number of
								//  answers found in between 0 an 999 ms.
	char	aszInBuf[			// Input buffer from the engine.
		cbINPUT_BUF];
	int	cSkip;					// Skip count, as documented in "epd2wb.txt".
	int	cLine;					// Line of the EPD file I'm processing.
	BOOL	fError;				// This is true if the EPD FEN seems to be
								//  bogus.
	BOOL	fEngineStarted;		// TRUE if I started the chess engine.
	BOOL	fPing;				// TRUE if I can use "ping".
	BOOL	fSetboard;			// TRUE if I can use "setboard".
	BOOL	fAnalyze;			// TRUE if I can use "analyze".
	BOOL	fColor;				// TRUE if I have to endure "color".
	BOOL	fUsermove;			// TRUE if I will deal with "usermove".
	BOOL	fDump;				// TRUE if I'm going to dump everything I get
								//  input and output from/to the engine.
	BOOL	fUseSt;				// TRUE if I'm going to try to use the "st"
								//  command to tell the engine how long to
								//  think.
	BOOL	fFindInfo;			// UNDONE BUG!
	BOOL	fQuit;				// After the test is done, I send "quit" to
								//  the engine and wait two seconds.  I might
								//  try to read from the engine during this
								//  time, and get an error message.  If I do,
								//  I will just quit silently without printing
								//  it.  This variable controls that behavior.
	TM	tmEnd;					// Time the current wait period is over,
								//  either the feature wait period, or the
								//  current test.
	TM	tmPerMove;				// Amount of time in milliseconds I am going
								//  to spend per test.
	TM	tmFoundIn;				// If "fCorrectAnswer", this is how many
								//  milliseconds, as reported by the engine,
								//  it took to find the correct answer.  This
								//  is "find and hold", meaning if it changes
								//  its mind later, this value is reset.
	TM	tmWait;					// If the engine cannot process "ping", the
								//  utility will wait this many seconds
								//  between tests, ignoring all input from
								//  the engine.
	TM	tmStart;				// For engines that accept pings, this is the
								//  time the engine acknowledged the ping that
								//  I sent to indicate that I'm listening to
								//  the engine.  For engines that don't accept
								//  pings, this is the time I told the program
								//  to go.
	PLANG	plang;				// The language I'm using (default English).
	PLANG	plangEnglish;		// Pointer to english language table.
}	IVARS;

IVARS	s_ivars;	// The one instance of this struct.

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

//	Time is measured in milliseconds.  This returns time since some
//	unspecified starting point, using a monotonic clock so it can't be
//	upset by the wall clock being adjusted (this is the POSIX analog of
//	GetTickCount()).

unsigned TmNow(void)
{
	struct timespec	ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (unsigned)((TM)ts.tv_sec * 1000 + (TM)ts.tv_nsec / 1000000);
}

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

#define	cbLINE	80	// Assumed width of the output console.

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

static char const s_aszModule[] = __FILE__;

void VAssertFailed(const char * szMod, int iLine)
{
	printf("Assert Failed: %s+%d\n", szMod, iLine);
	exit(1);
}

#define	Assert(cond)		if (!(cond)) VAssertFailed(s_aszModule, __LINE__)

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

//	This converts the last system error (errno) into a text string,
//	displays it along with some harness-specific error text, and exits.

//	This code was once again adapted (with permission) from code in Tim Mann's
//	Winboard.

void VDisplayLastError(char * sz)
{
	int	err = errno;

	if (!s_ivars.fQuit)
		fprintf(stderr, "%s: %s\n", sz, strerror(err));
	exit(1);
}

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

//	This is supposed to kill the engine, but when I call it, it hangs, so I
//	don't call it.

void DestroyChildProcess(PCHILDPROC pcp)
{
	close(pcp->hTo);
	if (pcp->hFrom >= 0)
		close(pcp->hFrom);
}

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

//	POSIX replacement for the original Win32 CreateProcess-based
//	function.  It starts the engine while hooking the engine's stdin
//	and stdout, exactly as the Win32 version did.
//
//	"szEngine" is handed to "/bin/sh -c", just as the original passed
//	the whole string to CreateProcess -- this means an engine command
//	line with arguments, e.g. "bringer.exe /winboard", still works
//	without epd2wb having to do its own argument splitting or quoting.
//
//	This function returns FALSE if it fails, and "errno" might explain
//	a little more about why it failed.

BOOL FStartProcess(PCHILDPROC pcp, char * szEngine)
{
	int	argpToEngine[2];	// [0] = read end (child stdin), [1] = write end.
	int	argpFromEngine[2];	// [0] = read end, [1] = write end (child stdout).
	pid_t	pid;

	if (pipe(argpToEngine) < 0)
		return fFALSE;
	if (pipe(argpFromEngine) < 0) {
		close(argpToEngine[0]);
		close(argpToEngine[1]);
		return fFALSE;
	}
	pid = fork();
	if (pid < 0) {
		close(argpToEngine[0]);
		close(argpToEngine[1]);
		close(argpFromEngine[0]);
		close(argpFromEngine[1]);
		return fFALSE;
	}
	if (pid == 0) {			// Child: becomes the engine.
		dup2(argpToEngine[0], STDIN_FILENO);
		dup2(argpFromEngine[1], STDOUT_FILENO);
		dup2(argpFromEngine[1], STDERR_FILENO);
		close(argpToEngine[0]);
		close(argpToEngine[1]);
		close(argpFromEngine[0]);
		close(argpFromEngine[1]);
		setpgid(0, 0);		// New process group, like Win32's
							//  CREATE_NEW_PROCESS_GROUP.
		execl("/bin/sh", "sh", "-c", szEngine, (char *)NULL);
		_exit(127);			// execl() only returns on failure.
	}
	//	Parent.
	close(argpToEngine[0]);
	close(argpFromEngine[1]);
	pcp->pid = pid;
	pcp->hTo = argpToEngine[1];
	pcp->hFrom = argpFromEngine[0];
	return fTRUE;
}

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

#if	0

//	This debug routine might come in handy.

void VDumpRaw(char * rgb, int cb)
{
	int	ib;

	printf("RAW: ");
	for (ib = 0; ib < cb; ib++) {
		if ((rgb[ib] < ' ') || (rgb[ib] > '~'))
			printf("<%02Xh>", rgb[ib]);
		else
			putchar(rgb[ib]);
	}
	putchar('\n');
}

#endif

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

//	This is a thread function.  It will sit here waiting for permission to
//	send the next line.  Once it gets it, it will read a line from the engine,
//	then it will break it up into chunks (delimited by \r\n) and pass them
//	back to the main thread one chunk at a time via "s_ivars.aszInBuf".

void * DwInput(void * pv)
{
	char	argb[cbINPUT_BUF];
	int	ib = 0;
	int	cb = 0;
	int	ibOut = 0;

	for (;;) {
		WaitForSingleObject(s_ivars.heventStdinAck, INFINITE);
		for (;;) {
			Assert(ib <= cb);
			if (ib == cb) {		// Empty buffer, read a new line.
				ssize_t	cbRead;

				do {
					cbRead = read(s_ivars.cp.hFrom, argb, sizeof(argb));
				} while ((cbRead < 0) && (errno == EINTR));
				if (cbRead < 0)
					VDisplayLastError("Can't read from engine");
				if (cbRead == 0)	// Engine closed its end (exited).
					exit(0);
				ib = 0;
				cb = (int)cbRead;
//				VDumpRaw(argb, cb);
				Assert(cb <= (int)sizeof(argb));
			} else if ((argb[ib] == '\r') || (argb[ib] == '\n')) {
				if ((++ib < cb) && (argb[ib] == '\n'))
					ib++;
				s_ivars.aszInBuf[ibOut] = '\0';
				ibOut = 0;
				if (s_ivars.fDump)
					printf(">%s\n", s_ivars.aszInBuf);
				else if (s_ivars.aszInBuf[0] == '#')
					printf("%s\n", s_ivars.aszInBuf);
				SetEvent(s_ivars.heventStdinReady);
				break;
			} else
				s_ivars.aszInBuf[ibOut++] = argb[ib++];
		}
	}
	return NULL;
}

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

//	This writes a line of stuff to the engine, with a '\n' appended.

void VSendToEngine(const char * szFmt, ...)
{
	char	aszBuf[2048];
	va_list	lpArgPtr;
	int	cb;
	int	ibWritten;

	va_start(lpArgPtr, szFmt);
	vsnprintf(aszBuf, sizeof(aszBuf) - 1, szFmt, lpArgPtr);
	va_end(lpArgPtr);
	cb = strlen(aszBuf);
	aszBuf[cb++] = '\n';
	aszBuf[cb] = '\0';
	if (s_ivars.fDump)
		printf("<%s", aszBuf);
	ibWritten = 0;
	while (ibWritten < cb) {
		ssize_t	cbWritten = write(s_ivars.cp.hTo,
			aszBuf + ibWritten, cb - ibWritten);

		if (cbWritten < 0) {
			if (errno == EINTR)
				continue;
			VDisplayLastError("Can't write to engine");
		}
		ibWritten += (int)cbWritten;
	}
}

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

//	This breaks a space-delimited line ("sz") up into pieces and returns a
//	count of the pieces.  The line has null characters poked into it as part
//	of the process.
//
//	"rgsz" is an array of pointers to the pieces.
//
//	"*pibSecond" is the index in "sz" of the second piece.
//
//	This function handles double-quoted matter by deleting the quotes and
//	ignoring spaces that occur within the quoted matter.  So:
//
//		id "tough position"
//
//	is turned into:
//
//		0: id
//		1: tough position

int	CszVectorizeEpd(char * sz, char * rgsz[], int * pibSecond)
{
	int	i;
	int	csz;

	for (csz = 0, i = 0; sz[i]; i++)
		if (sz[i] != ' ') {
			BOOL	fInQuote;

			if (sz[i] == '"') {
				fInQuote = fTRUE;
				i++;
			} else
				fInQuote = fFALSE;
			if (csz == 1)
				*pibSecond = i;
			rgsz[csz++] = sz + i;
			for (;; i++) {
				if ((sz[i] == ' ') && (!fInQuote))
					break;
				if ((sz[i] == '"') && (fInQuote))
					break;
				if (sz[i] == '\0')
					break;
			}
			if (sz[i] == '\0')
				break;
			sz[i] = '\0';
		}
	if (csz <= 1)
		*pibSecond = i;
	return csz;
}

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

//	This is similar to the "CszVectorizeEpd" function, but it doesn't strip
//	quotes, and if a quote occurs in the middle of a token, that's okay.
//
//	"feature myname="Mint v2.1" playother=1"
//
//		feature
//		myname="Mint v2.1"
//		playother=1

int	CszVectorizeCmd(char * sz, char * rgsz[], int * pibSecond)
{
	int	i;
	int	csz;

	for (csz = 0, i = 0; sz[i]; i++)
		if ((sz[i] != ' ') && (sz[i] != '\t')) {
			BOOL	fInQuote;

			if (csz == 1)
				*pibSecond = i;
			rgsz[csz++] = sz + i;
			fInQuote = fFALSE;
			for (;; i++) {
				if (((sz[i] == ' ') || (sz[i] == '\t')) && (!fInQuote))
					break;
				if (sz[i] == '"')
					fInQuote = !fInQuote;
				if (sz[i] == '\0')
					break;
			}
			if (sz[i] == '\0')
				break;
			sz[i] = '\0';
		}
	return csz;
}

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

//	This removes the newline at the end of "sz", if it is there.

void VStrip(char * sz)
{
	int	i;

	for (i = 0; sz[i]; i++)
		if (sz[i] == '\n') {
			sz[i] = '\0';
			break;
		}
}

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

//	These commands are sent to the engine once at the start of the run, after
//	the engine seems to be done sending the harness "feature" commands.

void VPrepEngine(void)
{
	VSendToEngine("new");
	VSendToEngine("level 0 5 0");
	VSendToEngine("post");
	VSendToEngine("hard");
	VSendToEngine("easy");
}

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

//	In a few places I am going to vectorize a string, then search for the
//	first vectorized element in a command table, then call a function with
//	the original string and the vector.  This structure facilitates this.

typedef	struct	tagCMD {
	char * sz;
	BOOL (* pfn)(char * sz, char * rgsz[], int csz);
}	CMD, * PCMD;

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

//	EPD processing commands.

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

//	This one processes "bm".  It converts the arguments into a double null-
//	terminated string.

BOOL FCmdBm(char * sz, char * rgsz[], int csz)
{
	int	ib;
	int	i;

	ib = 0;
	for (i = 1; i < csz; i++)
		ib += sprintf(s_ivars.aszAnswer + ib, "%s", rgsz[i]) + 1;
	s_ivars.aszAnswer[ib] = '\0';
	return fTRUE;
}

//	This one processes "am".  It converts the arguments into a double null-
//	terminated string.

BOOL FCmdAm(char * sz, char * rgsz[], int csz)
{
	int	ib;
	int	i;

	ib = 0;
	for (i = 1; i < csz; i++)
		ib += sprintf(s_ivars.aszAvoid + ib, "%s", rgsz[i]) + 1;
	s_ivars.aszAvoid[ib] = '\0';
	return fTRUE;
}

//	This one processes "id".  It just remembers the argument.

BOOL FCmdId(char * sz, char * rgsz[], int csz)
{
	if (csz < 2)
		return fTRUE;
	strcpy(s_ivars.aszId, rgsz[1]);
	return fTRUE;
}

//	This one processes "fmvn", which stands for "first move number".

BOOL FCmdFmvn(char * sz, char * rgsz[], int csz)
{
	if (csz < 2)
		return fTRUE;
	s_ivars.movStart = atoi(rgsz[1]);
	if (s_ivars.movStart < 1)
		s_ivars.movStart = 1;
	return fTRUE;
}

//	This one processes "hmvc", which is the fifty-move counter (number of
//	moves since the last reversible move.

BOOL FCmdHmvc(char * sz, char * rgsz[], int csz)
{
	if (csz < 2)
		return fTRUE;
	s_ivars.plyFifty = atoi(rgsz[1]);
	if (s_ivars.plyFifty < 0)
		s_ivars.plyFifty = 0;
	return fTRUE;
}

//	EPD command table.

CMD const c_argcmdEpd[] = {
	"bm",			FCmdBm,		// Best move
	"id",			FCmdId,		// Avoid move.
	"fmvn",			FCmdFmvn,	// Full move number.
	"hmvc",			FCmdHmvc,	// "Half-move clock".  Fifty-move counter.
	NULL,
};

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

//	This ignores all input for the specified number of seconds.  I use this
//	if I can't use ping, to give the engine time to shut up and stop spitting
//	analysis crap at me.

void VKillTime(TM tmToWait)
{
	TM	tmEnd = TmNow() + tmToWait;

	for (;;) {
		if (WaitForSingleObject(s_ivars.heventStdinReady,
			tmEnd - TmNow()) == WAIT_TIMEOUT)
			break;
		SetEvent(s_ivars.heventStdinAck);
	}
}

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

void VSendMoveToEngine(char * szMov)
{
	if (s_ivars.fUsermove)
		VSendToEngine("usermove %s", szMov);
	else
		VSendToEngine(szMov);
}

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

BOOL FFenToBd(char * szFen, PBD pbd)
{
	int	isq;
	int	rnk;
	int	fil;
	int	co;
	
	for (isq = 0; isq < csqMAX; isq++)
		pbd->argpcco[isq].co = coMAX;
	rnk = 7;
	fil = 0;
	for (;; szFen++)
		if (*szFen == ' ')
			break;
		else if (*szFen == '/') {
			rnk--;
			fil = 0;
		} else if ((*szFen >= '1') && (*szFen <= '8'))
			fil += *szFen - '0';
		else if ((fil > 7) || (rnk < 0))
			return fFALSE;
		else {
			int	pc;
			
			for (pc = pcPAWN; pc <= pcKING; pc++)
				if (s_ivars.plangEnglish->argbPc[pc] == *szFen) {
					co = coWHITE;
					break;
				}
			if (pc > pcKING)
				for (pc = pcPAWN; pc <= pcKING; pc++)
					if (s_ivars.plangEnglish->argbPc[pc] ==
						toupper(*szFen)) {
						co = coBLACK;
						break;
					}
			if (pc > pcKING)
				return fFALSE;
			pbd->argpcco[rnk * 8 + fil].pc = pc;
			pbd->argpcco[rnk * 8 + fil].co = co;
			fil++;
		}
	switch (*++szFen) {
	case 'w':
		pbd->coMove = coWHITE;
		break;
	case 'b':
		pbd->coMove = coBLACK;
		break;
	default:
		return fFALSE;
	}
	return fTRUE;
}

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

//	This is NOT guaranteed to get a correct FEN.

BOOL FSendEdit(char * szFen)
{
	BD	bd;
	int	co;

	if (!FFenToBd(szFen, &bd))
		return fFALSE;
	if (bd.coMove == coBLACK)
		VSendMoveToEngine("a2a3");
	VSendToEngine("edit");
	VSendToEngine("#");
	for (co = coWHITE; co <= coBLACK; co++) {
		int	isq;

		for (isq = 0; isq < csqMAX; isq++)
			if (bd.argpcco[isq].co == co) {
				char	asz[8];
				
				asz[0] = s_ivars.plangEnglish->argbPc[bd.argpcco[isq].pc];
				asz[1] = (isq % 8) + 'a';
				asz[2] = (isq / 8) + '1';
				asz[3] = '\0';
				VSendToEngine(asz);
			}
		VSendToEngine((co == coWHITE) ? "c" : ".");
	}
	return fTRUE;
}

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

//	This gets a line from the EPD file, breaks it up and processes it, gets
//	the engine ready to go, and sends a ping.

//	Later on, something else will see the "pong" from the engine, and the
//	engine will be told to analyze.

//	If there are no more EPD lines, or if this EPD line is seriously broken,
//	this returns FALSE.

BOOL FNextTest(void)
{
	char	aszBuf[256];
	int	i;
	int	cSpaces;
	int	coMove;

	for (;;) {
		//
		//	Get the line.
		//
		if (fgets(aszBuf, sizeof(aszBuf), s_ivars.pfI) != aszBuf)
			return fFALSE;
		s_ivars.cLine++;
		VStrip(aszBuf);
		if (aszBuf[0] != '\0')
			break;
	}
	//	Break the FEN out of the line.  The EPD standard doesn't include the
	//	list two fields of the FEN.  These may be added while processing
	//	the semi-colon terminated EPD fields that will probably follow.
	//
	for (i = cSpaces = 0; aszBuf[i]; i++) {
		if (aszBuf[i] == ' ') {
			if (++cSpaces == 4)
				break;
			if (cSpaces == 1)
				if (aszBuf[i + 1] == 'w')
					coMove = coWHITE;
				else
					coMove = coBLACK;
		}
		s_ivars.aszFen[i] = aszBuf[i];
	}
	if (aszBuf[i] == '\0') {	// If I didn't find the FEN I assume I'm
								//  broken.
		printf("Obviously bogus FEN, line %d\n", s_ivars.cLine);
		return fFALSE;
	}
	s_ivars.aszFen[i] = '\0';
	//
	//	I'm sitting at the space after the FEN now.  I'm going to eat EPD
	//	semi-colon delimited fields.  First step is to zero some fields that
	//	I might not find.
	//
	s_ivars.aszId[0] = '\0';
	s_ivars.aszAnswer[0] = '\0';
	s_ivars.aszAvoid[0] = '\0';
	s_ivars.movStart = 1;
	s_ivars.plyFifty = 0;
	//
	//	One pass through this loop for every EPD field.
	//
	for (;;) {
		char	aszCmd[256];
		int	j;
		char *	argsz[256];
		int	ibSecond;
		int	csz;

		while (aszBuf[i] == ' ')	// Strip preceding spaces.
			i++;
		if (aszBuf[i] == '\0')		// A null here indicates no more fields.
			break;
		//
		//	Grab everything from here until the semi-colon.
		//
		for (j = 0; aszBuf[i]; i++) {
			if (aszBuf[i] == ';') {
				i++;
				break;
			}
			aszCmd[j++] = aszBuf[i];
		}
		aszCmd[j] = '\0';
		//
		//	Break the argument up, then try to process it via the command
		//	table.
		//
		csz = CszVectorizeEpd(aszCmd, argsz, &ibSecond);
		if (csz)
			for (j = 0; c_argcmdEpd[j].sz != NULL; j++)
				if (!strcmp(c_argcmdEpd[j].sz, argsz[0])) {
					(*c_argcmdEpd[j].pfn)(aszBuf + ibSecond, argsz, csz);
					break;
				}
	}
	//	At this point the entire EPD is eaten.
	//
	sprintf(s_ivars.aszFen + strlen(s_ivars.aszFen), " %d %d",
		s_ivars.plyFifty, s_ivars.movStart);
	s_ivars.fCorrectAnswer = fFALSE;
	s_ivars.tmFoundIn = -1;
	s_ivars.fError = fFALSE;
	printf("Id:  %s\n", (s_ivars.aszId[0] == '\0') ?
		"(none)" : s_ivars.aszId);
	printf("Fen: %s\n", s_ivars.aszFen);
	if (s_ivars.aszAnswer[0] != '\0') {
		printf("Bm: ");
		for (i = 0; s_ivars.aszAnswer[i] != '\0';) {
			printf(" %s", s_ivars.aszAnswer + i);
			i += strlen(s_ivars.aszAnswer + i) + 1;
		}
		putchar('\n');
	}
	if (s_ivars.aszAvoid[i] != '\0') {
		printf("Am: ");
		for (i = 0; s_ivars.aszAvoid[i] != '\0';) {
			printf(" %s", s_ivars.aszAvoid + i);
			i += strlen(s_ivars.aszAvoid + i) + 1;
		}
		putchar('\n');
	}
	putchar('\n');
	//
	//	Tell the engine to do its thing.
	//
	VSendToEngine("force");
	VSendToEngine("new");
	//
	//	If I can't use "analyze", I have to give the engine a time control.
	//	This is problematic.  I don't know if all engines support "st".  If
	//	they do, that's the solution.  Otherwise, I set this to do a game
	//	that's sudden death in 10000 minutes.  That's a long time and I'd hope
	//	that it will prove long enough for most purposes.
	//
	//	If you can think of a better way to do this, or if this causes you
	//	significant trouble, please email me at brucemo@seanet.com
	//
	if (!s_ivars.fAnalyze)
		VSendToEngine("level 0 10000 0");
	else
		VSendToEngine("level 0 5 0");
	VSendToEngine("post");
	VSendToEngine("hard");
	VSendToEngine("easy");
	//
	//	Send ping if I can, otherwise wait efficiently for a while.
	//
	if (s_ivars.fPing) {
		VSendToEngine("ping %d", ++s_ivars.cPing);
		s_ivars.mode = modeWAITING;
	} else {
		VKillTime(s_ivars.tmWait);
		s_ivars.mode = modeTESTING;
	}
	VSendToEngine("force");
	//
	//	Send the position.
	//
	if (s_ivars.fSetboard) {
		VSendToEngine("setboard %s", s_ivars.aszFen);
		if ((s_ivars.fColor) && (!s_ivars.fAnalyze))
			// This line is *not* wrong.
			VSendToEngine((coMove == coWHITE) ? "black" : "white");
	} else if (!FSendEdit(s_ivars.aszFen)) {// If I couldn't turn the FEN into
		s_ivars.fError = fTRUE;				//  a valid position, I'm going to
		return fTRUE;						//  set the "Error" flag and just
	}										//  wait until the test period is
											//  over.  The "Error" flag can
											//  also be set if the engine
											//  rejects the position (this is
											//  handled elsewhere).
	//	Figure out when to end.
	//
	s_ivars.tmEnd = TmNow() + s_ivars.tmPerMove;
	//
	//	Tell it to go.
	//
	if (s_ivars.fAnalyze)
		VSendToEngine("analyze");
	else {
		if (s_ivars.fUseSt) {
			VSendToEngine("st %d", s_ivars.tmPerMove / 1000);
			VSendToEngine("time %d", s_ivars.tmPerMove / 10);
		} else
			VSendToEngine("time 1000000");		// 10000 seconds for this.
		if (s_ivars.fColor)
			VSendToEngine((coMove == coWHITE) ? "white" : "black");
		VSendToEngine("go");
	}
	s_ivars.tmStart = TmNow();
	return fTRUE;
}

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

//	When I am done processing "feature" commands from the engine, I'll do
//	this.  The funtion sends a few simple commands to the engine, then tries
//	to set up the first EPD line.

char const * c_argszNoYes[] = { "No", "Yes" };

BOOL FFeatureTimeout(void)
{
	int	i;

	putchar('\n');
	printf("Engine:         %s\n", s_ivars.aszEngine);
	printf("Suite:          %s\n", s_ivars.aszSuite);
	printf("Time per move:  %d second%s\n", s_ivars.tmPerMove / 1000,
		(s_ivars.tmPerMove / 1000 == 1) ? "" : "s");
	printf("Language:       %s (%c%c%c%c%c%c)\n", s_ivars.plang->szLang,
		s_ivars.plang->argbPc[pcPAWN],
		s_ivars.plang->argbPc[pcKNIGHT],
		s_ivars.plang->argbPc[pcBISHOP],
		s_ivars.plang->argbPc[pcROOK],
		s_ivars.plang->argbPc[pcQUEEN],
		s_ivars.plang->argbPc[pcKING]);
	if (s_ivars.cSkip)
		printf("Analysis skip:  %d\n", s_ivars.cSkip);
	printf("Engine will use ...\n");
	printf("    \"analyze\":  %s\n", c_argszNoYes[s_ivars.fAnalyze]);
	printf("    \"white\" &\n");
	printf("      \"black\":  %s\n", c_argszNoYes[s_ivars.fColor]);
	printf("    \"ping\":     %s\n", c_argszNoYes[s_ivars.fPing]);
	printf("    \"setboard\": %s\n", c_argszNoYes[s_ivars.fSetboard]);
	printf("    \"usermove\": %s\n", c_argszNoYes[s_ivars.fUsermove]);
	if ((!s_ivars.fPing) && (!s_ivars.fUseSt))
		printf("Test delay:     %d second%s\n", s_ivars.tmWait / 1000,
			(s_ivars.tmWait / 1000 == 1) ? "" : "s");
	if (s_ivars.fUseSt)
		printf("Try \"st\":       %s\n", c_argszNoYes[s_ivars.fUseSt]);
	if (s_ivars.fFindInfo)
		return fFALSE;
	putchar('\n');
	for (i = 0; i < cbLINE - 1; i++)
		putchar('-');
	putchar('\n');
	putchar('\n');
	VPrepEngine();
	return FNextTest();
}

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

//	Breaks "done=1", etc., into two parts.

BOOL FBreakFeature(char * szCmd, char * szName, int * piValue)
{
	for (;;) {
		if ((*szCmd == '\0') || (*szCmd == '='))
			break;
		*szName++ = *szCmd++;
	}
	*szName = '\0';
	if (*szCmd++ != '=')
		return fFALSE;
	if (*szCmd == '"') {	// I'm going to handle string features by
		*piValue = 0;		//  interpreting strings as zero.  This is fine
		return fTRUE;		//  for now but may need changing later.
	}
	if (!isdigit(*szCmd))
		return fFALSE;
	*piValue = atoi(szCmd);
	return fTRUE;
}

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

//	Engine commands.

//	These functions are called in reaction to command received from the
//	engine.

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

//	"feature" command.  All I care about are "feature done=0", which tells the
//	engine to ignore the two-second timeout, and "feature done=1", which is
//	treated as a feature timeout.

BOOL FCmdFeature(char * sz, char * rgsz[], int csz)
{
	int	isz;
	
	for (isz = 1; isz < csz; isz++) {
		char	aszName[256];
		int	iValue;

		if (!FBreakFeature(rgsz[isz], aszName, &iValue))
			return fTRUE;	// Ignore that which I don't understand.
		if (!strcmp(aszName, "done")) {
			if (iValue == 0) {
				if (s_ivars.mode == modeFEATURE_TIMEOUT)
					s_ivars.mode = modeFEATURE_NOTIMEOUT;
			} else {
				if ((s_ivars.mode == modeFEATURE_NOTIMEOUT) ||
					(s_ivars.mode == modeFEATURE_TIMEOUT))
					if (!FFeatureTimeout())
						return fFALSE;
			}
		} else if (!strcmp(aszName, "ping"))
			s_ivars.fPing = (iValue) ? fTRUE : fFALSE;
		else if (!strcmp(aszName, "setboard"))
			s_ivars.fSetboard = (iValue) ? fTRUE : fFALSE;
		else if (!strcmp(aszName, "analyze")) {
			s_ivars.fAnalyze = (iValue) ? fTRUE : fFALSE;
			if (s_ivars.fUseSt)		// "-t" switch turns off analyze.
				s_ivars.fAnalyze = fFALSE;
		} else if (!strcmp(aszName, "colors"))
			s_ivars.fColor = (iValue) ? fTRUE : fFALSE;
		else if (!strcmp(aszName, "usermove"))
			s_ivars.fUsermove = (iValue) ? fTRUE : fFALSE;
	}
	return fTRUE;
}

//	A "pong" indicates that the engine is listening to me and is ready to
//	start analyzing the current position, so I change the mode to TESTING.

//	In some cases I need to keep track of time taken by the program.  I start
//	the clock when I tell the program to go and think, but I also send a ping,
//	and if I get a pong back, I reset the clock.

BOOL FCmdPong(char * sz, char * rgsz[], int csz)
{
	if (csz == 0)
		return fTRUE;
	if (atoi(rgsz[1]) == s_ivars.cPing) {
		s_ivars.mode = modeTESTING;
		s_ivars.tmStart = TmNow();
		s_ivars.tmEnd = s_ivars.tmStart + s_ivars.tmPerMove;
	}
	return fTRUE;
}

void VAnalysisLine(char * rgsz[], int csz);

void VEngineMoved(char * sz)
{
	char	asz[256];
	char *	argsz[256];
	int	ibSecond;
	int	csz;

	s_ivars.tmEnd = TmNow();
	sprintf(asz, "-1 0 %lu 0 %s", (s_ivars.tmEnd - s_ivars.tmStart) / 10, sz);
	csz = CszVectorizeCmd(asz, argsz, &ibSecond);
	VAnalysisLine(argsz, csz);
}

BOOL FCmdMove(char * sz, char * rgsz[], int csz)
{
	if (csz != 2)
		return fTRUE;
	if (s_ivars.mode != modeTESTING)
		return fTRUE;
	VEngineMoved(rgsz[1]);
	return fTRUE;
}

//	This will be received if I send a bogus FEN to the engine.  I'm not going
//	to look at the error message at all.  It is supposed to be
//	"Illegal position", but I am not going to count on that.

BOOL FCmdTellusererror(char * sz, char * rgsz[], int csz)
{
	if (s_ivars.mode != modeTESTING)
		return fTRUE;
	s_ivars.fError = fTRUE;
	return fTRUE;
}

//	Engine command table.

CMD const c_argcmdEngine[] = {
	"feature",			FCmdFeature,
	"pong",				FCmdPong,
	"move",				FCmdMove,
	"tellusererror",	FCmdTellusererror,
	NULL,
};

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

BOOL FIsInteger(char * sz, BOOL * pfNonBlankTrailer)
{
	if ((*sz == '-') || (*sz == '+'))
		sz++;
	if (!isdigit(*sz++))	// Must have at least one digit.
		return fFALSE;
	while (isdigit(*sz))
		sz++;
	if (*sz != '\0') {		// I'm going to allow one non-blank trailer.
		*pfNonBlankTrailer = fTRUE;
		sz++;
	} else
		*pfNonBlankTrailer = fFALSE;
	return (*sz == '\0');
}

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

//	This checks to see if "sz" is a correct answer to the current test
//	position.

BOOL FCorrectAnswer(char * sz)
{
	int	i;

	while (isdigit(*sz))
		sz++;
	while (*sz == '.')
		sz++;
	if (s_ivars.aszAnswer[0] != '\0') {
		for (i = 0; s_ivars.aszAnswer[i] != '\0';) {
			if (FCheckAnswer(s_ivars.aszFen, sz, s_ivars.aszAnswer + i,
				s_ivars.plang, s_ivars.plangEnglish))
				return fTRUE;
			i += strlen(s_ivars.aszAnswer + i) + 1;
		}
		return fFALSE;
	}
	if (s_ivars.aszAvoid[0] != '\0') {
		for (i = 0; s_ivars.aszAvoid[i] != '\0';) {
			if (FCheckAnswer(s_ivars.aszFen, sz, s_ivars.aszAnswer + i,
				s_ivars.plang, s_ivars.plangEnglish))
				return fFALSE;
			i += strlen(s_ivars.aszAvoid + i) + 1;
		}
		return fTRUE;
	}
	return fFALSE;
}

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

BOOL FIsMove(char * sz)
{
	while (isdigit(*sz))
		sz++;
	while (*sz == '.')
		sz++;
	if (*sz != '\0')
		return fTRUE;
	return fFALSE;
}

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

//	This function handles a line of analysis nonsense sent by the engine.  At
//	the start of the function I don't know for sure that it's actually a line
//	of analysis.  I do some dummy checking to see if it starts with four
//	integers, and if so I assume it's an analysis line.

void VAnalysisLine(char * rgsz[], int csz)
{
	int	plyDepth;
	int	valScore;
	TM	tm;
	long	nodes;
	int	i;
	char	aszResult[16];
	int	cb;
	int	cbStats;
	BOOL	fTrailer;
	int	cSkip;

	if ((csz == 1) && ((!strcmp(rgsz[0], "++")) ||
		(!strcmp(rgsz[0], "--")))) {
		printf("%40s\n", rgsz[0]);
	}
	if (csz < 4)
		return;
	//
	//	An analysis line starts with four integers.  If the first one has a
	//	trailing non-blank, that's okay but it puts us into Gnuchess mode,
	//	in which case the third parameter is a value in seconds, not in 1/100
	//	of a second.
	//
	for (i = 0; i < 4; i++) {
		BOOL	f;

		if (!FIsInteger(rgsz[i], &f))
			return;
		if (i == 0)
			fTrailer = f;
		else if (f)
			return;
	}
	//	It had four integers, break them out.
	//
	plyDepth = atoi(rgsz[0]);
	cSkip = (plyDepth < 0) ? 0 : s_ivars.cSkip;
	valScore = atoi(rgsz[1]);
	tm = atol(rgsz[2]) * 10;
	if (fTrailer)
		tm *= 100;
	nodes = atol(rgsz[3]);
	//
	//	Check to see if the answer is correct.  If so, record the time, if
	//	not, clear the time.
	//
	//	This loop is a little gross, because I might decide that the first
	//	"move" or two aren't really moves (they might be move numbers or some
	//	symbol that indicates that black is on move), so I'll skip them.
	//
	for (i = 4 + cSkip;; i++)
		if (i >= csz) {
lblWrong:	s_ivars.fCorrectAnswer = fFALSE;
			strcpy(aszResult, "no");
			s_ivars.tmFoundIn = -1;
			break;
		} else if (FIsMove(rgsz[i])) {
			if (!FCorrectAnswer(rgsz[i]))
				goto lblWrong;
			s_ivars.fCorrectAnswer = fTRUE;
			strcpy(aszResult, "yes");
			if (s_ivars.tmFoundIn == -1)
				s_ivars.tmFoundIn = tm;
			break;
		}
	//
	//	Output the line in a somewhat prettied up format.
	//
	if (plyDepth < 0)
		cb = cbStats = printf("%-3s ??? %9ld (moved)         ???",
			aszResult, tm);
	else
		cb = cbStats = printf("%-3s %3d %9ld %+6d %12ld",
			aszResult, plyDepth, tm, valScore, nodes);
	for (i = 4 + cSkip; i < csz; i++) {
		int	cbCur = strlen(rgsz[i]) + 1;

		if (cb + cbCur >= cbLINE) {
			putchar('\n');
			for (cb = 0; cb < cbStats; cb++)
				putchar(' ');
		}
		printf(" %s", rgsz[i]);
		cb += cbCur;
	}
	putchar('\n');
}

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

//	This is called at the end, and it just dumps out the number solved, and
//	a breakdown of how many problems were solved in less than N seconds.

void VDumpResults(void)
{
	int	isolHi;
	int	i;
	
	for (isolHi = isolMAX - 1; isolHi >= 0; isolHi--)
		if (s_ivars.argsol[isolHi])
			break;
	printf("Results:\n\n");
	if (s_ivars.cSolved) {
		if (isolHi < 0)
			printf("No problems solved in < %d seconds\n", isolMAX);
		else {
			int	cTotal = 0;

			printf("<Sec Solved Total\n");
			printf("---- ------ ------\n");
			for (i = 0; i <= isolHi; i++) {
				cTotal += s_ivars.argsol[i];
				printf("%4d %6d %6d\n", i + 1, s_ivars.argsol[i], cTotal);
			}
		}
		putchar('\n');
	}
	printf("%d problem%s solved.\n", s_ivars.cSolved,
		(s_ivars.cSolved == 1) ? "" : "s");
	printf("%d problem%s unsolved.\n", s_ivars.cFailed,
		(s_ivars.cFailed == 1) ? "" : "s");
	if (s_ivars.cError)
		printf("%d error%s found!\n",
			s_ivars.cError, (s_ivars.cError == 1) ? "" : "s");
}


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

#define	uHOUR_FROM_MILLI(tm)	(unsigned)((TM)(tm) / 3600000)
#define	uMIN_FROM_MILLI(tm)		(unsigned)(((TM)(tm) / 60000) % 60)
#define	uSEC_FROM_MILLI(tm)		(unsigned)(((TM)(tm) / 1000) % 60)
#define	uMILLI_FROM_MILLI(tm)	(unsigned)((TM)(tm) % 1000)

BOOL FSummary(void)
{
	int	i;

	if (s_ivars.fAnalyze)
		VSendToEngine("exit");
	printf("\nResult:   ");
	if (s_ivars.fError) {
		printf("ERROR!!");
		s_ivars.cError++;
	} else if (s_ivars.fCorrectAnswer) {
		printf("Success");
		s_ivars.cSolved++;
	} else {
		printf("Failure");
		s_ivars.cFailed++;
	}
	putchar('\n');
	if (s_ivars.fCorrectAnswer) {
		int	isol;

		printf("Found in: %ld ms (%02d:%02d:%02d.%03d)\n",
			s_ivars.tmFoundIn,
			uHOUR_FROM_MILLI(s_ivars.tmFoundIn),
			uMIN_FROM_MILLI(s_ivars.tmFoundIn),
			uSEC_FROM_MILLI(s_ivars.tmFoundIn),
			uMILLI_FROM_MILLI(s_ivars.tmFoundIn));
		isol = s_ivars.tmFoundIn / 1000;
		if (isol < isolMAX)
			s_ivars.argsol[isol]++;
	}
	putchar('\n');
	for (i = 0; i < cbLINE - 1; i++)
		putchar('-');
	putchar('\n');
	putchar('\n');
	return FNextTest();
}

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

//	This is this program's main loop.

void VProcess(void)
{
	int	i;

	//	The function starts in FEATURE_TIMEOUT mode.  It's going to sit here
	//	and collect feature commands for two seconds or perhaps longer or
	//	shorter if directed by the feature commands.
	//
	//	Change in Release 01:  The engine now waits five seconds, since Tim
	//	has said that he's going to make this change in protover 3 anyway.
	//
	printf("\nEpd2Wb, Release 02\n");
	VSendToEngine("xboard");
	VSendToEngine("protover 2");
	s_ivars.mode = modeFEATURE_TIMEOUT;
	s_ivars.tmEnd = TmNow() + 5000;
	//
	//	Tell the input thread that I'm ready for a line.
	//
	SetEvent(s_ivars.heventStdinAck);
	//
	//	Forever loop.  If this exits, it will be because the EPD file is
	//	exhausted.
	//
	for (;;) {
		int	csz;
		char	aszBuf[1024];
		char	aszVec[1024];
		char *	argsz[256];
		int	ibSecond;

		if (s_ivars.mode == modeFEATURE_TIMEOUT) {
			TM	tm;

			//	In FEATURE_TIMEOUT mode I'll wait some small amount of time
			//	for a line, and if this expires, the features are done and
			//	I start processing the EPD.
			//
			tm = TmNow();
			if ((tm >= s_ivars.tmEnd) ||
				(WaitForSingleObject(s_ivars.heventStdinReady,
				s_ivars.tmEnd - tm) == WAIT_TIMEOUT)) {
				if (!FFeatureTimeout())
					return;
				continue;	// There's no command, so back to the top of the
							//  loop.
			}
		} else if (s_ivars.mode == modeTESTING) {
			TM	tm;
			DWORD	dwTimeout;

			//	In TESTING mode, I'm running a test, so I'll wait for input
			//	only until the text time has expired.
			//
			//	If I run out of time I'll write some output then try to start
			//	a new test.
			//
			tm = TmNow();
			dwTimeout = (s_ivars.fUseSt) ?
				INFINITE : s_ivars.tmEnd - tm;
			if ((tm >= s_ivars.tmEnd) ||
				(WaitForSingleObject(s_ivars.heventStdinReady,
				dwTimeout) == WAIT_TIMEOUT)) {
				if (!FSummary())
					return;
				continue;	// There's no command, so back to the top of the
							//  loop.
			}
		} else
			//	In any other mode I'll wait forever for input.
			//
			WaitForSingleObject(s_ivars.heventStdinReady, INFINITE);
		//
		//	If I'm here, it's because the input request did not time out, so
		//	I received an input line, which is presumed to contain a command
		//	or some analysis.
		//
		//	Vectorize the line and try to process it as a command.
		//
		strcpy(aszBuf, s_ivars.aszInBuf);
		strcpy(aszVec, s_ivars.aszInBuf);
		csz = CszVectorizeCmd(aszVec, argsz, &ibSecond);
		if (csz) {
			for (i = 0; c_argcmdEngine[i].sz != NULL; i++)
				if (!strcmp(c_argcmdEngine[i].sz, argsz[0])) {
					if (!(*c_argcmdEngine[i].pfn)(aszBuf + ibSecond,
						argsz, csz))
						return;
					break;
				}
			if (s_ivars.mode == modeTESTING)
				if ((csz == 3) && (!strcmp(argsz[1], "...")))
					VEngineMoved(argsz[2]);
				else
					VAnalysisLine(argsz, csz);
		}
		//	Tell the input thread that I'm ready for another line of stuff.
		//
		SetEvent(s_ivars.heventStdinAck);
	}
}

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

void ShutdownSignalHandler(int signr)
{
	(void)signr;
	s_ivars.fQuit = fTRUE;
	if (s_ivars.fEngineStarted)
		VSendToEngine("quit");
}

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

LANG c_arglang[] = {
	"Czech",		'P',	'J',	'S',	'V',	'D',	'K',
	"Danish",		'B',	'S',	'L',	'T',	'D',	'K',
	"Dutch",		'O',	'P',	'L',	'T',	'D',	'K',
	"English",		'P',	'N',	'B',	'R',	'Q',	'K',
	"Estonian",		'P',	'R',	'O',	'V',	'L',	'K',
	"Finnish",		'P',	'R',	'L',	'T',	'D',	'K',
	"French",		'P',	'C',	'F',	'T',	'D',	'R',
	"German",		'B',	'S',	'L',	'T',	'D',	'K',
	"Hungarian",	'G',	'H',	'F',	'B',	'V',	'K',
	"Icelandic",	'P',	'R',	'B',	'H',	'D',	'K',
	"Italian",		'P',	'C',	'A',	'T',	'D',	'R',
	"Norwegian",	'B',	'S',	'L',	'T',	'D',	'K',
	"Polish",		'P',	'S',	'G',	'W',	'H',	'K',
	"Portuguese",	'P',	'C',	'B',	'T',	'D',	'R',
	"Romanian",		'P',	'C',	'N',	'T',	'D',	'R',
	"Spanish",		'P',	'C',	'A',	'T',	'D',	'R',
	"Swedish",		'B',	'S',	'L',	'T',	'D',	'K',
	NULL,
};

PLANG PlangFindLang(char * szLang)
{
	int	i;
	
	for (i = 0; c_arglang[i].szLang != NULL; i++)
		if (!strcmpi(c_arglang[i].szLang, szLang))
			return &c_arglang[i];
	return NULL;
}

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

char * const c_aszUsage[] = {
	"Usage: epd2wb <engine> <EPD> <seconds> [flags]",
	"  -?      Usage",
	"  -d      Dumps all input and output to the console",
	"  -i      Outputs engine information then stops",
	"  -l<L>   Choose analysis input language",
	"  -s<D>   Skip D analysis fields",
	"  -t      Uses Winboard \"st\" command to try to get old engines to work.",
	"  -w<S>   Protover 1 between-test wait period (in seconds)",
	NULL,
};

void VUsage(void)
{
	int	i;
	
	for (i = 0; c_aszUsage[i] != NULL; i++)
		fprintf(stderr, "%s\n", c_aszUsage[i]);
	printf("\nLanguages:\n\n");
	for (i = 0; c_arglang[i].szLang != NULL; i++) {
		if ((i % 4 == 3) || (c_arglang[i + 1].szLang == NULL))
			fprintf(stderr, "%s\n", c_arglang[i].szLang);
		else
			fprintf(stderr, "%-20s", c_arglang[i].szLang);
	}
	if (s_ivars.fEngineStarted)
		VSendToEngine("quit");
	exit(1);
}

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

//	This initializes some static variables and processes command-line
//	arguments.

int main(int argc, char * argv[])
{
	pthread_t	threadInput;
	int	iszArg;
	int	isz;
	char	aszLanguage[256];

	s_ivars.fDump = fFALSE;
	s_ivars.cSkip = 0;
	s_ivars.fEngineStarted = fFALSE;
	s_ivars.fPing = fFALSE;
	s_ivars.fSetboard = fFALSE;
	s_ivars.fAnalyze = fFALSE;
	s_ivars.fColor = fTRUE;
	s_ivars.aszEngine[0] = '\0';
	s_ivars.aszSuite[0] = '\0';
	s_ivars.fUseSt = fFALSE;
	s_ivars.tmWait = 4000;		// 4000 milliseconds.
	s_ivars.fFindInfo = fFALSE;
	s_ivars.fQuit = fFALSE;
	strcpy(aszLanguage, "English");
	signal(SIGINT, ShutdownSignalHandler);		// CTRL-C
	signal(SIGHUP, ShutdownSignalHandler);		// POSIX analog of SIGBREAK.
	signal(SIGTERM, ShutdownSignalHandler);
	for (isz = 1, iszArg = 0; isz < argc; isz++) {
		char * sz = argv[isz];

		switch (*sz++) {
		case '-':
			// NOTE: the original Windows version also accepted '/' as a
			// switch prefix (a DOS/Windows convention).  That's dropped
			// here on purpose -- on POSIX, absolute paths start with
			// '/' (e.g. "/tmp/suite.epd" as the EPD file argument), so
			// treating '/' as a switch prefix would misparse them.
			switch (*sz++) {
			case 'd':
				s_ivars.fDump = fTRUE;
				break;
			case 'i':
				s_ivars.fFindInfo = fTRUE;
				break;
			case 'l':
				if (*sz != '\0')
					strcpy(aszLanguage, sz);
				else if (isz + 1 == argc)
					VUsage();
				else if (argv[++isz][0] != '-')
					strcpy(aszLanguage, argv[isz]);
				else
					VUsage();
				break;
			case 's':
				if (*sz != '\0') {
					if (!isdigit(*sz))
						VUsage();
					s_ivars.cSkip = atoi(sz);
				} else if (isz + 1 == argc)
					VUsage();
				else if (isdigit(argv[++isz][0]))
					s_ivars.cSkip = atoi(argv[isz]);
				else
					VUsage();
				break;
			case 't':
				s_ivars.fUseSt = fTRUE;
				break;
			case 'w':
				if (*sz != '\0') {
					if (!isdigit(*sz))
						VUsage();
					s_ivars.tmWait = atoi(sz) * 1000;
				} else if (isz + 1 == argc)
					VUsage();
				else if (isdigit(argv[++isz][0]))
					s_ivars.tmWait = atoi(argv[isz]) * 1000;
				else
					VUsage();
				break;
			default:
				VUsage();
			}
			break;
		default:
			switch (iszArg++) {
			case 0:
				if (!FStartProcess(&s_ivars.cp, argv[isz]))
					VDisplayLastError("Can't start engine");
				strcpy(s_ivars.aszEngine, argv[isz]);
				s_ivars.fEngineStarted = fTRUE;
				break;
			case 1:
				if ((s_ivars.pfI = fopen(argv[isz], "r")) == NULL) {
					perror(argv[isz]);
					exit(1);
				}
				strcpy(s_ivars.aszSuite, argv[isz]);
				break;
			case 2:
				s_ivars.tmPerMove = atoi(argv[isz]) * 1000;
				break;
			default:
				VUsage();
			}
			break;
		}
	}
	if (iszArg < 3)
		VUsage();
	if ((s_ivars.plang = PlangFindLang(aszLanguage)) == NULL)
		VUsage();
	if (s_ivars.fUseSt)			// "-t" switch eliminates "tmWait".
		s_ivars.tmWait = 0;
	s_ivars.plangEnglish = PlangFindLang("English");
	Assert(s_ivars.plangEnglish != NULL);
	s_ivars.heventStdinReady = CreateEvent(NULL, FALSE, FALSE, NULL);
	s_ivars.heventStdinAck = CreateEvent(NULL, FALSE, FALSE, NULL);
	pthread_create(&threadInput, NULL, DwInput, NULL);
	VProcess();
	if (!s_ivars.fFindInfo)
		VDumpResults();
	VSendToEngine("quit");
	s_ivars.fQuit = fTRUE;
	VKillTime(2000);
//	DestroyChildProcess(&s_ivars.cp);	// This hangs so it's commented out.
	return 1;
}

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