
//	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-	-
//
//	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 just a normal printf with a "fflush" after it.

void VSendToWinboard(const char * szFmt, ...)
{
	char	aszBuf[1024];
	va_list	lpArgPtr;

	va_start(lpArgPtr, szFmt);
	vsprintf(aszBuf, szFmt, lpArgPtr);
	printf("%s\n", aszBuf);
	fflush(stdout);
}

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

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

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

void VStripWb(char * sz)
{
	int	i;

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

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

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

	for (csz = 0, i = 0; sz[i]; i++)
		if (sz[i] != ' ') {
			rgsz[csz++] = sz + i;
			for (;; i++) {
				if (sz[i] == ' ')
					break;
				if (sz[i] == '\0')
					break;
			}
			if (sz[i] == '\0')
				break;
			sz[i] = '\0';
		}
	return csz;
}

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

void VErrorWb(char * szMsg, char * szCmd)
{
	VSendToWinboard("Error (%s): %s", szMsg, szCmd);
}

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

//	I'm going to init the engine after sending "done=0".  I'm a little
//	worried about doing this at inital startup.

//	The engine I'm working on now isn't that likely to take a long time to
//	boot, but another engine might, and Winboard might decide that the engine
//	isn't going to send any feature commands, which Winboard would interpret
//	as meaning that it's protover 1.

//	If the interface really is using protover 1, we won't ever get to this
//	routine, and the engine will be initialized when we find a command that
//	requires engine initialization to run.

BOOL FCmdProtover(char * szCmd, char * rgsz[], int csz)
{
	static char const * s_argszFeatures[] = {
		"done=0",		// <-- Must be first.
#ifdef	LOSERS
		"variant=losers",
#endif
		"ping=1",
		"setboard=1",
		"sigint=0",
		"sigterm=0",
		"colors=0",
		"name=1",
		"draw=1",
		"time=1",
		"reuse=1",
		"analyze=1",
		"ics=1",
		NULL,
	};

	Assert(csz > 1);
	s_ivars.iProtover = atoi(rgsz[1]);
	if (s_ivars.iProtover < 1)
		s_ivars.iProtover = 1;
	if (s_ivars.iProtover > 1) {
		int	i;
		char	asz[256];

		for (i = 0; s_argszFeatures[i] != NULL; i++) {
			VSendToWinboard("feature %s", s_argszFeatures[i]);
			if ((i == 0) && (!s_ivars.fDidInitEngine)) {
				if (!FInitEngineThread(0, NULL))
					return fFALSE;
				s_ivars.fDidInitEngine = fTRUE;
			}
		}
		VPrMyName(asz);
		VSendToWinboard("feature myname=\"%s\"", asz);
		VSendToWinboard("feature done=1");
	}
	return fTRUE;
}

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

BOOL FCmdXboard(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	return fTRUE;
}

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

BOOL FCmdNew(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	s_ivars.fForce = fFALSE;
	VSendToEngine("setboard "
		"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
	VSendToEngine("sd 0");
	s_ivars.coOnMove = coWHITE;					// Protover 1.
	if (s_ivars.wmode == wmodeANALYZE)
		VSendToEngine("analyze");
	return fTRUE;
}

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

BOOL FCmdQuit(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	return fFALSE;
}

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

BOOL FCmdForce(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	s_ivars.fForce = fTRUE;
	VSendToEngine("abort");
	return fTRUE;
}

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

BOOL FCmdGo(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	s_ivars.fForce = fFALSE;
	s_ivars.wmode = wmodeTHINK;
	VSendToEngine("think %ld %ld", s_ivars.tmMyTime, s_ivars.tmTheirTime);
	return fTRUE;
}

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

BOOL FCmdLevel(char * szCmd, char * rgsz[], int csz)
{
	int	iMoves;
	unsigned	tmMinutes;
	unsigned	tmSeconds;
	unsigned	tmIncr;
	char * sz;

	Assert(csz == 4);
	iMoves = atoi(rgsz[1]);
	sz = rgsz[2];
	tmMinutes = tmSeconds = 0;
	for (; *sz; sz++) {
		if (*sz == ':') {
			for (sz++; *sz; sz++) {
				if (!isdigit(*sz)) {
					VErrorWb("bad 'seconds' value", szCmd);
					return fTRUE;
				}
				tmSeconds = tmSeconds * 10 + *sz - '0';
			}
			break;
		}
		if (!isdigit(*sz)) {
			VErrorWb("bad 'minutes' value", szCmd);
			return fTRUE;
		}
		tmMinutes = tmMinutes * 10 + *sz - '0';
	}
	tmIncr = atol(rgsz[3]);
	VSendToEngine("level %d %ld %ld",
		iMoves, (tmMinutes * 60 + tmSeconds) * 1000, tmIncr * 1000);
	return fTRUE;
}

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

BOOL FCmdSt(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 2);
	VSendToEngine("st %lu", atol(rgsz[1]) * 1000);
	return fTRUE;
}

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

