/* $Id: proto_xboard.cc,v 1.37 2003/11/22 04:55:29 bergo Exp $ */

/*

    eboard - chess client
    http://eboard.sourceforge.net
    Copyright (C) 2000-2002 Felipe Paulo Guazzi Bergo
    bergo@seul.org

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include "eboard.h"
#include "global.h"
#include "network.h"
#include "protocol.h"
#include "chess.h"
#include "status.h"
#include "util.h"
#include "stl.h"
#include "tstring.h"

XBoardProtocol::XBoardProtocol() {
  EngineWhite=0;
  MoveNumber=1;
  MoveBlack=0;
  Variant=REGULAR;
  want_path_pane=1;
  supports_setboard=false;
  timeout_id=-1;
  need_handshake=true; // send xboard/protover 2

  got_init_pos = false;
  just_sent_fen = false;

  timecontrol.setSecondsPerMove(20);

  strcpy(ComputerName,_("Computer"));
  strcpy(EngineCommandLine,"gnuchess");
  strcpy(EngineRunDir,"~/.eboard/eng-out");

  Features.set("*feature*");
  IllegalMove.set("Illegal move*");

  Moved[0].set("move%b%r");
  Moved[1].set("%N.%b...%b%r");
  Moved[2].set("%N%b...%b%r");

  WhiteWin[0].set("1-0 {*}*");
  BlackWin[0].set("0-1 {*}*");
  Drawn[0].set   ("1/2-1/2 {*}*");
  WhiteWin[1].set("result 1-0*");
  BlackWin[1].set("result 0-1*");
  Drawn[1].set   ("result 1/2-1/2*");

  Dialect[0].set("White resigns*");
  Dialect[1].set("Black resigns*");
  Dialect[2].set("White %s*");
  Dialect[3].set("Black %s*");
  Dialect[4].set("Draw*");
  Dialect[5].set("computer mates*");
  Dialect[6].set("opponent mates*");
  Dialect[7].set("computer resigns*");
  Dialect[8].set("game is a draw*");
  Dialect[9].set("checkmate*");

  CurrentPosition.setStartPos();
  LegalityBackup.setStartPos();

  ebm=0;
}

void XBoardProtocol::setInitialPosition(Position *p) {

  initpos=(*p);
  got_init_pos=true;

}

// the "dialect" for game ends
char * XBoardProtocol::xlateDialect(char *s) {
  static char result[128];

  if (Dialect[0].match(s)) { strcpy(result,"0-1 { White resigns }"); return result; }
  if (Dialect[1].match(s)) { strcpy(result,"1-0 { Black resigns }"); return result; }
  if (Dialect[2].match(s)) { strcpy(result,"1-0 { White mates }"); return result; }
  if (Dialect[3].match(s)) { strcpy(result,"0-1 { Black mates }"); return result; }
  if (Dialect[4].match(s)) { strcpy(result,"1/2-1/2 { Draw }"); return result; }
  if (Dialect[5].match(s)) { strcpy(result,EngineWhite?"1-0 { White mates }":"0-1 { Black mates }"); return result; }
  if (Dialect[6].match(s)) { strcpy(result,EngineWhite?"0-1 { Black mates }":"1-0 { White mates }"); return result; }
  if (Dialect[7].match(s)) { strcpy(result,EngineWhite?"0-1 { White resigns }":"1-0 { Black resigns }"); return result; }
  if (Dialect[8].match(s)) { strcpy(result,"1/2-1/2 { Draw }"); return result; }
  if (Dialect[9].match(s)) { strcpy(result,EngineWhite?"1-0 { White mates }":"0-1 { Black mates }"); return result; }
  return 0;
}

void XBoardProtocol::receiveString(char *netstring) {
  ChessGame *refgame;
  char *p;
  char a[64],b[64];
  int i,moved;
  int wc,bc;

  p=xlateDialect(netstring);
  if (p)
    netstring=p;

  if (Features.match(netstring)) {
    parseFeatures();
    goto just_print;
  }

  if (IllegalMove.match(netstring)) {

    if (just_sent_fen)
      if ((CurrentPosition.sidehint && EngineWhite) ||
	  (!CurrentPosition.sidehint && !EngineWhite) ) {
	// engine sent illegal move string during HIS move
	// happens when FEN string is sent to GNU Chess 5 and
	// it doesn't understand the castling permissions
	global.output->append(_("engine claimed illegal move but we didn't move, ignoring it."), global.Colors.TextBright, IM_IGNORE);
	return;
      }

    CurrentPosition=LegalityBackup;
    refgame=getGame();
    backMove();
    refgame->retreat(1);
    
    wc = bc = CLOCK_UNCHANGED;
    if (timecontrol.mode == TC_SPM) {
      if (EngineWhite) bc=0; else wc=0;
    }
    refgame->updatePosition(CurrentPosition, MoveNumber,
			    EngineWhite?1:0, 
			    wc,bc,
			    _("illegal move!"));
    global.status->setText(netstring,15);
    global.BoardList.front()->setCanMove(true);
    return;
  }

  moved=-1;
  for(i=0;i<3;i++)
    if (Moved[i].match(netstring)) {
      moved = i;
      break;
    }

  if (moved>=0) {
    LegalityBackup=CurrentPosition;
    CurrentPosition.SANstring(Moved[moved].getRToken(0),a);
    CurrentPosition.moveAnyNotation(Moved[moved].getRToken(0),
                                    (MoveBlack?BLACK:WHITE),Variant);
    if (EngineWhite)
      sprintf(b,"%d. %s",MoveNumber,a);
    else
      sprintf(b,"%d. ... %s",MoveNumber,a);

    refgame=getGame();

    wc = bc = CLOCK_UNCHANGED;
    if (timecontrol.mode == TC_ICS) {
      if (MoveNumber == 1)
	wc = bc = timecontrol.startValue();
      else
	refgame->incrementActiveClock(timecontrol.value[1]);
    }    
    if (timecontrol.mode == TC_SPM) {
      if (EngineWhite) bc=0; else wc=0;
    }

    refgame->updatePosition(CurrentPosition, MoveNumber,
                            !MoveBlack, 
			    wc, bc,
			    b, true);
    advanceMove();
    global.BoardList.front()->setCanMove(true);
    global.opponentMoved();
    just_sent_fen = false;
    return;
  }

  for(i=0;i<2;i++) {
    if (WhiteWin[i].match(netstring)) {
      gameOver(WhiteWin[i],WHITE_WIN);
      goto just_print;
    }
    if (BlackWin[i].match(netstring)) {
      gameOver(BlackWin[i],BLACK_WIN);
      goto just_print;
    }
    if (Drawn[i].match(netstring)) {
      gameOver(Drawn[i],DRAW);
      goto just_print;
    }
  }
  
 just_print:
  global.output->append(netstring, global.Colors.Engine, IM_IGNORE);
}


void XBoardProtocol::advanceMove() {
  if (MoveBlack)
    MoveNumber++;
  MoveBlack=!MoveBlack;
}

void XBoardProtocol::backMove() {
  if (!MoveBlack)
    MoveNumber--;
  MoveBlack=!MoveBlack;
}

void XBoardProtocol::sendMove(int x1,int y1,int x2,int y2,int prom) {
  ChessGame *refgame;
  char move[6];
  char mymove[12],ppfmm[18]; // [p]retty [p]rint [f]or [m]y [m]ove
  piece pp;
  int wc, bc;

  refgame=getGame();
  LegalityBackup=CurrentPosition;

  pp = prom ? global.promotion->getPiece() : EMPTY;

  CurrentPosition.stdNotationForMove(x1,y1,x2,y2,pp,mymove,refgame->Variant);
  CurrentPosition.moveCartesian(x1,y1,x2,y2,Variant,true);

  if (EngineWhite)
    sprintf(ppfmm,"%d. ... %s",MoveNumber,mymove);
  else
    sprintf(ppfmm,"%d. %s",MoveNumber,mymove);

  wc = bc = CLOCK_UNCHANGED;
  if (timecontrol.mode == TC_ICS) {
    if (MoveNumber == 1)
      wc = bc = timecontrol.startValue();
    else
      refgame->incrementActiveClock(timecontrol.value[1]);
  }
  if (timecontrol.mode == TC_SPM) {
    if (EngineWhite) wc=0; else bc=0;
  }

  refgame->updatePosition(CurrentPosition,
                          MoveNumber,
                          !MoveBlack,
			  wc,bc,
			  ppfmm);
  move[4]=0;
  move[5]=0;
  move[0]='a'+x1;
  move[1]='1'+y1;
  move[2]='a'+x2;
  move[3]='1'+y2;

  if (prom) {
    switch(pp) {
    case QUEEN:   move[4]='q'; break;
    case ROOK:    move[4]='r'; break;
    case BISHOP:  move[4]='b'; break;
    case KNIGHT:  move[4]='n'; break;
    case KING:    move[4]='k'; break;
    }
  }

  global.network->writeLine(move);
  advanceMove();
  global.BoardList.front()->setCanMove(false);
}

void XBoardProtocol::finalize() {
  ChessGame *refgame;
  refgame=getGame();
  if (refgame) {
    refgame->GameNumber=global.nextFreeGameId(XBOARD_GAME+1);
    refgame->endGame("interrupted",UNDEF);
  }
  if (global.network)
    global.network->close();
}

void XBoardProtocol::gameOver(ExtPatternMatcher &pm,GameResult gr,
                                    int hasreason)
{
  char reason[64];
  ChessGame *refgame;  

  if (hasreason) {
    reason[63]=0;
    strncpy(reason,pm.getStarToken(0),64);
  } else
    strcpy(reason,"Game over"); // I can't think of anything better...

  if ( (gr==BLACK_WIN && EngineWhite) ||
       (gr==WHITE_WIN && !EngineWhite) )
    global.gameWon();

  if ( (gr==WHITE_WIN && EngineWhite) ||
       (gr==BLACK_WIN && !EngineWhite) )
    global.gameLost();

  refgame=getGame();
  refgame->endGame(reason,gr);

  if (global.AppendPlayed) {
    if  (refgame->savePGN(global.AppendFile,true)) {
      char z[128];
      sprintf(z,_("Game appended to %s"),global.AppendFile);
      global.status->setText(z,10);
    }
  }

  global.network->close();
}

void XBoardProtocol::resign() {
  global.network->writeLine("resign");
  global.status->setText(_("Resigned."),10);
}

void XBoardProtocol::draw() {
  global.network->writeLine("draw");
  global.status->setText(_("<XBoard Protocol> draw request sent"),20);
}

void XBoardProtocol::adjourn() {
  global.status->setText(_("<XBoard Protocol> Adjourning not supported"),10);
}

void XBoardProtocol::abort() {
  finalize();
  global.status->setText(_("<XBoard Protocol> Session Aborted"),15);
}

void XBoardProtocol::retractMove() {
  ChessGame *ref;
  ref = getGame();
  if (ref && MoveNumber>1) {
    if ((!MoveBlack && EngineWhite)||
	(MoveBlack && !EngineWhite)) {
      global.status->setText(_("You can only retract when it's your turn to move."),15);
    } else {
      Position p;
      char z[64];
      ref->retreat(2);
      p = ref->getLastPosition();
      --MoveNumber;
      memset(z,0,64);
      p.getLastMove().copy(z,63);
      ref->updatePosition(p,MoveNumber,MoveBlack,
			  CLOCK_UNCHANGED, CLOCK_UNCHANGED,z);
      global.network->writeLine("remove");
      global.status->setText(_("Retracted last move."),15);
      CurrentPosition = p;
    }
  }
}

ChessGame * XBoardProtocol::getGame() {
  ChessGame *refgame;
  refgame=global.getGame(XBOARD_GAME);
  if (!refgame) {
    cerr << _("* game not found: ") << XBOARD_GAME << endl;
    exit(5);
  }
  return refgame;
}

int XBoardProtocol::run() {
  createDialog();
  if (!runDialog()) {
    destroyDialog();
    return 0;
  }

  ebm=0;

  readDialog();

  if (ebm!=0)
    global.addEngineBookmark(ebm);

  ebm=0;

  destroyDialog();
  return(loadEngine());
}

int XBoardProtocol::run(EngineBookmark *bm) {  
  loadBookmark(bm);
  return(loadEngine());
}

gboolean xb_initengine_timeout(gpointer data) {
  XBoardProtocol *me;
  me=(XBoardProtocol *) data;

  global.status->setText(_("Initializing engine"),30);

  me->initEngine();
  return FALSE;
}

int XBoardProtocol::loadEngine() {
  PipeConnection *link;
  global.debug("XBoardProtocol","loadEngine",EngineCommandLine);

  if (strlen(EngineRunDir))
    sprintf(FullCommand,"cd %s ; %s",EngineRunDir,EngineCommandLine);
  else
    strcpy(FullCommand,EngineCommandLine);

  link=new PipeConnection("/bin/sh","-c",FullCommand,0,0);

  // only gnu chess 4 has trouble with it
  if (need_handshake)
    link->setHandshake("xboard\nprotover 2\n");

  if (link->open()==0) { 
    global.status->setText(_("Engine started (2 sec timeout)"),30);
    global.network=link;

    // run initEngine after 2 seconds
    timeout_id=gtk_timeout_add(2000,xb_initengine_timeout,(gpointer) this);

    if (global.network->isConnected())
      return 1;
    else {
      global.network=0;      
      dumpGame();
    }
  }

  global.status->setText(_("Failed to run engine."),30);
  delete link;
  return 0;
}

void XBoardProtocol::initEngine() {
  char z[256];
  createGame();
  // xboard/protover already sent in open() call
  global.network->writeLine("nopost");

  if (ThinkAlways)
    global.network->writeLine("hard");
  else
    global.network->writeLine("easy");    

  global.network->writeLine("new");

  timecontrol.toXBoard(z);
  if (z[0])
    global.network->writeLine(z);

  if (MaxDepth > 0) {
    sprintf(z,"sd %d",MaxDepth);
    global.network->writeLine(z);
  }

  lateInit();
  endInit();
}

void XBoardProtocol::endInit() {
  char z[256];
  sprintf(z,_("%s engine started."), ComputerName);
  global.status->setText(z,15);
}

void XBoardProtocol::lateInit() {
  if (got_init_pos) {    

    global.network->writeLine("force");

    setupPosition();

    // the a2a3 thing suggested in the protocol spec 
    // makes GNU chess 4 go nuts

    if (initpos.sidehint)
      global.network->writeLine("white");
    else
      global.network->writeLine("black");

    global.network->writeLine("go");
    
  } else {
    
    if (EngineWhite) {
      global.network->writeLine("white");
      global.network->writeLine("go");
    }
    
  }
}

void XBoardProtocol::setupPosition() {
  char z[256];

  if (!got_init_pos) return;

  if (supports_setboard) {
    string s;

    s="setboard ";
    s+=initpos.getFEN();
    strcpy(z,s.c_str());
    global.network->writeLine(z);    
    just_sent_fen = true;

  } else {
    char assoc[128];
    int i,j;
    piece p;

    global.network->writeLine("edit");
    global.network->writeLine("#");

    assoc[PAWN]  ='P';
    assoc[KNIGHT]='N';
    assoc[BISHOP]='B';
    assoc[ROOK]  ='R';
    assoc[QUEEN] ='Q';
    assoc[KING]  ='K';

    for(i=0;i<8;i++)
      for(j=0;j<8;j++) {
	p=initpos.getPiece(i,j);
	if ((p&COLOR_MASK) == WHITE) {
	  sprintf(z,"%c%c%d",assoc[p&PIECE_MASK],'a'+i,1+j);
	  global.network->writeLine(z);
	}
      }
    
    global.network->writeLine("c");

    for(i=0;i<8;i++)
      for(j=0;j<8;j++) {
	p=initpos.getPiece(i,j);
	if ((p&COLOR_MASK) == BLACK) {
	  sprintf(z,"%c%c%d",assoc[p&PIECE_MASK],'a'+i,1+j);
	  global.network->writeLine(z);
	}
      }

    global.network->writeLine(".");
    just_sent_fen = true;
  }

}

void XBoardProtocol::createGame() {
  ChessGame *cg;
  Position *pos;
  global.debug("XBoardProtocol","createGame");

  MoveNumber=1;
  MoveBlack=0;

  cg=new ChessGame();
  cg->GameNumber=XBOARD_GAME;
  cg->source = GS_Engine;
  strcpy(cg->PlayerName[EngineWhite?0:1],getComputerName());
  strcpy(cg->PlayerName[EngineWhite?1:0],global.env.User.c_str());

  cg->MyColor=EngineWhite?BLACK:WHITE;

  timecontrol.toString(cg->info0);

  cg->clock_regressive=timecontrol.isRegressive()?1:0;
  cg->Rated=0;
  cg->Variant=Variant;
  global.appendGame(cg);
  global.BoardList.front()->reset();
  cg->setBoard(global.BoardList.front());
  global.BoardList.front()->setGame(cg);
  global.BoardList.front()->pop();
  global.BoardList.front()->setCanMove(true);
  global.BoardList.front()->repaint();
  if (EngineWhite)
    global.BoardList.front()->setFlipped(true);
  cg->acknowledgeInfo();
  cg->fireWhiteClock(timecontrol.startValue(),
		     timecontrol.startValue());

  pos=new Position();

  if (got_init_pos) {
    (*pos)=initpos;
    CurrentPosition = initpos;
    LegalityBackup  = initpos;
    MoveBlack = pos->sidehint ? 0 : 1;
  }

  cg->updatePosition(*pos,1,pos->sidehint ? 0:1,
		     timecontrol.startValue(),
		     timecontrol.startValue(),
		     "0. startpos");
  delete pos;

  global.gameStarted();
}

void XBoardProtocol::dumpGame() {
  ChessGame *cg;

  cg=global.getGame(XBOARD_GAME);
  if (!cg) return;

  if (!cg->isOver()) cg->endGame("*",UNDEF);
  global.BoardList.front()->reset();

}

void XBoardProtocol::parseFeatures() {
  ExtPatternMatcher sf,nf;
  ChessGame *cg;
  char *cand,*p;
  int n;
  bool gotdone=false;

  sf.set("%b%s=\"*\"*");
  nf.set("%b%s=%n*");
  cand=Features.getStarToken(1);
  
  while(1) {
    if (sf.match(cand)) {
      p=sf.getSToken(0);
      if (!strcmp(p,"myname")) {
	ComputerName[63]=0;
	strncpy(ComputerName,sf.getStarToken(0),64);
	global.status->setText(ComputerName,10);
	cg=global.getGame(XBOARD_GAME);
	if (cg) {
	  strncpy(cg->PlayerName[EngineWhite?0:1],ComputerName,64);
	  cg->acknowledgeInfo();
	}
      }
      cand=sf.getStarToken(1);
      continue;
    }
    if (nf.match(cand)) {
      p=nf.getSToken(0);

      if (!strcmp(p,"setboard")) {
	n=atoi(nf.getNToken(0));
	if (n) supports_setboard=true;
      }

      if (!strcmp(p,"done")) {
	n=atoi(nf.getNToken(0));
	if (n)
	  gotdone=true;
	else {
	  // engine asked more time
	  global.status->setText(_("Engine asked more time to startup, waiting."),120);
	  if (timeout_id >= 0)
	    gtk_timeout_remove(timeout_id);
	  
	  // 1 hour, as per Mann's doc
	  timeout_id=gtk_timeout_add(3600000, xb_initengine_timeout,
				     (gpointer) this);
	}
	
      }

      cand=nf.getStarToken(0);
      continue;
    }
    break;
  }

  if (gotdone && timeout_id >= 0) {
    gtk_timeout_remove(timeout_id);
    timeout_id=-1;
    global.status->setText(_("Engine loaded."),20);
    initEngine();
  }
}

char * XBoardProtocol::getDialogName() {
  return(_("Play against Engine"));
}

char * XBoardProtocol::getComputerName() {
  return(ComputerName);
}

// ----- the bloated world of GUI -------

void XBoardProtocol::createDialog() {
  GtkWidget *v,*v2;
  GtkWidget *cfr,*rd[2],*ok,*cancel,*bhb,*hs;
  GtkWidget *f2,*tabl,*l2,*tch,*tce,*tl1,*tl2,*te1,*tv;
  GSList *rg;  
  char z[64];

  GtkWidget *bp_l,*bp_v, *pp_l=0, *pp_v=0;
  GtkWidget *h3=0,*l3=0,*path3,*b31,*l31,*h4=0,*l4=0,*path4;

  eng_dlg=gtk_window_new(GTK_WINDOW_DIALOG);
  gtk_window_set_title(GTK_WINDOW(eng_dlg),getDialogName());
  gtk_window_set_position(GTK_WINDOW(eng_dlg),GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(eng_dlg),6);
  gtk_widget_realize(eng_dlg);

  v=gtk_vbox_new(FALSE,0);
  gtk_container_add(GTK_CONTAINER(eng_dlg),v);

  eng_book=gtk_notebook_new();
  gtk_box_pack_start(GTK_BOX(v),eng_book,TRUE,TRUE,0);
  
  // basic pane
  bp_v=gtk_vbox_new(FALSE,0);
  bp_l=gtk_label_new(_("Side & Time"));
  gtk_container_set_border_width(GTK_CONTAINER(bp_v),4);
  cfr=gtk_frame_new(_("Side Selection"));
  gtk_frame_set_shadow_type(GTK_FRAME(cfr),GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(bp_v),cfr,FALSE,TRUE,4);
  v2=gtk_vbox_new(TRUE,2);
  gtk_container_add(GTK_CONTAINER(cfr),v2);
  rd[0]=grnew( 0, _("Human White vs. Computer Black") );
  rg=grg(GTK_RADIO_BUTTON(rd[0]));
  rd[1]=grnew(rg, _("Computer White vs. Human Black") );
  gtset(GTK_TOGGLE_BUTTON(rd[0]), TRUE);
  gtk_box_pack_start(GTK_BOX(v2),rd[0],FALSE,TRUE,2);
  gtk_box_pack_start(GTK_BOX(v2),rd[1],FALSE,TRUE,2);
  // time per move
  f2=gtk_frame_new(_("Time Control"));
  gtk_frame_set_shadow_type(GTK_FRAME(f2),GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(bp_v),f2,FALSE,TRUE,4);  
  tv=gtk_vbox_new(FALSE,2);
  gtk_container_add(GTK_CONTAINER(f2),tv);

  tch = gtk_hbox_new(FALSE,2);
  gtk_box_pack_start(GTK_BOX(tv),tch,TRUE,TRUE,0);

  timecontrol.toString(z);
  l2=gtk_label_new(z);  
  gtk_box_pack_start(GTK_BOX(tch),l2,TRUE,TRUE,0);
  
  tce = gtk_button_new_with_label(_("Time Control..."));
  gtk_box_pack_start(GTK_BOX(tch),tce,FALSE,TRUE,0);

  tabl=gtk_table_new(2,2,FALSE);
  gtk_container_set_border_width(GTK_CONTAINER(tabl),4);
  gtk_table_set_row_spacings(GTK_TABLE(tabl),2);
  gtk_table_set_col_spacings(GTK_TABLE(tabl),2);
  gtk_box_pack_start(GTK_BOX(tv),tabl,FALSE,FALSE,2);

  tl2=gtk_label_new(_("Depth Limit:"));
  te1=gtk_entry_new();
  gtk_entry_set_text(GTK_ENTRY(te1),"0");
  gtk_table_attach_defaults(GTK_TABLE(tabl),tl2,0,1,0,1);
  gtk_table_attach_defaults(GTK_TABLE(tabl),te1,1,2,0,1);

  tl1=gtk_label_new(_("Set depth limit to 0 to use the engine's default."));
  gtk_box_pack_start(GTK_BOX(tv),tl1,FALSE,FALSE,2);

  think=gtk_check_button_new_with_label(_("Think on opponent's time"));
  gtk_table_attach_defaults(GTK_TABLE(tabl),think,0,2,1,2);
  gtset(GTK_TOGGLE_BUTTON(think), 1);

  bookmark=gtk_check_button_new_with_label(_("Add to Peer/Engine Bookmarks menu"));
  gtk_box_pack_start(GTK_BOX(v),bookmark,FALSE,FALSE,2);
  gtset(GTK_TOGGLE_BUTTON(bookmark), 1);

  Gtk::show(bookmark,think,te1,tl2,tl1,tabl,tv,f2,tch,l2,tce,rd[1],rd[0],
	    v2,cfr,0);
  gtk_notebook_append_page(GTK_NOTEBOOK(eng_book),bp_v,bp_l);
  Gtk::show(bp_v,bp_l,0);

  // path pane
  if (want_path_pane) {
    pp_v=gtk_vbox_new(FALSE,0);
    pp_l=gtk_label_new(_("Engine Command"));
    gtk_container_set_border_width(GTK_CONTAINER(pp_v),4);
    h3=gtk_hbox_new(FALSE,0);
    l3=gtk_label_new(_("Engine command line"));
    gtk_box_pack_start(GTK_BOX(pp_v),h3,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(h3),l3,FALSE,FALSE,0);
  }
  path3=gtk_entry_new();
  gtk_entry_set_text(GTK_ENTRY(path3),EngineCommandLine);  
  if (want_path_pane) {
    gtk_box_pack_start(GTK_BOX(pp_v),path3,FALSE,TRUE,2);
    h4=gtk_hbox_new(FALSE,0);
    l4=gtk_label_new(_("Directory to run from (e.g., where book files are)"));
    gtk_box_pack_start(GTK_BOX(pp_v),h4,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(h4),l4,FALSE,FALSE,0);
  }
  path4=gtk_entry_new();
  gtk_entry_set_text(GTK_ENTRY(path4),EngineRunDir);
  if (want_path_pane) {
    gtk_box_pack_start(GTK_BOX(pp_v),path4,FALSE,TRUE,2);
    b31=gtk_hbox_new(FALSE,0);
    l31=gtk_label_new(_("The engine will be run with\n/bin/sh -c 'cd directory ; command line'"));
    gtk_label_set_justify(GTK_LABEL(l31),GTK_JUSTIFY_LEFT);
    gtk_box_pack_start(GTK_BOX(b31),l31,FALSE,FALSE,2);
    gtk_box_pack_start(GTK_BOX(pp_v),b31,FALSE,FALSE,2);

    Gtk::show(h3,l3,l31,b31,path3,h4,l4,path4,0);
    gtk_notebook_append_page(GTK_NOTEBOOK(eng_book),pp_v,pp_l);
    Gtk::show(pp_v,pp_l,0);
  }

  // bottom box
  hs=gtk_hseparator_new();
  gtk_box_pack_start(GTK_BOX(v),hs,FALSE,TRUE,4);

  bhb=gtk_hbutton_box_new();
  gtk_button_box_set_layout(GTK_BUTTON_BOX(bhb), GTK_BUTTONBOX_END);
  gtk_button_box_set_spacing(GTK_BUTTON_BOX(bhb), 5);
  gtk_box_pack_start(GTK_BOX(v),bhb,FALSE,FALSE,4);
  ok=gtk_button_new_with_label    (_("OK"));
  GTK_WIDGET_SET_FLAGS(ok,GTK_CAN_DEFAULT);
  cancel=gtk_button_new_with_label(_("Cancel"));
  GTK_WIDGET_SET_FLAGS(cancel,GTK_CAN_DEFAULT);
  gtk_box_pack_start(GTK_BOX(bhb),ok,TRUE,TRUE,0);
  gtk_box_pack_start(GTK_BOX(bhb),cancel,TRUE,TRUE,0);
  gtk_widget_grab_default(ok);

  Gtk::show(ok,cancel,bhb,hs,v,0);

  gtk_signal_connect(GTK_OBJECT(ok), "clicked",
                     GTK_SIGNAL_FUNC(xboard_eng_ok),this);
  gtk_signal_connect(GTK_OBJECT(cancel), "clicked",
                     GTK_SIGNAL_FUNC(xboard_eng_cancel),this);
  gtk_signal_connect(GTK_OBJECT(eng_dlg), "delete_event",
                     GTK_SIGNAL_FUNC(xboard_eng_delete),0);

  gtk_signal_connect(GTK_OBJECT(tce), "clicked",
                     GTK_SIGNAL_FUNC(xboard_edit_time),
		     (XBoardProtocol *) this);

  gshow(eng_book);
  gtk_notebook_set_page(GTK_NOTEBOOK(eng_book),0);

  engctl_lbl=l2;
  engctl_ply=te1;
  engctl_ewhite=rd[1];
  engctl_engcmd=path3;
  engctl_engdir=path4;

  if (got_init_pos) {
    if (initpos.sidehint) // white to move
      gtset(GTK_TOGGLE_BUTTON(rd[1]), TRUE);
    else
      gtset(GTK_TOGGLE_BUTTON(rd[0]), TRUE);

    gtk_widget_set_sensitive(rd[0],FALSE);
    gtk_widget_set_sensitive(rd[1],FALSE);
  }
}

void XBoardProtocol::update() {
  char z[64];
  timecontrol.toString(z);
  gtk_label_set_text(GTK_LABEL(engctl_lbl), z);
  gtk_widget_queue_resize(engctl_lbl);
}

void XBoardProtocol::readDialog() {

  EngineWhite=0;
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(engctl_ewhite)))
    EngineWhite=1;
  MaxDepth=atoi(gtk_entry_get_text(GTK_ENTRY(engctl_ply)));
  strncpy(EngineCommandLine,gtk_entry_get_text(GTK_ENTRY(engctl_engcmd)),512);
  strncpy(EngineRunDir,gtk_entry_get_text(GTK_ENTRY(engctl_engdir)),256);
  ThinkAlways=(bool)gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(think));

  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bookmark))) {
    ebm=new EngineBookmark();
    ebm->timecontrol =timecontrol;
    ebm->maxply      =MaxDepth;
    ebm->humanwhite  =1-EngineWhite;
    ebm->think       =ThinkAlways?1:0;
    ebm->directory   =EngineRunDir;
    ebm->cmdline     =EngineCommandLine;
    ebm->mode        =REGULAR;
    ebm->proto       =0; // base xboard protocol

    makeBookmarkCaption();

    // addition is done in run() to allow all virtual versions of this
    // to run before doing it

  } else {
    ebm=0; // derivated classes should check ebm!=0 before doing oddities
  }
}

void XBoardProtocol::makeBookmarkCaption() {
  tstring t,u;
  string x, *p;
  char caption[1024], z[128];

  if (!ebm) return;

  t.set(EngineCommandLine);
  x=EngineCommandLine;
  if (x.empty()) x="<blank?>";
  
  p=t.token(" ");
  if (p != 0) {
    u.set(*p);
    while( (p=u.token("/")) != 0 )
      x=*p;
  }
  
  ebm->timecontrol.toString(z);
  sprintf(caption,_("Play %s as %s vs. %s (%s, maxdepth %d, think always: %s)"),
	  ChessGame::variantName(ebm->mode),
	  ebm->humanwhite?_("White"):
	                  _("Black"),
	  x.c_str(),
	  z,
	  ebm->maxply,
	  ebm->think?_("yes"):
	             _("no"));
  ebm->caption = caption;
}

void XBoardProtocol::loadBookmark(EngineBookmark *bm) {
  timecontrol = bm->timecontrol;
  MaxDepth    = bm->maxply;
  ThinkAlways = (bm->think!=0);
  EngineWhite = 1-bm->humanwhite;
  Variant     = bm->mode;
  strcpy(EngineRunDir, bm->directory.c_str() );
  strcpy(EngineCommandLine, bm->cmdline.c_str() );

  if (got_init_pos)
    EngineWhite=initpos.sidehint ? 1 : 0;
}

int XBoardProtocol::runDialog() {
  GotResult=0;
  gshow(eng_dlg);
  gtk_grab_add(eng_dlg);
  while(!GotResult)
    global.WrappedMainIteration();
  gtk_grab_remove(eng_dlg);
  return(GotResult==1);
}

void XBoardProtocol::destroyDialog() {
  gtk_widget_destroy(eng_dlg);
}

void xboard_edit_time(GtkWidget *w,gpointer data) {
  XBoardProtocol *me = (XBoardProtocol *) data;
  TimeControlEditDialog *dlg;

  dlg = new TimeControlEditDialog(&(me->timecontrol));
  dlg->setUpdateListener( (UpdateInterface *) me);
  dlg->show();
}

void xboard_eng_ok(GtkWidget *w,gpointer data) {
  XBoardProtocol *me;
  me=(XBoardProtocol *)data;
  me->GotResult=1;
}

void xboard_eng_cancel(GtkWidget *w,gpointer data) {
  XBoardProtocol *me;
  me=(XBoardProtocol *)data;
  me->GotResult=-1;
}

gboolean xboard_eng_delete(GtkWidget *w,GdkEvent *e,gpointer data) {
  return TRUE;
}


/* ************************************************** */
/* *********** specific engines ********************* */
/* ************************************************** */

