/* GNU Chess 5.0 - book.c - book code
   Copyright (c) 1999-2002 Free Software Foundation, Inc.

   GNU Chess is based on the two research programs 
   Cobalt by Chua Kong-Sian and Gazebo by Stuart Cracraft.

   GNU Chess 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, or (at your option)
   any later version.

   GNU Chess 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 GNU Chess; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Contact Info: 
     bug-gnu-chess@gnu.org
     cracraft@ai.mit.edu, cracraft@stanfordalumni.org, cracraft@earthlink.net
     lukas@debian.org
*/

// includes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>

#include "util.h"
#include "pgn.h"
#include "book.h"
#include "move.h"
#include "pgn.h"
#include "genmove.h"
#include "polyglot.h"
#include "option.h"
#include "square.h"

/* Only players in the table below are permitted into the opening book 
   from the PGN files. Please expand the table as needed. Generally,
   I would recommend only acknowledged GM's and IM's and oneself, but
   because of the self-changing nature of the book, anything inferior
   will eventually be eliminated through automatic play as long as
   you feed the games the program plays back to itself with "book add pgnfile"
*/
/* TODO: Fix this so the list isn't hardcoded. */

static const char *const trusted_players[] = {
  "Alekhine",
  "Adams",
  "Anand",
  "Anderssen",
  "Andersson",
  "Aronin",
  "Averbakh",
  "Balashov",
  "Beliavsky",
  "Benko",
  "Bernstein",
  "Bird",
  "Bogoljubow",
  "Bolbochan",
  "Boleslavsky",
  "Byrne",
  "Botvinnik",
  "Bronstein",
  "Browne",
  "Capablanca",
  "Chigorin",
  "Christiansen",
  "De Firmian",
  "Deep Blue",
  "Deep Thought",
  "Donner",
  "Dreev",
  "Duras",
  "Euwe",
  "Evans",
  "Filip",
  "Fine",
  "Fischer",
  "Flohr",
  "Furman",
  "Gelfand",
  "Geller",
  "Gereben",
  "Glek",
  "Gligoric",
  "GNU",
  "Golombek",
  "Gruenfeld",
  "Guimard",
  "Hodgson",
  "Ivanchuk",
  "Ivkov",
  "Janowsky",
  "Kamsky",
  "Kan",
  "Karpov",
  "Kasparov",
  "Keres",
  "Korchnoi",
  "Kortschnoj",
  "Kotov",
  "Kramnik",
  "Kupreich",
  "Lasker",
  "Lautier",
  "Letelier",
  "Lilienthal",
  "Ljubojevic",
  "Marshall",
  "Maroczy",
  "Mieses",
  "Miles",
  "Morphy",
  "Mueller",     /* Every other German has this name... */
  "Nimzowitsch",
  "Nunn",
  "Opocensky",
  "Pachman",
  "Petrosian",
  "Piket",
  "Pilnik",
  "Pirc",
  "Polgar",
  "Portisch",
  "Psakhis",
  "Ragozin",
  "Reshevsky",
  "Reti",
  "Romanish",
  "Rubinstein",
  "Saemisch",
  "Seirawan",
  "Shirov",
  "Short",
  "Silman",
  "Smyslov",
  "Sokolsky",
  "Spassky",
  "Sveshnikov",
  "Stahlberg",
  "Steinitz",
  "Tal",
  "Tarjan",
  "Tartakower",
  "Timman",
  "Topalov",
  "Torre",
  "Vidmar"

  };

int IsTrustedPlayer(const char *name)
/* Return 1 if name in trusted_players list, else 0 */
{
	int i;
	for (i = 0; i < (sizeof(trusted_players) / sizeof(*trusted_players));
	    	i++) {
		if (strstr(name, trusted_players[i]) != NULL)
			return 1;
       }
	return 0;
}




///* The book which BookBuilder() uses */
//#define BOOKRUN "book.dat"

/*
 * The last byte of magic_str should be the version
 * number of the format, in case we have to change it.
 *
 * Format 0x01 had an index field which is now gone.
 *
 * Format 0x03 uses an additional size header, which 
 * comes directly after the magic string, and has the
 * number of entries as a big-endian encoded uint32_t
 * number. 
 */