BOOL FCmdSd(char * szCmd, char * rgsz[], int csz)
{
	int	plyDepth;

	Assert(csz == 2);
	plyDepth = atoi(rgsz[1]);
	if (plyDepth < 1)
		plyDepth = 1;
	VSendToEngine("sd %d", plyDepth);
	return fTRUE;
}

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

BOOL FCmdTime(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 2);
	s_ivars.tmMyTime = atol(rgsz[1]) * 10;
	return fTRUE;
}

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

BOOL FCmdOtim(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 2);
	s_ivars.tmTheirTime = atol(rgsz[1]) * 10;
	return fTRUE;
}

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

BOOL FCmdHook(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("movenow");
	return fTRUE;
}

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

BOOL FCmdPing(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 2);
	VSendToWinboard("pong %s", rgsz[1]);
	return fTRUE;
}

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

BOOL FCmdDraw(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("draw");
	return fTRUE;
}

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

BOOL FCmdResult(char * szCmd, char * rgsz[], int csz)
{
	//	This can have any number of parameters, because the comment field is
	//	not handled properly.
	//
	VSendToEngine("%s", szCmd);
	return fTRUE;
}

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

BOOL FCmdSetboard(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 7);
	VSendToEngine("setboard %s %s %s %s %s %s",
		rgsz[1], rgsz[2], rgsz[3], rgsz[4], rgsz[5], rgsz[6]);
	s_ivars.coOnMove = (rgsz[2][0] == 'w') ?	// Protover 1.
		coWHITE : coBLACK;
	if (s_ivars.wmode == wmodeANALYZE)
		VSendToEngine("analyze");
	return fTRUE;
}

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

BOOL FCmdHint(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("hint");
	return fTRUE;
}

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

BOOL FCmdBk(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("bk");
	return fTRUE;
}

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

BOOL FCmdUndo(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("undo");
	s_ivars.coOnMove ^= 1;					// Protover 1.
	if (s_ivars.wmode == wmodeANALYZE)
		VSendToEngine("analyze");
	return fTRUE;
}

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

BOOL FCmdRemove(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("remove");
	return fTRUE;
}

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

BOOL FCmdHard(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("ponder");
	return fTRUE;
}

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

BOOL FCmdEasy(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("noponder");	// This will cause a current search to abort.
	return fTRUE;
}

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

BOOL FCmdPost(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("post");
	return fTRUE;
}

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

BOOL FCmdNopost(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("nopost");
	return fTRUE;
}

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

BOOL FCmdAnalyze(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	s_ivars.fForce = fFALSE;
	s_ivars.wmode = wmodeANALYZE;
	VSendToEngine("analyze");
	return fTRUE;
}

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

//	Exit analysis, not exit the whole thing.  All this does is turn the
//	engine off.

BOOL FCmdExit(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	s_ivars.wmode = wmodeTHINK;
	VSendToEngine("abort");
	return fTRUE;
}

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

BOOL FCmdComputer(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	VSendToEngine("computer");
	return fTRUE;
}

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

BOOL FCmdIcs(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 2);
	VSendToEngine(szCmd);
	return fTRUE;
}

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

BOOL FCmdRating(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 3);
	VSendToEngine("rating %ld %ld", atol(rgsz[1]), atol(rgsz[2]));
	return fTRUE;
}

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

BOOL FCmdName(char * szCmd, char * rgsz[], int csz)
{
	VSendToEngine("%s", szCmd);
	return fTRUE;
}

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

//	I spit out an error message if a few of the weirder functions are
//	encountered, including a few that I explicitly turn off with "feature"
//	commands.

BOOL FCmdUnimplemented(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz >= 0);
	VErrorWb("Unimplemented", rgsz[0]);
	return fTRUE;
}

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

//	This section is for arcane protover 1 garbage.

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

//	This one just turns edit mode on, and gets us ready to accept pieces.

BOOL FCmdEdit(char * szCmd, char * rgsz[], int csz)
{
	int	i;

	Assert(csz == 1);
	s_ivars.fEditing = fTRUE;
	s_ivars.coEditing = coWHITE;
	for (i = 0; i < csqMAX; i++) {
		s_ivars.argpcco[i].co = coMAX;
		s_ivars.argpcco[i].pc = pcMAX;
	}
	return fTRUE;
}