// =============== CRAFTY ============================

CraftyProtocol::CraftyProtocol() : XBoardProtocol() {
  strcpy(ComputerName,"Crafty");
  want_path_pane=0;
}

char * CraftyProtocol::getDialogName() {
  return(_("Play against Crafty"));
}

void CraftyProtocol::readDialog() {
  XBoardProtocol::readDialog();
  resolvePaths();
  sprintf(EngineCommandLine,"crafty bookpath=%s logpath=%s tbpath=%s",
	  BookPath,LogPath,LogPath);
  if (!global.env.Home.empty())
    sprintf(EngineRunDir,"%s/.eboard/craftylog",global.env.Home.c_str());
  else
    strcpy(EngineRunDir,"/tmp");

  if (ebm) {
    ebm->proto     = 1;
    ebm->cmdline   = EngineCommandLine;
    ebm->directory = EngineRunDir;
    makeBookmarkCaption();
  }
}

void CraftyProtocol::initEngine() {
  global.network->writeLine("log off");
  XBoardProtocol::initEngine();
}

// keep this and Documentation/Crafty.txt coherent
void CraftyProtocol::resolvePaths() {
  FileFinder ff;
  char z[256],zz[256],*p;

  // book path
  if (!global.env.Home.empty())
    sprintf(z,"%s/.eboard",global.env.Home.c_str());
  else
    strcpy(z,"/tmp");

  ff.addMyDirectory(".eboard");
  ff.addMyDirectory(".");
  ff.addDirectory("/usr/local/share/eboard");
  ff.addDirectory("/usr/share/eboard");
  ff.addDirectory("/usr/local/share/crafty");
  ff.addDirectory("/usr/share/crafty");

  if (ff.find("book.bin",zz)) {
    p=rindex(zz,'/');
    *p=0;
  } else
    strcpy(zz,z);

  strcpy(BookPath,zz);

  // log path
  if (!global.env.Home.empty())
    sprintf(z,"%s/.eboard/craftylog",global.env.Home.c_str());
  else
    strcpy(z,"/tmp");
  strcpy(LogPath,z);
}