#define MAGIC_LENGTH 5
#define MAXMATCH 100

/*
 * We now allocate memory for the book dynamically,
 * according to the size field in the header of the binary
 * book. However, during book building the following value
 * is used.
 */
#define MAX_DIGEST_BITS 20
#define DIGEST_SIZE(book) (1UL << (book)->digest_bits)
#define DIGEST_MASK(book) (DIGEST_SIZE(book) - 1)
/*
 * Initial hash function, relies on the quality of the lower
 * bits of the 64 bit hash function
 */
#define DIGEST_START(book, i,key)		\
  ((i) = (key) & DIGEST_MASK(book))
/*
 * See if there is already a matching entry
 */
#define DIGEST_MATCH(book,i,the_key,the_move)	\
    (   ((the_key) == (book)->bookpos[i].key)		\
    &&  ((the_move)==(book)->bookpos[i].move)  )

#define DIGEST_MATCH_KEY(book,i,the_key)	\
    (   ((the_key) == (book)->bookpos[i].key) )	





/*
 * See if the entry is empty
 */
#define DIGEST_EMPTY(book, i) is_empty(book, i)

/*
 * Check for collision
 */
#define DIGEST_COLLISION(book, i,key,move)				\
    (!DIGEST_MATCH(book, i,key,move) && !DIGEST_EMPTY(book, i))

/*
 * The next macro is used in the case of hash collisions.
 * We use double hashing with the higher bits of the key.
 * In order to have the shift relatively prime to the hash
 * size, we OR by 1.
 */
#define DIGEST_NEXT(book,i,key)					\
  ((i) = ((i) + (((key) >> (book)->digest_bits) | 1)) & DIGEST_MASK(book))


/*
 * This is considered to be the limit for the hash, I chose
 * 95% because it is Monday... No, I am open for suggestions
 * for the right value, I just don't know.
 */
#define DIGEST_LIMIT(book) (0.95 * DIGEST_SIZE(book))


/*
 * This scoring algorithm comes from the "book prefer" option in GnuChess 5.07
 * It is extremely weird and looks more like a bug than anything else.
 * Should we change it to polyglot scoring which is simply 2*wins+draws?
 *
 */

#define SCORE(wins,draws,losses)         \
    (100*(((uint32_t) wins)+(((uint32_t) draws)/2))/\
      (MAX(((uint32_t)wins)+((uint32_t)losses)+((uint32_t)draws),1)) + ((uint32_t)wins)/2);


// constants

//Since the hashcode and format has changed we change the magic

static const char magic_str[] = "GnuCx";
/* Offsets */
static const int wins_off   = 0;
static const int losses_off = 2;
static const int draws_off  = 4;
static const int key_off    = 6;
static const int move_off   = 14;

// types

/*
 * This is the record as it will be written in the binary
 * file in network byte order. HashType is uint64_t. To
 * avoid endianness and padding issues, we do not read or
 * write structs but put the values in an unsigned char array.
 */

typedef unsigned char buf_t[2+2+2+8+4];


book_t Book[1];

// functions

static FILE* BookFileOpen(const char *file);
static int   BookLoad(book_t *book);
static void  BookClear(book_t *book);
static inline int is_empty(book_t *book, uint32_t index);

static void int_to_file(FILE *f, int l, uint64_t r){
    int i,c;
    for(i=0;i<l;i++){
        c=(r>>8*(l-i-1))&0xff;
        fputc(c,f);
    }
}

static void pgentry_to_file(FILE *f, pgentry_t *entry){
    int_to_file(f,8,entry->key);
    int_to_file(f,2,entry->move);
    int_to_file(f,2,entry->score);
    int_to_file(f,4,entry->learn);
}

static int compare_pg(const void* entry1_,const void *entry2_){
    pgentry_t *entry1,*entry2;
    entry1=(pgentry_t *)entry1_;
    entry2=(pgentry_t *)entry2_;
    if(entry1->key<entry2->key){
	return -1;
    }else if(entry1->key>entry2->key){
	return 1;
    }else{
	return 0;
    }
}

