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

// Utility to read a PGN file and convert it into the opening book.
// Input is a raw PGN file.

#include "stdafx.h"
#include "board.h"
#include "movegen.h"
#include "options.h"
#include "movearr.h"
#include "notation.h"
#include "chessio.h"
#include "eco.h"
#include "movearr.h"
#include "rmove.h"
#include "strpair.h"
#include "bearing.h"
#include "globals.h"
#include "util.h"
#include "ginfo.h"
#include "eco.h"

extern "C"
{
#include <string.h>
#include <ctype.h>
};
#include <afxcoll.h>
#include <fstream.h>
#include <iostream.h>
#include <strstrea.h>
#include <ctype.h>

BOOL replace = TRUE;
static Hash *hashTable = NULL;
static Hash *nameHash = NULL;
static int sample = 1;
static int max_ply = 40;
static const MARGIN = 75;

static void show_usage()
{
	 cerr << "Usage: pgn2book -s <sample> -p <max ply> pgn_file" << endl;
}

static BOOL legal_move(const Board &board, const Square &start,
  const Square &dest)
{
    // do a little basic sanity checking:
      if (!start.OnBoard() || !dest.OnBoard() || board[start].IsEmpty())
          return FALSE;

      ExtendedMove emove(board,start,dest);
      Move moves[Move_Generator::MaxMoves];
      Move_Generator mg( board, 0, Move::NullMove() );
      int found = 0;
          int n = mg.Generate_Moves(moves,FALSE);

      for (int i = 0; i < n; i++)
          {
          if (moves[i].StartSquare() == emove.StartSquare() &&
              moves[i].DestSquare() == emove.DestSquare())
          {
             found++;
             break;
          }
      }
      if (!found)
          return FALSE;
      else        
      {
          // check for king en prise
          Board board_copy(board);
          const ColorType side = board.Side();
          board_copy.MakeMove(emove);
          return !board_copy.any_attacks(
            board_copy.KingPos(side),OppositeColor(side));
          
      }
}

static char * getHeader(CPtrArray *hdrs, LPCSTR key)
{
    for (int i=0;i<hdrs->GetSize();i++)
    {
        StringPair *h = (StringPair*)hdrs->GetAt(i);
	if (strcmp(h->tag,key)==0)
        {
           return h->val;       
        }
    }
    return NULL;
}    

void write_moves(ostream &game_file, CPtrArray *hdrs, 
                const Move_Array &moves, const CString &eco,
                const CString result)
{
    Board board;
    int max = Util::Min(moves.num_moves(),max_ply);
    char buf[80];
    *buf = '\0';
    int last = 0;
    int num_image_size;
    for (int i = 0; i < max; i++)
    {
       Move_Record mr = moves[i];
       num_image_size = 0;
       char img[20];
       Notation::Image(board,mr.move(),img);
       const int image_size = strlen(img);
       if (last + image_size + num_image_size + 1 >= MARGIN)
       {
	    buf[last] = '\0';
	    game_file << buf << endl;
    	    last = 0;
   	    *buf = '\0';
       }
       if (last != 0)
	    strcat(buf," ");
       strcat(buf,img);
       last = strlen(buf);
       board.MakeMove(mr.move());
    }
    if (last)
    {	
       buf[last + strlen((LPCSTR)result) + 1] = '\0';
       game_file << buf;
    }
    game_file << endl;
}

