// 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 "moveord.h"
#include "globals.h"
#include "util.h"
#include <string.h>
#include <iostream.h> // for debugging

static void show_error(const char *fileName)
{
   LPVOID errMsg;
 
   FormatMessage( 
    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    (LPTSTR)&errMsg,
    0,
    NULL 
   );
   static char *prefix = "Cannot open ";
   int len = strlen((LPCSTR)errMsg) + strlen(fileName) + strlen(prefix) + 2;

   char *msg = new char[len];
   strcpy(msg,prefix);
   strcat(msg,fileName);
   strcat(msg," ");
   strcat(msg,(LPCSTR)errMsg);
   MessageBox(NULL,msg,"Warning",
              MB_ICONEXCLAMATION | MB_OK);
   LocalFree(errMsg);
   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);
       }
       else
          strcpy(book_path,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)
   {
      pBook = MapViewOfFile(hFileMapping,
                            FILE_MAP_READ,
                            0,0,4096);
      if (!pBook)
      {
          show_error(fileName);
          return;
      }
   }
   
   is_open = pBook != NULL;
   if (!is_open)
      return;

   //memcpy(&hdr,(char*)pBook,Header_Size);
   char *p = (char*)pBook;
   hdr.version = (byte)(*p);
   hdr.num_pages = (byte)p[1];
   memcpy(&hdr.page_capacity,p+2,4);
   memcpy(&hdr.hash_table_size,p+6,4);

   if (hdr.version != (byte)Book_Version)
   {
      char msg[128];
      sprintf(msg,"Expected book version %d, got %d instead",
          (int)Book_Version,(int)hdr.version);
//      MessageBox(NULL,"Wrong version of opening book!","Warning",
      MessageBox(NULL,msg,"Warning",MB_ICONEXCLAMATION);
      UnmapViewOfFile(pBook);
      CloseHandle(hFileMapping);
      CloseHandle(book_file);
      is_open = FALSE;
      return;
   }
   UnmapViewOfFile(pBook);
   pBook = NULL;
}

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)
{
   int page = (int)(b.HashCode() % hdr.num_pages);
   if (pBook)
   {
      UnmapViewOfFile(pBook);
   }
   pBook = MapViewOfFile(hFileMapping,
                         FILE_MAP_READ,
                         0,page*hdr.page_capacity,hdr.page_capacity);
   if (!pBook)
   {
      MessageBox( NULL, "File mapping failed for opening book", "Warning", MB_ICONEXCLAMATION);
      pPage = NULL;
      return NULL;
   }
   pPage = (byte*)pBook;
   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;
   uint16 freq;
   byte rec, indx;
   // We assume here that the compiler doesn't reorder structures
   memcpy(&hc,entry,sizeof(hash_t));
   memcpy(&freq,entry+8,sizeof(uint16));
   indx = entry[10];
   rec = entry[11];
   BOOL last = (rec & 0x80) ? TRUE: FALSE;
   book_entry.init(hc,freq,rec & 0x7f,indx, last);
   book_entry.winloss = entry[12];
}

static int get_weight(const Book_Entry &be,int total_freq,const ColorType side)
{
     int rec = be.get_recommend();
     if (rec != 50)
        return rec; // weight from the old book
     int w = be.get_winloss();
     int freqWeight = (int)((100L*be.get_frequency())/total_freq);
     // Slightly inflate the frequency of rare moves
     freqWeight = (freqWeight*(200-freqWeight))/100;
     int winWeight = (w+100)/2;
     // Favor more frequent moves and moves that win
     return (freqWeight*winWeight)/200;
}

Move Book_Reader::pick( const Board &b, uint16 node_index,
   const Book_Entry &node )
{
     unsigned total_weight = 0;
     uint16 indx = node_index;
     int total_freq = 0;
     // get the total move frequency
     while (indx != INVALID)
     {
        Book_Entry be;
        Fetch(indx, be);
        if (be.is_equal(node))
           total_freq += be.get_frequency();
        if (be.is_last())
           break;
        else
           indx++;
     }
     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 += get_weight(be,total_freq,b.Side());
        }
        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);
        int w;
        if (be.is_equal(node) && (w = get_weight(be,total_freq,b.Side())) > 0)
        {
           weight += w;
           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();
}

int Book_Reader::book_moves(const Board &b, Move *moves, const unsigned limit)
{
     unsigned indx = Head(b,Balanced);
     Book_Entry target(b.HashCode(),0,0,0);
     int num_moves = 0;
     int total_freq = 0;
     // get the total move frequency
     while (indx != INVALID)
     {
        Book_Entry be;
        Fetch(indx,be);
        if (target.is_equal(be) && be.get_recommend())
        {
            num_moves++;
            total_freq += be.get_frequency();
        }
        if (be.is_last())
            break;
        else
            indx++;
    }
    if (num_moves == 0)
        return 0;
    Move *tmp_moves = new Move[Move_Generator::MaxMoves];
    Move_Generator mg( b, 0, Move::NullMove() );
    int n = mg.Generate_Moves(tmp_moves,TRUE /* repeatable */); 
    int * scores = new int[num_moves];
    Move * tmp_moves2 = new Move[num_moves];
    indx = Head(b,Balanced);
    num_moves = 0;
    while (indx != INVALID)
    {
        Book_Entry be;
        Fetch(indx,be);
        if (target.is_equal(be) && be.get_recommend())
        {
           ASSERT(be.move_index < n);
           tmp_moves2[num_moves] = tmp_moves[be.move_index];
           scores[num_moves] = get_weight(be,total_freq,b.Side());
           ++num_moves;
        }
        if (be.is_last())
           break;
        else
           indx++;
    }
    Move_Ordering::sort_moves(tmp_moves2,scores,num_moves);
    unsigned ret_val = Util::Min(num_moves,limit);
    for (unsigned i = 0; i < ret_val; i++)
       moves[i] = tmp_moves2[i];
    delete [] scores;
    delete [] tmp_moves;
    delete [] tmp_moves2;
    return ret_val;
}