static uint8_t gnutopgprom[6]={0, /*no promotion */
			       0, /*impossible */
			       1, /*knight*/
			       2, /*bishop*/
			       3, /*rook*/
			       4};/*queen*/

static int pg_move(int move){
    int move_part,prom_part,t;
    
    t=TOSQ(move);

    if(move & CASTLING){
	switch(t){
	case G1:
	    return MOVE(E1,H1);
	case C1:
	    return MOVE(E1,A1);
	case G8:
	    return MOVE(E8,H8);
	case C8:
	    return MOVE(E8,A8);
	default:
	    Log("Illegal castling move %x",move);
	    return NOMOVE;
	}
    }
    
    move_part=move & 0xFFF;
    prom_part=(move & PROMOTION)>>12;
    
    ASSERT(prom_part<6);
    
    return (gnutopgprom[prom_part]<<12)+move_part;
    
}

void BookConvert(const char *dst_file, const char *src_file){
    int i,j,size;
    book_t book[1];
    pgentry_t *pgentries;
    FILE *f;
    BookInit(book);
    BookSetFile(book,src_file);
    if(BookLoad(book)!=BOOK_SUCCESS){
	OutputConsole("Unable to parse \"%s\"\n",src_file);
	return;
    }
    pgentries=malloc(DIGEST_SIZE(book)*sizeof(pgentry_t));
    j=0;
    for (i = 0; i < DIGEST_SIZE(book); i++) {
	// TODO: check for score!=0. This makes the book more compact.
	// Leave it for now as we are close to a release and we have no time 
	// for validating.
	if (!is_empty(book,i)) {
	    pgentries[j].key=book->bookpos[i].key;    
	    pgentries[j].move=pg_move(book->bookpos[i].move);    
	    pgentries[j].score=SCORE(book->bookpos[i].wins,
				     book->bookpos[i].draws,
				     book->bookpos[i].losses);
	    j++;
	}
    }
    size=j;
    qsort(pgentries,size,sizeof(pgentry_t),compare_pg);
    f=fopen(dst_file,"w+b");
    for(i=0;i<size;i++){
	pgentry_to_file(f,pgentries+i);
    }
    fclose(f);
    Output("Conversion finished\n");
}


void BookAdd (const char *file, const char *bookfile)
/****************************************************************************
 *
 *  To read a game from a PGN file and store out the hash entries to book.
 *
 ****************************************************************************/
{
    int i;
    int ngames = 0;
    time_t t1, t2;
    double et;
    board_t board[1];
    pgn_iter_t pgn_iter[1];
    game_t game[1];
    book_t book[1];
    //int legacy_filter=true;
    //    int legacy_filter=false;
    int legacy_filter;
    int book_depth;
    int addtobook[2]={true,true};
    
    legacy_filter=OptionGetBool(Option,"BookBuilderLegacyFilter");
    if(legacy_filter){
	Output("Warning: using legacy filter\n");
    }

    book_depth=OptionGetInt(Option,"BookBuilderDepth");
    if(book_depth<1000){
	Output("Warning: restricting depth to %d plies\n",book_depth);
    }

    et = 0.0;
    t1 = time(NULL);
    
    GameInit(game);
    BookInit(book);
    BookSetFile(book,bookfile);
    if (BookBuilderOpen(book) != BOOK_SUCCESS){
	return;
    }
    PGNIterInit(pgn_iter);
    PGNIterStart(pgn_iter,file);
    
    while(PGNIterNext(pgn_iter,game)){
	if(legacy_filter){
	    const char *white_title;
	    const char *black_title;
	    addtobook[white]=false;
	    addtobook[black]=false;
	    white_title=game->white_title;
	    black_title=game->black_title;
	    if (white_title &&
	    	(strcmp(white_title, "GM") == 0 ||
	    	 strcmp(white_title, "IM") == 0 ||
	    	 strcmp(white_title, "FM") == 0)){
	    	addtobook[white]=true;
	    }
	    if (black_title &&
	    	(strcmp(black_title, "GM") == 0 ||
	    	 strcmp(black_title, "IM") == 0 ||
	    	 strcmp(black_title, "FM") == 0)){
	    	addtobook[black]=true;
	    }
	    if(game->white_name && IsTrustedPlayer(game->white_name)){
	    	addtobook[white]=true;
	    }
	    if(game->black_name && IsTrustedPlayer(game->black_name)){
	    	addtobook[black]=true;
	    }
	}
	BoardFromGame(board,game,BOARD_FIRST);
	for(i=game->InitialGameCnt;i<game->RealGameCnt;i++){
	    uint32_t move=game->boards[i-game->InitialGameCnt].move & MOVEMASK;
	    if (board->GameCnt > book_depth){ 
		break;
	    }
	    if( (addtobook[board->side]) && (BookBuilder (book, board,move,game->result)!=0)){
	    	break;
	    };
	    MoveMake(board,move);
	}
	ngames++;
	if(ngames%10==0){
	    Output("Games processed: %d\r",ngames);
	}
    }
    
    t2 = time(NULL);
    et += difftime(t2, t1);
    putchar('\n');
    
    /* Handle divide-by-zero problem */
    if (et < 0.5) { et = 1.0; };
    
    Output("Time = %.0f seconds\n", et);
    Output("Games compiled: %d\n",ngames);
    Output("Games per second: %f\n",ngames/et);
    Output("Positions scanned: %d\n",book->newpos+book->existpos);
    Output("Positions per second: %f\n",(book->newpos+book->existpos)/et);
    Output("New & unique added: %d positions\n",book->newpos);
    Output("Duplicates not added: %d positions\n",book->existpos);
    BookBuilderClose(book);
    

}



