// Copyright 1993-1996 by Jon Dart.  All Rights Reserved.

#include "stdafx.h"
#include "bookread.h"
#include "constant.h"
#include "bhash.h"
#include "bookdefs.h"
#include "movegen.h"
#include "globals.h"
#include <string.h>

static void show_error(const char *fileName)
{
   DWORD errCode = GetLastError();
   char *errMsg;
   switch(errCode)
   {
      case ERROR_NOT_ENOUGH_MEMORY:
      case ERROR_OUTOFMEMORY:
        errMsg = " : not enough memory";
        break;
      case ERROR_FILE_NOT_FOUND:
      case ERROR_PATH_NOT_FOUND:
        errMsg = " : file not found";
        break;
      case ERROR_TOO_MANY_OPEN_FILES:
        errMsg = " : too many open files.";
        break;
      case ERROR_INVALID_DRIVE:
        errMsg = " : invalid drive.";
        break;
      case ERROR_ACCESS_DENIED:
        errMsg = " : access denied";
        break;
      default:
        errMsg = "";
   }
   static char *prefix = "Cannot open ";
   int len = strlen(errMsg) + strlen(fileName) + strlen(prefix) + 2;

   char *msg = new char[len];
   strcpy(msg,prefix);
   strcat(msg,fileName);
   strcat(msg," ");
   strcat(msg,errMsg);
   MessageBox(NULL,msg,"Warning",
              MB_ICONEXCLAMATION | MB_OK);
   delete [] msg;
}

Book_Reader::Book_Reader(LPCSTR fileName, LPCSTR mappingName)
: book_file(NULL),
  pBook(NULL),
  pPage(NULL),
  hFileMapping(NULL),
  is_open(FALSE)
{                 

   static char book_path[MAX_PATH];
   strcpy(book_path,programPath);
   {
       // find the last part of the program pathname
       char *p = strrchr(book_path,'\\');
       if (p)
       {
          // replace it with the log file name
          strcpy(p+1,fileName);
          book_file = CreateFile(book_path,
                                 GENERIC_READ,
                                 FILE_SHARE_READ,
                                 NULL /* security attributes */,
                                 OPEN_EXISTING,
                                 0,0);
          if (!book_file)
          {
             show_error(fileName);
             return;
          }
      }
   }
   if (book_file)
   {
      hFileMapping = CreateFileMapping(book_file,
                                       NULL,
                                       PAGE_READONLY,
                                       0,
                                       0,
                                       mappingName);
      if (!hFileMapping)
      {
             show_error(fileName);
             return;
      }
   }
   if (hFileMapping)
   {
      // Currently we map the entire book file into memory.
      // If the file becomes very large, this is probably not
      // a good idea.
      pBook = MapViewOfFile(hFileMapping,
                            FILE_MAP_READ,
                            0,0,0);
      if (!pBook)
      {
          show_error(fileName);
          return;
      }
   }
   
   is_open = pBook != NULL;
   if (!is_open)
      return;

   memcpy(&hdr,(char*)pBook,Header_Size);

   if (hdr.version != Book_Version)
   {
      MessageBox(NULL,"Wrong version of opening book!","Warning",
         MB_ICONEXCLAMATION | MB_OK);
      UnmapViewOfFile(pBook);
      CloseHandle(hFileMapping);
      CloseHandle(book_file);
      is_open = FALSE;
      return;
   }
}

Book_Reader::~Book_Reader()
{
   if (pBook)
      UnmapViewOfFile(pBook);
   if (hFileMapping)
      CloseHandle(hFileMapping);
   if (book_file)
      CloseHandle(book_file);
}

const uint16 Book_Reader::Head( const Board & b, enum styles style)
{
   byte * lpBook = (byte*)pBook;
   ASSERT(lpBook);
   
   int page = (int)(b.HashCode() & (unsigned long)(hdr.num_pages-1));
   pPage = lpBook + page*hdr.page_capacity;
   unsigned probe = (unsigned)((b.HashCode()>>32) % hdr.hash_table_size);
   if (style != Balanced)
   {
       // Make the low-level bits of the bucket index match the
       // style
       probe &= ~(MAX_STYLES-1);
       probe |= (int)style;
   }

   uint16 ret_val;
   memcpy(&ret_val,pPage+Header_Size+sizeof(uint16)*probe,sizeof(uint16));
   return ret_val;
}

void Book_Reader::Fetch( const uint16 n, Book_Entry &book_entry )
{
   ASSERT(pPage);

   byte *entry = pPage + Header_Size + hdr.hash_table_size*2 + n*Entry_Size;
   hash_t hc;
   byte rec, indx;
   // We assume here that the compiler doesn't reorder structures
   memcpy(&hc,entry,sizeof(hash_t));
   indx = entry[8];
   rec = entry[9];
   BOOL last = (rec & 0x80) ? TRUE: FALSE;
   book_entry.init(hc,rec & 0xf,indx, last);
}

Move Book_Reader::pick( const Board &b, uint16 node_index,
   const Book_Entry &node )
{
     unsigned total_weight = 0;
     uint16 indx = node_index;
     // Determine the total weights of moves for this position.
     while (indx != INVALID)
     {
        Book_Entry be;
        Fetch(indx, be);
        if (be.is_equal(node))
           total_weight += be.get_recommend();
        if (be.is_last())
           break;
        else
           indx++;
     }
     // If total_weight is 0, no moves have non-zero weights.
     // Return without any move selected.
     if (total_weight == 0)
        return Move::NullMove();
     unsigned weight = 0;
     indx = node_index;
     const unsigned nRand = (rand()*(unsigned long)total_weight)/RAND_MAX;
     // Randomly pick from the available moves.  Prefer moves
     // with high weights.
     while (indx != INVALID)
     {
        Book_Entry be;
        Fetch(indx,be);
        if (be.is_equal(node) && be.get_recommend() > 0)
        {
           weight += be.get_recommend();
           if (nRand < weight)
           {
               // We have selected a move. The book contains
               // only the move index, not the move itself.
               // We must call the move generator to obtain the
               // move.
               Move moves[Move_Generator::MaxMoves];
               Move_Generator mg( b, 0, Move::NullMove() );
               int n = mg.Generate_Moves(moves,TRUE /*repeatable*/); 
               ASSERT(be.move_index < n);
               return moves[be.move_index];
           }
        }
        if (be.is_last())
           break;
        else
           indx++;
     }
     // should never get here
     ASSERT(FALSE);
     return Move::NullMove();
}