//	I need this to avoid a lot of ugly code.

char const s_argbPcWb[coMAX][pcMAX] = {
	'P',	'N',	'B',	'R',	'Q',	'K',
	'p',	'n',	'b',	'r',	'q',	'k',
};

//	I need a few defines for critical squares (64 square board, not 128).

#define	isqA1	0
#define	isqE1	4
#define	isqH1	7
#define	isqA8	56
#define	isqE8	60
#define	isqH8	63

//	This ends edit mode.  I convert the internal piece array into a FEN and
//	spit it at the engine.  The whole reason this is so nasty and awful is
//	that I have to deal with the color to move.  Winboard protover 1 expected
//	you to keep track of that yourself, although it sometimes sends "white"
//	and "black" commands whose purpose seems to be completely without point.

BOOL FCmdDot(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	if (s_ivars.fEditing) {
		char	aszFen[256];
		char *	sz = aszFen;
		BOOL	fAllowWhiteOO;
		BOOL	fAllowWhiteOOO;
		BOOL	fAllowBlackOO;
		BOOL	fAllowBlackOOO;
		int	rnk;
		int	fil;
		int	isq;

		for (rnk = 7; rnk >= 0; rnk--) {
			int	csqEmpty;

			if (rnk != 7)
				*sz++ = '/';
			csqEmpty = 0;
			for (fil = 0; fil < 8; fil++) {
				isq = rnk * 8 + fil;

				if (s_ivars.argpcco[isq].pc != pcMAX) {
					if (csqEmpty) {
						*sz++ = csqEmpty + '0';
						csqEmpty = 0;
					}
					*sz++ = s_argbPcWb[s_ivars.argpcco[isq].co]
						[s_ivars.argpcco[isq].pc];
				} else
					csqEmpty++;
			}
			if (csqEmpty)
				*sz++ = csqEmpty + '0';
		}
		*sz++ = ' ';
		*sz++ = (s_ivars.coOnMove == coWHITE) ? 'w' : 'b';
		*sz++ = ' ';
		fAllowWhiteOO = fAllowWhiteOOO = fFALSE;
		fAllowBlackOO = fAllowBlackOOO = fFALSE;
		if ((s_ivars.argpcco[isqE1].pc == pcKING) &&
			(s_ivars.argpcco[isqE1].co == coWHITE)) {
			if ((s_ivars.argpcco[isqH1].pc == pcROOK) &&
				(s_ivars.argpcco[isqH1].co == coWHITE))
				fAllowWhiteOO = fTRUE;
			if ((s_ivars.argpcco[isqA1].pc == pcROOK) &&
				(s_ivars.argpcco[isqA1].co == coWHITE))
				fAllowWhiteOOO = fTRUE;
		}
		if ((s_ivars.argpcco[isqE8].pc == pcKING) &&
			(s_ivars.argpcco[isqE8].co == coBLACK)) {
			if ((s_ivars.argpcco[isqH8].pc == pcROOK) &&
				(s_ivars.argpcco[isqH8].co == coBLACK))
				fAllowBlackOO = fTRUE;
			if ((s_ivars.argpcco[isqA8].pc == pcROOK) &&
				(s_ivars.argpcco[isqA8].co == coBLACK))
				fAllowBlackOOO = fTRUE;
		}
		if (fAllowWhiteOO)
			*sz++ = 'K';
		if (fAllowWhiteOOO)
			*sz++ = 'Q';
		if (fAllowBlackOO)
			*sz++ = 'k';
		if (fAllowBlackOOO)
			*sz++ = 'q';
		if (*(sz - 1) == ' ')
			*sz++ = '-';
		*sz++ = ' ';
		*sz++ = '-';
		*sz++ = ' ';
		*sz++ = '0';
		*sz++ = ' ';
		*sz++ = '1';
		*sz = '\0';
		s_ivars.fEditing = fFALSE;
		VSendToEngine("setboard %s", aszFen);
		if (s_ivars.wmode == wmodeANALYZE)
			VSendToEngine("analyze");
	} else
		VSendToEngine("status");
	return fTRUE;
}

//	Change colors.

BOOL FCmdC(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	if (s_ivars.fEditing)
		s_ivars.coEditing ^= 1;
	return fTRUE;
}

//	In editing mode, we will get a "white" right after entering edit mode.  I
//	can only surmise that this means that we're placing white pieces.  I could
//	ignore this entirely, I think, but I might as well implement it.