static int check_magic(FILE *f)
{
  char buf[MAGIC_LENGTH];
  int r;

  r = fread(&buf, 1, MAGIC_LENGTH, f);
  return (r == MAGIC_LENGTH &&
	  memcmp(buf, magic_str, MAGIC_LENGTH) == 0);
}

static int write_magic(FILE *f)
{
  if (MAGIC_LENGTH != fwrite(&magic_str, 1, MAGIC_LENGTH, f)) {
    return BOOK_EIO;
  } else {
    return BOOK_SUCCESS;
  }
}

/* Write and read size information for the binary book */

static int write_size(FILE *f, uint32_t size)
{
  unsigned char sizebuf[4];
  int k;

  for (k = 0; k < 4; k++) {
    sizebuf[k] = (size >> ((3-k)*8)) & 0xff;
  }
  if (1 == fwrite(&sizebuf, sizeof(sizebuf), 1, f)) {
    return BOOK_SUCCESS;
  } else {
    return BOOK_EIO;
  }
}

/* Returns 0 if some read error occurs */

static uint32_t read_size(FILE *rfp)
{
  unsigned char sizebuf[4];
  uint32_t size = 0;
  int k;

  if (1 != fread(&sizebuf, sizeof(sizebuf), 1, rfp)) {
    return 0;
  }
  for (k = 0; k < 4; k++) {
    size = (size << 8) | sizebuf[k];
  }
  return size;
}


static inline int is_empty(book_t *book, uint32_t index)
{
  return 
      book->bookpos[index].move    == 0 &&
      book->bookpos[index].key     == 0 &&
      book->bookpos[index].wins    == 0 &&
      book->bookpos[index].draws   == 0 &&
      book->bookpos[index].losses  == 0;
}




static void buf_to_book(buf_t buf, book_t *book){
    HashType key;
    uint32_t i,move;

    key = ((uint64_t)buf[key_off] << 56)
	| ((uint64_t)buf[key_off+1] << 48)
	| ((uint64_t)buf[key_off+2] << 40)
	| ((uint64_t)buf[key_off+3] << 32)
	| ((uint64_t)buf[key_off+4] << 24)
	| ((uint64_t)buf[key_off+5] << 16)
	| ((uint64_t)buf[key_off+6] << 8)
	| ((uint64_t)buf[key_off+7]);
    
    move=((uint32_t)buf[move_off] << 24)
	| ((uint32_t)buf[move_off+1] << 16)
	| ((uint32_t)buf[move_off+2] << 8)
	| ((uint32_t)buf[move_off+3]);

    /*
     * This is an infinite loop if the hash is 100% full,
     * but other parts should check that this does not happen.
     */
    for (DIGEST_START(book, i, key);
	 DIGEST_COLLISION(book, i, key,move);
	 DIGEST_NEXT(book, i, key))
	/* Skip */
	book->bookhashcollisions++;
    
    book->bookpos[i].wins   += (buf[wins_off]   << 8) | buf[wins_off  +1];
    book->bookpos[i].draws  += (buf[draws_off]  << 8) | buf[draws_off +1];
    book->bookpos[i].losses += (buf[losses_off] << 8) | buf[losses_off+1];
    book->bookpos[i].key = key;
    book->bookpos[i].move = move;
}