// =============== SJENG ============================

SjengProtocol::SjengProtocol() : XBoardProtocol() {
  strcpy(ComputerName,"Sjeng");
  strcpy(EngineCommandLine,"sjeng");
  want_path_pane=1;
}

void SjengProtocol::initEngine() {
  char z[32];
  createGame();
  global.network->writeLine("xboard");
  global.network->writeLine("protover 2");
  global.network->writeLine("nopost");

  if (ThinkAlways)
    global.network->writeLine("hard");
  else
    global.network->writeLine("easy");

  global.network->writeLine("new");

  switch(Variant) {
  case CRAZYHOUSE: global.network->writeLine("variant crazyhouse"); break;
  case SUICIDE:    global.network->writeLine("variant suicide"); break;
  case LOSERS:     global.network->writeLine("variant losers"); break;
  case GIVEAWAY:   global.network->writeLine("variant giveaway"); break;
  default: z[31]=0; /* nothing! */ break;
  }

  timecontrol.toXBoard(z);
  if (z[0])
    global.network->writeLine(z);

  if (MaxDepth>0) {
    sprintf(z,"sd %d",MaxDepth);
    global.network->writeLine(z);
  }

  lateInit();
  endInit();
}

void SjengProtocol::createDialog() {
  int i;
  GtkWidget *fr,*v,*rd[5],*vl;
  GSList *rg;

  XBoardProtocol::createDialog();

  // add variant pane
  fr=gtk_frame_new(_("Variant"));
  gtk_frame_set_shadow_type(GTK_FRAME(fr),GTK_SHADOW_ETCHED_IN);
  v=gtk_vbox_new(TRUE,2);
  gtk_container_add(GTK_CONTAINER(fr),v);

  rd[0]=grnew( 0, _("Normal Chess") );
  rg=grg(GTK_RADIO_BUTTON(rd[0]));
  rd[1]=grnew(rg, _("Crazyhouse") );
  rg=grg(GTK_RADIO_BUTTON(rd[1]));
  rd[2]=grnew(rg, _("Suicide") );
  rg=grg(GTK_RADIO_BUTTON(rd[2]));
  rd[3]=grnew(rg, _("Losers") );
  rg=grg(GTK_RADIO_BUTTON(rd[3]));
  rd[4]=grnew(rg, _("Giveaway") );

  gtset(GTK_TOGGLE_BUTTON(rd[0]), TRUE);
  for(i=0;i<5;i++) {
    gtk_box_pack_start(GTK_BOX(v),rd[i],FALSE,TRUE,2);
    gshow(rd[i]);
    varbutton[i]=rd[i];
  }
  Gtk::show(v,fr,0);
  vl=gtk_label_new(_("Variant"));
  gshow(vl);
  gtk_notebook_append_page(GTK_NOTEBOOK(eng_book),fr,vl);
  gtk_notebook_set_page(GTK_NOTEBOOK(eng_book),0);
}