BOOL FCmdWhite(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	if (s_ivars.fEditing)
		s_ivars.coEditing = coWHITE;
	return fTRUE;
}

//	See the comment for "white".  I don't expect to get this, but who knows.

BOOL FCmdBlack(char * szCmd, char * rgsz[], int csz)
{
	Assert(csz == 1);
	if (s_ivars.fEditing)
		s_ivars.coEditing = coBLACK;
	return fTRUE;
}

//	This is called when I'm in edit mode and get something that I don't
//	recognize as a valid command.  It eats syntax such as "Pa4".  "xa4" is
//	defined but I won't get it.  I implement it anyway.

BOOL FEditCmd(char * rgsz[], int csz)
{
	int	pc;

	Assert(csz == 1);
	for (pc = pcPAWN; pc < pcMAX; pc++)
		if (rgsz[0][0] == s_argbPcWb[coWHITE][pc]) {
			int	isq;

lblSet:		if ((rgsz[0][1] < 'a') || (rgsz[0][1] > 'h'))
				return fFALSE;
			if ((rgsz[0][2] < '1') || (rgsz[0][2] > '8'))
				return fFALSE;
			if (rgsz[0][3] != '\0')
				return fFALSE;
			isq = (rgsz[0][1] - 'a') + (rgsz[0][2] - '1') * 8;
			s_ivars.argpcco[isq].pc = pc;
			s_ivars.argpcco[isq].co = s_ivars.coEditing;
			return fTRUE;
		}
	if (rgsz[0][0] == 'x') {	// We won't get this, but it's defined.
		pc = pcMAX;
		goto lblSet;
	}
	return fFALSE;
}

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

typedef	struct	tagCMD {
	char * sz;
	BOOL	fInitFirst;
	int	cszMin;				// I need at least this many params.
	int	cszMax;				// I need at most this many params (-1 for any).
	BOOL (* pfn)(char * szCmd, char * rgsz[], int csz);
}	CMD, * PCMD;

CMD const c_argcmd[] = {
	"protover",		fFALSE,	2,	2,		FCmdProtover,
//	"protover",		fFALSE,	2,	2,		NULL,
	"xboard",		fFALSE,	1,	1,		FCmdXboard,
	"accepted",		fTRUE,	0,	256,	NULL,
	"rejected",		fTRUE,	0,	256,	NULL,
	"new",			fTRUE,	1,	1,		FCmdNew,
	"variant",		fTRUE,	0,	256,	NULL,
	"quit",			fTRUE,	1,	1,		FCmdQuit,
	"random",		fTRUE,	0,	256,	NULL,
	"force",		fTRUE,	1,	1,		FCmdForce,
	"go",			fTRUE,	1,	1,		FCmdGo,
	"playother",	fTRUE,	0,	256,	FCmdUnimplemented,
	"white",		fTRUE,	1,	1,		FCmdWhite,				// Protover 1.
	"black",		fTRUE,	1,	1,		FCmdBlack,				// Protover 1.
	"level",		fTRUE,	4,	4,		FCmdLevel,
	"st",			fTRUE,	2,	2,		FCmdSt,
	"sd",			fTRUE,	2,	2,		FCmdSd,
	"time",			fTRUE,	2,	2,		FCmdTime,
	"otim",			fTRUE,	0,	256,	FCmdOtim,
	"usermove",		fTRUE,	0,	256,	FCmdUnimplemented,
	"?",			fTRUE,	1,	1,		FCmdHook,
	".",			fTRUE,	1,	1,		FCmdDot,				// Protover 1.
	"c",			fTRUE,	1,	1,		FCmdC,					// Protover 1.
	"#",			fTRUE,	0,	256,	NULL,					// Protover 1.
	"ping",			fTRUE,	2,	2,		FCmdPing,
	"draw",			fTRUE,	1,	1,		FCmdDraw,
	"result",		fTRUE,	0,	256,	FCmdResult,
	"setboard",		fTRUE,	7,	7,		FCmdSetboard,
	"edit",			fTRUE,	1,	1,		FCmdEdit,				// Protover 1.
	"hint",			fTRUE,	1,	1,		FCmdHint,
	"bk",			fTRUE,	1,	1,		FCmdBk,
	"undo",			fTRUE,	1,	1,		FCmdUndo,
	"remove",		fTRUE,	1,	1,		FCmdRemove,
	"hard",			fTRUE,	1,	1,		FCmdHard,
	"easy",			fTRUE,	1,	1,		FCmdEasy,
	"post",			fTRUE,	1,	1,		FCmdPost,
	"nopost",		fTRUE,	1,	1,		FCmdNopost,
	"analyze",		fTRUE,	1,	1,		FCmdAnalyze,
	"exit",			fTRUE,	1,	1,		FCmdExit,
	"name",			fTRUE,	2,	256,	FCmdName,
	"rating",		fTRUE,	3,	3,		FCmdRating,
	"computer",		fTRUE,	1,	1,		FCmdComputer,
	"pause",		fTRUE,	0,	256,	FCmdUnimplemented,
	"resume",		fTRUE,	0,	256,	FCmdUnimplemented,
	"ics",			fTRUE,	2,	2,		FCmdIcs,
	NULL,
};

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