static void book_to_buf(book_t *book, buf_t buf, uint32_t index){
    int k;

    for (k=0; k<2; k++) {
	buf[wins_off + k]   = ((book->bookpos[index].wins)   >> ((1-k)*8)) & 0xff;
	buf[draws_off + k]  = ((book->bookpos[index].draws)  >> ((1-k)*8)) & 0xff;
	buf[losses_off + k] = ((book->bookpos[index].losses) >> ((1-k)*8)) & 0xff;
    }
    for (k=0; k<8; k++) {
	buf[key_off + k] = ((book->bookpos[index].key) >> ((7-k)*8)) & 0xff;
    }
    for (k=0; k<4; k++) {
	buf[move_off + k] = ((book->bookpos[index].move) >> ((3-k)*8)) & 0xff;
    }
}

/*
 * Reads an existing binary book from f. The header must
 * already be skipped, when you call this function. The
 * variable digest_bits must be set to the correct value
 * before calling this function. If any book was allocated
 * before, it will be lost.
 */
static int read_book(book_t *book, FILE *rfp){
    buf_t buf;
    ASSERT(book->bookpos==NULL);
    ASSERT(book->bookcnt==0);
    ASSERT(book->bookhashcollisions==0);
    book->bookpos = calloc(DIGEST_SIZE(book), sizeof(struct hashtype));
    if (book->bookpos == NULL) {
	return BOOK_ENOMEM;
    }
    while ( 1 == fread(&buf, sizeof(buf), 1, rfp) ) {
	buf_to_book(buf, book);
	book->bookcnt++;
    }
    return BOOK_SUCCESS;
}


// Obviously only for testing!

void BookShow(book_t *book){
    int i;
    char LANmv[SANSZ];
    for(i=0;i<DIGEST_SIZE(book);i++){
	if(!is_empty(book,i)){
	    MoveToLAN(book->bookpos[i].move,LANmv);
	    OutputConsole("key="U64_FORMAT" move=%s wins=%d losses=%d draws=%d\n",
			  book->bookpos[i].key,
			  LANmv,
			  //			  book->bookpos[i].move,
			  book->bookpos[i].wins,
			  book->bookpos[i].losses,
			  book->bookpos[i].draws);
	}
		      
    }
}
  
/* 
 * Return values are defined in book.h
 */

int BookBuilderOpen(book_t *book)
{
    FILE *rfp, *wfp;
    int res;

    rfp=BookFileOpen(book->filename);

    if (rfp != NULL) {
	Output("Opened existing book!\n");
	/*
	 * We have to read the size header, but in book building we
	 * use the maximum-sized hash table, so we discard the value.
	 */
	book->digest_bits = MAX_DIGEST_BITS;
	read_size(rfp);
	res = read_book(book,rfp);
	fclose(rfp);
	if (res != BOOK_SUCCESS) {
	    return res;
	}
	Output("Read %d book positions\n", book->bookcnt);
	Output("Got %d hash collisions\n", book->bookhashcollisions);
    } else {
	wfp = fopen(book->filename,"w+b");
	if (wfp == NULL) {
	    Output("Could not create %s file: %s\n",
		   book->filename, strerror(errno));
	    return BOOK_EIO;
	}
	if (write_magic(wfp) != BOOK_SUCCESS) {
	    Output("Could not write to %s: %s\n",
		   book->filename, strerror(errno));
	    return BOOK_EIO;
	}
	if (fclose(wfp) != 0) {
	    Output("Could not write to %s: %s\n",
		   book->filename, strerror(errno));
	    return BOOK_EIO;
	}
	Output("Created new book %s!\n", book->filename);
	rfp = fopen(book->filename, "rb");
	if (rfp == NULL) {
	    Output("Could not open %s for reading: %s\n",
		   book->filename, strerror(errno));
	    return BOOK_EIO;
	}
	book->digest_bits = MAX_DIGEST_BITS;
	/* We use read_book() here only to allocate memory */
	if (read_book(book,rfp) == BOOK_ENOMEM) {
	    return BOOK_ENOMEM;
	}
    }
    return BOOK_SUCCESS;
}