void SjengProtocol::readDialog() {
  XBoardProtocol::readDialog();
  if (gtget(GTK_TOGGLE_BUTTON(varbutton[0]))) Variant=REGULAR;
  if (gtget(GTK_TOGGLE_BUTTON(varbutton[1]))) Variant=CRAZYHOUSE;
  if (gtget(GTK_TOGGLE_BUTTON(varbutton[2]))) Variant=SUICIDE;
  if (gtget(GTK_TOGGLE_BUTTON(varbutton[3]))) Variant=LOSERS;
  if (gtget(GTK_TOGGLE_BUTTON(varbutton[4]))) Variant=GIVEAWAY;

  if (ebm) {
    ebm->proto=2;
    ebm->mode=Variant;

    makeBookmarkCaption();
  }
}

char * SjengProtocol::getDialogName() {
  return(_("Play against Sjeng"));
}

void SjengProtocol::sendDrop(piece p,int x,int y) {
  ChessGame *refgame;
  char mymove[12],ppfmm[18]; // [p]retty [p]rint [f]or [m]y [m]ove
  piece clp;

  refgame=getGame();
  LegalityBackup=CurrentPosition;

  sprintf(mymove,"P@%c%c",'a'+x,'1'+y);
  clp=p&PIECE_MASK;
  switch(clp) {
  case PAWN:    mymove[0]='P'; break;
  case ROOK:    mymove[0]='R'; break;
  case KNIGHT:  mymove[0]='N'; break;
  case BISHOP:  mymove[0]='B'; break;
  case QUEEN:   mymove[0]='Q'; break;
  case KING:    mymove[0]='K'; break;
  }
  CurrentPosition.moveDrop(p|(refgame->MyColor),x,y);

  if (EngineWhite)
    sprintf(ppfmm,"%d. ... %s",MoveNumber,mymove);
  else
    sprintf(ppfmm,"%d. %s",MoveNumber,mymove);

  refgame->updatePosition(CurrentPosition, 
                          MoveNumber,!MoveBlack,
			  CLOCK_UNCHANGED,
			  CLOCK_UNCHANGED,
			  ppfmm);

  global.network->writeLine(mymove);
  advanceMove();
  global.BoardList.front()->setCanMove(false);
}