int __cdecl main(int argc, char **argv)
{
   init_globals(argv[0],FALSE,FALSE);
   atexit(cleanup_globals);
   
   Board board;
   ECO eco;
   int arg = 1;

   if (argc ==1)
   {
         show_usage();
	 return -1;
   }
   else
   {
        for (;arg < argc;++arg)
        {
           if (argv[arg][0] == '-')
           {
              switch(argv[arg][1])
              {
                 case 'p':
                   ++arg;
                   max_ply = atoi(argv[arg]);
                   break;
                 case 's':
                   ++arg;
                   sample = atoi(argv[arg]);
                   break;
                 default:
                   cerr << "Unrecognized switch : " << argv[arg][1] << endl;
                   break;
              }
           }
           else
             break;
        }
	ifstream pgn_file( argv[arg], ios::in);
	int c;
        int count = 0;
        hash_t game_hash;
        ColorType side;
        CString result, white, black;
	if (pgn_file.good())
	{
            CPtrArray *hdrs = NULL;
            hashTable = new Hash(5003,65535);           
            while (!pgn_file.eof())
            {
               long first;
               Move_Array moves;
               board.Reset();
               side = White;
               if (hdrs)
               {
                  for (int i=0;i<hdrs->GetSize();i++)
                  {
                     StringPair *p = (StringPair*)hdrs->GetAt(i);
                     delete p;
                  }
                  delete hdrs;
               }
               hdrs = new CPtrArray();
               while (pgn_file.good() && (c = pgn_file.get()) != EOF)
               {
                   if (c=='[')
                   {
                      pgn_file.putback(c);
                      break;
                   }
               }
               ChessIO::collect_headers(pgn_file,hdrs,first);
               white = getHeader(hdrs,"White");
               black = getHeader(hdrs,"Black");
               
               game_hash = 0;
               for (;;)
               {
                   static char buf[2048];
                   static char num[20];
                   ChessIO::Token tok = ChessIO::get_next_token(pgn_file,buf,2048);
                   if (tok == ChessIO::Eof)
                      break;
                   if (tok == ChessIO::Number)
                   {
                      strncpy(num,buf,20);
                   }
                   else if (tok == ChessIO::GameMove)
                   {
                      // parse the move
                      Move m = Notation::Value(board,side,buf);
                      if (m.IsNull() ||
                        !legal_move(board,m.StartSquare(),
                            m.DestSquare()))
                      {
                          // echo to both stdout and stderr
                          fprintf(stderr,"Illegal move: %s\n",buf);
                          cout << "Illegal move: " << buf << endl;
                          break;
                      }
                      else
                      {
/**
                          char result[40];
                          Notation::Image(board,m,result); **/
                          ExtendedMove emove(board,m);
                          moves.add_move(board,emove);
                          
                          ReversibleMove rmove(board,emove);
                          board.MakeMove(rmove);
                          game_hash ^= board.HashCode();
/**                          Bitmap b(game_hash);
                          cout << (hex) << 
                           result << " game hash = " << 
                           b.hivalue() <<
                           (uint32)game_hash << endl; **/
                      }
                      side = OppositeColor(side);
                  }
                  else if (tok == ChessIO::Unknown)
                  {
                      fprintf(stderr,"Unrecognized text: %s\n",buf);
                  }
                  else if (tok == ChessIO::Comment)
                  {
                      // ignored for now
                  }
                  else if (tok == ChessIO::Result)
                  {
                       result = buf;
                       break;
                  }
               }
               // done with a game
               Game_Info search(game_hash);
               //cout << "game_hash = " << (hex) << (int)game_hash << (dec) << endl;
               Game_Info *g = (Game_Info*)hashTable->search(search);
               if (g == NULL)
               {
                   g = new Game_Info(game_hash);
                   hashTable->insert(g,FALSE);
               }
               else
               {
                   //cout << "duplicate" << endl;
                   continue; // duplicate game
               }
               if (moves.num_moves() < 30)
               {
                   continue;
               }
               if (++count % sample != 0)
                   continue;
               // output header
               char *ecoC = getHeader(hdrs,"ECO");
               char *white = getHeader(hdrs,"White");
               char *black = getHeader(hdrs,"Black");
               char *event = getHeader(hdrs,"Event");
               char *site = getHeader(hdrs,"Site");
               char *round = getHeader(hdrs,"Round");
               char *date = getHeader(hdrs,"Date");
               char *opening = getHeader(hdrs,"Opening");
               char *source = getHeader(hdrs,"Source");
               char header[255];
               *header = ';';
               header[1] = ' ';
               header[2] = '\0';
               if (white)
               {
                  strcat(header,white);
                  strcat(header,"-");
                  if (black)
                    strcat(header,black);
               }
               else
               {
                  if (opening)
                  {
                     strcat(header,opening);
                     strcat(header," ");
                  }
                  if (source)
                     strcat(header,source);
               }
               if (event && *event != '?')
               {
                  strcat(header," ");
                  strcat(header,event);
               }
               if (site && *site != '?')
               {
                  strcat(header," ");
                  strcat(header,site);
               }
               if (round && *round != '?')
               {
                  strcat(header," (");
                  strcat(header,round);
                  strcat(header,")");
               }
               if (date && *date != '?')
               {
                  strcat(header," ");
                  char *p = header + strlen(header);
                  strncpy(p,date,4);
                  p[4] = '\0';
               }
               if (result && *result != '*')
               {
                  strcat(header," (");
                  strcat(header,result);
                  strcat(header,")");
               }
               if (ecoC)
               {
                  strcat(header," [");
                  strcat(header,ecoC);
                  strcat(header,"]");
               }
               cout << "-" << endl;
               cout << header << endl;
               write_moves(cout,hdrs,moves,ecoC,result);
            }
            pgn_file.close();
	}
	else
	{
  	    cerr << "file not found: " << argv[arg] << endl;
	    return -1;
	}
   }

   return 0;
}