/*
 * Store the position and results of last search
 * if and only if in the first 10 moves of the game.
 * This routine is called after the computer makes its
 * most recent move. Lastly, after the 10th move, on 
 * the 11th move, store the data out to the running file.
 */
/*
 * NOTE: Before you build a book, you have to call
 * BookBuilderOpen() now, after building it BookBuilderClose()
 * in order to actually write the book to disk.
 */

int BookBuilder(book_t *book, board_t *board, uint32_t move, short result)
{
    uint32_t i;
    uint8_t side;

    side=board->side;
  
  for (DIGEST_START(book, i, board->HashKey);
       ; 
       DIGEST_NEXT(book, i, board->HashKey)) {
      if (DIGEST_MATCH(book, i, board->HashKey, move)) {
	  book->existpos++;
	  break;
      } else if (DIGEST_EMPTY(book, i)) {
	  if (book->bookcnt > DIGEST_LIMIT(book))
	      return BOOK_EFULL;
	  book->bookpos[i].key = board->HashKey;
	  book->bookpos[i].move = move;
	  book->newpos++;
	  book->bookcnt++;
	  break;
      } else {
	  book->bookhashcollisions++;
      }
  }
  if (side == white) {
      if (result == R_WHITE_WINS){
	  book->bookpos[i].wins++;
      }else if (result == R_BLACK_WINS){
	  book->bookpos[i].losses++;
      }else if (result == R_DRAW)
	  book->bookpos[i].draws++;
  } else{
      if (result == R_WHITE_WINS){
	  book->bookpos[i].losses++;
      }else if (result == R_BLACK_WINS){
	  book->bookpos[i].wins++;
      }else if (result == R_DRAW){
	  book->bookpos[i].draws++;
      }
  }

  if((book->bookpos[i].wins> 0xFFF0) || 
     (book->bookpos[i].draws> 0xFFF0) || 
     (book->bookpos[i].losses> 0xFFF0)){
      return BOOK_OVERFLOW;
  }
  return BOOK_SUCCESS;
}

int BookBuilderClose(book_t *book)
{
  /*
   * IMHO the following part needs no temporary file.
   * If two gnuchess invocations try to write the same
   * file at the same time, this goes wrong anyway.
   * Please correct me if I am wrong. If you generate
   * a temporary file, try to generate it in some writable
   * directory.
   */
  FILE *wfp;
  buf_t buf;
  unsigned int i;
  int errcode = BOOK_SUCCESS;

  wfp = fopen(book->filename, "wb");
  if (wfp == NULL) {
    errcode = BOOK_EIO;
    goto bailout_noclose;
  }
  if (write_magic(wfp) != BOOK_SUCCESS) {
    errcode = BOOK_EIO;
    goto bailout;
  }
  if (write_size(wfp, book->bookcnt) != BOOK_SUCCESS) {
    errcode = BOOK_EIO;
    goto bailout;
  }
  for (i = 0; i < DIGEST_SIZE(book); i++) {
    if (!is_empty(book,i)) {
      book_to_buf(book,buf,i);

      if (1 != fwrite(&buf, sizeof(buf), 1, wfp)) {
	errcode = BOOK_EIO;
	goto bailout;
      }
    }
  }
  Output("Got %d hash collisions\n", book->bookhashcollisions);

 bailout:
  if (fclose(wfp) != 0) {
    errcode = BOOK_EIO;
  }

 bailout_noclose:
  BookClear(book);

  return errcode;
}