void VExecuteMove(char * rgsz[], int csz)
{
	Assert(csz >= 1);
	s_ivars.coOnMove ^= 1;						// Protover 1.
	if (s_ivars.fForce)
		VSendToEngine("move %s", rgsz[0]);
	else
		switch (s_ivars.wmode) {
		case wmodeANALYZE:
			VSendToEngine("analyze %s", rgsz[0]);
			break;
		case wmodeTHINK:
			VSendToEngine("think %ld %ld %s",
				s_ivars.tmMyTime, s_ivars.tmTheirTime, rgsz[0]);
			break;
		}
}

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

//	This is the input thread.  The program will spin in here until it gets a
//	command that tells it to exit.

//	The engine is supposed to be initialized in response to the "protover"
//	command from Winboard.  If the user starts Gerbil from the command line,
//	or if an old version of Winboard is used, I am not going to get the
//	"protover" command.  So what I do is check for a line from the user that
//	is going to make me do something (a move, or a command other than the
//	"protover" command), and initialize at that point if I haven't already.

//	Note that the "xboard" command is not hooked up, or that would cause the
//	engine to initialize.  If you hook this command up, you have some changes
//	to make.

void VReadFromWinboard(void)
{
	for (;;) {
		char	aszBuf[1024];
		char	aszVec[1024];
		char *	argsz[256];
		int	csz;

		if (fgets(aszBuf, sizeof(aszBuf), stdin) != aszBuf)
			break;
		VStripWb(aszBuf);
		strcpy(aszVec, aszBuf);
		csz = CszVectorizeWb(aszVec, argsz);
		if (csz != 0)
			if ((isdigit(argsz[0][1])) && (isdigit(argsz[0][3]))) {
				if (!s_ivars.fDidInitEngine) {
					if (!FInitEngineThread(0, NULL))
						return;
					s_ivars.fDidInitEngine = fTRUE;
				}
				VExecuteMove(argsz, csz);
			} else {
				CMD const * pcmd;

				for (pcmd = c_argcmd; pcmd->sz != NULL; pcmd++) {
					if (strcmp(pcmd->sz, argsz[0]))
						continue;
					if (pcmd->pfn == NULL)
						break;
					//
					//	Check for some dummy cases.  These might really happen
					//	if the user starts the program from the command-line.
					//
					if (csz < pcmd->cszMin) {
						VErrorWb("too few parameters", aszBuf);
						break;
					}
					if (csz > pcmd->cszMax) {
						VErrorWb("too many parameters", aszBuf);
						break;
					}
					//	If I haven't initialized the engine, check to see if
					//	this is the "protover" command, which is where I am
					//	supposed to do this.  If I get a real command before
					//	"protover", something weird happened, but I can still
					//	handle it.
					//
					if ((!s_ivars.fDidInitEngine) &&
						(pcmd->fInitFirst)) {
						if (!FInitEngineThread(0, NULL))
							return;
						s_ivars.fDidInitEngine = fTRUE;
					}
					if (!(*pcmd->pfn)(aszBuf, argsz, csz))
						return;
					break;
				}
				if ((pcmd->sz == NULL) &&
					((!s_ivars.fEditing) || (!FEditCmd(argsz, csz))))
					VErrorWb("Unknown command", argsz[0]);
			}
	}
}

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

int main(int argc, char * argv[])
{
	s_ivars.wmode = wmodeTHINK;
	s_ivars.fEditing = fFALSE;
	s_ivars.fForce = fFALSE;
	s_ivars.fDidInitEngine = fFALSE;
	s_ivars.tmMyTime = 2000;	// If nobody tells me differently, I am going
								//  to think for 2 seconds per move.
	s_ivars.tmTheirTime = 2000;
	s_ivars.iProtover = 1;
	VPrBanner();
	if (argc > 1) {
		if (!FInitEngineThread(argc, argv))
			return 1;
		s_ivars.fDidInitEngine = fTRUE;
	}
	VReadFromWinboard();
	return 1;
}

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