// ==================== GNU CHESS 4 =========================

GnuChess4Protocol::GnuChess4Protocol() : XBoardProtocol() {
  strcpy(ComputerName,"GNU Chess");
  want_path_pane=0;
  need_handshake=false; // sending xboard confuses GNU chess 4
}

void GnuChess4Protocol::initEngine() {
  char z[32];
  createGame();
  global.network->writeLine("nopost");

  global.network->writeLine("hard");
  if (!ThinkAlways)
    global.network->writeLine("easy");

  global.network->writeLine("new");

  timecontrol.toXBoard(z,TCF_GNUCHESS4QUIRK);
  if (z[0])
    global.network->writeLine(z);

  if (MaxDepth > 0) {
    sprintf(z,"depth %d",MaxDepth);
    global.network->writeLine(z);
  }

  lateInit();
  endInit();
}

void GnuChess4Protocol::readDialog() {
  XBoardProtocol::readDialog();
  strcpy(EngineCommandLine,"gnuchessx");
  EngineRunDir[0]=0;

  if (ebm) {
    ebm->proto=3;
    ebm->cmdline=EngineCommandLine;
    ebm->directory=EngineRunDir;
    makeBookmarkCaption();
  }
}

char * GnuChess4Protocol::getDialogName() {
  return(_("Play against GNU Chess 4"));
}