static FILE* BookFileOpen(const char *file){
  FILE* rfp;
   rfp = fopen(file, "rb");
   if (rfp == NULL) return NULL;
   OutputConsole("Read opening book (%s)...\n", file);
   if (!check_magic(rfp)) {
       //Output( " File %s does not conform to the current format.\n"
       //     " Consider rebuilding it.\n\n",
       //     file);
     fclose(rfp);
     rfp=NULL;
   }
   return rfp;
}

void BookInit(book_t *book){
    book->type=BOOK_UNKNOWN;
    book->pg_file=NULL;
    book->filename=NULL;
    book->bookpos=NULL;
    book->bookcnt=0;
    book->digest_bits=0;
    book->bookhashcollisions=0;
    book->newpos=0;
    book->existpos=0;
}


void BookClear(book_t *book){
  if(book->bookpos){
    free(book->bookpos);
  }
  if(book->pg_file){
      fclose(book->pg_file);
  }
  BookInit(book);
}


void BookSetFile(book_t *book, const char *filename){
  BookClear(book);
  my_string_set(&book->filename,filename);
}



int BookLoad(book_t *book){
  FILE *rfp;
  int booksize,res;
  rfp=BookFileOpen(book->filename);
  if(!rfp) return BOOK_ENOBOOK;
  OutputConsole("Loading book from %s.\n", book->filename);
  /*
   * The factor 1.06 is the minimum additional amount of
   * free space in the hash
   */
  booksize = read_size(rfp) * 1.06;
  for (book->digest_bits = 1; booksize; booksize >>= 1) {
    book->digest_bits++;
  }
  // allocates memory
  res = read_book(book,rfp);
  fclose(rfp);
  if (res != BOOK_SUCCESS) {
    return res;
  }
  OutputConsole("%d hash collisions... \n", book->bookhashcollisions);
  return res;
}





/*
 * Return values are defined in book.h
 * NOTE: This function now returns BOOK_SUCCESS on
 * success, which is 0. So all the possible callers
 * should adjust to this. (At present it is only called
 * in iterate.c.) It used to return 1 on success before.
 */


void BookQuery(book_t *book, board_t *board, movelist_t *movelist){

    int i,j,mcnt;
    int ret;
    leaf pref[MAXMOVES];

    movelist->gencnt=0;

    if(book->type==BOOK_INVALID){
	return;
    }

    if(book->type==BOOK_POLYGLOT){
	PolyglotBookQuery(book, board, movelist);
	return;
    }

    if(book->type==BOOK_UNKNOWN){
	if(book->bookpos!=NULL || BookLoad(book)==BOOK_SUCCESS){
	    Log("Using a gnuchess book...\n");
	    book->type=BOOK_GNUCHESS; // query follows below
	}else if(PolyglotBookValidate(book)){
	    Log("Using a polyglot book...\n");
	    book->type=BOOK_POLYGLOT;
	    PolyglotBookQuery(book, board, movelist);
	    return;
	}else{
	    book->type=BOOK_INVALID;
	    OutputConsole("The file \"%s\" appears to be neither a "
			  "gnuchess nor a polyglot book...\n",book->filename);
	    return;
	}

    }
    mcnt = -1;
    for (DIGEST_START(book,j,board->HashKey);
	 !DIGEST_EMPTY(book, j);
	 DIGEST_NEXT(book,j, board->HashKey)) {
	if (DIGEST_MATCH_KEY(book, j, board->HashKey)) {
	    int losses,wins,draws;
	    pref[++mcnt].move = book->bookpos[j].move;
	    losses = book->bookpos[j].losses;
	    wins = book->bookpos[j].wins;
	    draws = book->bookpos[j].draws;
	    
	    /* by percent score starting from this book position */
	    
	    pref[mcnt].score = SCORE(wins,draws,losses);
	    
	    if (mcnt >= MAXMATCH) {
		OutputConsole(" Too many matches in book.\n\n");
		goto fini;
	    }
	}
    }
    
 fini:  
    for(i=0;i<=mcnt;i++){
	movelist->moves[i].move=pref[i].move;
	movelist->moves[i].score=pref[i].score;
    }
    movelist->gencnt=mcnt+1;

}
