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

// Stand-alone console executable to test the search engine

#include "stdafx.h"
#include "board.h"
#include "movegen.h"
#include "search.h"
#include "options.h"
#include "movearr.h"
#include "notation.h"
#include "timectrl.h"
#include "globals.h"
#include "rmove.h"
#include "scoring.h"
#include "attacks.h"
extern "C"
{
#include <string.h>
#include <ctype.h>
};
#include <fstream.h>
#include <iostream.h>
#include <strstrea.h>
#include <ctype.h>
#include <time.h>
#include <sys/timeb.h>

Options *global_options = NULL;
Move_Array *game_moves = NULL;
int count = 0;
int verbose = 0;
long total_nodes = 0L;
long total_correct = 0L;
long total_time = 0L;
char *programPath;

static Move search(Board &board, Search_Limit limit,
   Search_Type type, Search::Statistics &stats)
{
    Search searcher;
    game_moves = new Move_Array();

    Time_Info ti(type,limit);
    Move move = searcher.find_best_move(board, ti,
      stats, Move::NullMove(), FALSE, verbose );

    char val[20];
    sprintf(val,"%6.2f",stats.value/64.0);
    ExtendedMove emove(board,move);
    char move_text[20];
    Notation::Image(board, emove, move_text);
    cout << "search:";
    cout << '\t' << move_text << "\t" << stats.elapsed_time <<
     " seconds.\tscore:" << val << "\t" << stats.num_moves << " moves.\t" <<
     stats.num_nodes << " nodes.";
    total_time += stats.elapsed_time;
    total_nodes += stats.num_nodes;
 
    delete game_moves;
    Move_Record::freeAll(TRUE);
    return (Move)emove;
}

static void show_usage()
{
    cerr << "Usage:"  << endl;
    cerr << "  testsrc <options> <position file>" << endl;
    cerr << "Options are:" << endl;
    cerr << "  -t [seconds] - seconds to search per position" << endl;
    cerr << "  -p [plies]   - ply limit for search" << endl;
    cerr << "  -n [0|1|2]   - null depth reduction (default 2)" << endl;
    cerr << "  -k [0|1|2]   - killer moves per ply (default 2)" <<
        endl;
    cerr << "  -H [size]    - hash table size (number of entries)" << endl;
    cerr << "  -c[+|-]      - use/don't use check extensions (default on)" << endl;
    cerr << "  -pp[+|-]     - use/don't use pawn push extensions (default on)" << endl;
    cerr << "  -h[+|-]      - use/don't use history heuristic (default on)" << endl;
    cerr << "  -v           - be verbose\n" << endl;
}

static void do_search(ifstream &pos_file, Board &board, Search_Type type,
   Search_Limit limit) 
{
       char buf[255];
	   while (!pos_file.eof())
	   {
	      pos_file.getline(buf,256);
	      if (!pos_file)
	      {
	         cout << "File not found, or bad format." << endl;
		     return;
	      }
	      if (strncmp(buf,"svfe",4)==0)
	      {
		     istrstream s(buf+5);
		     s >> board;
	      }
	      else if (strncmp(buf,"echo",4)==0)
	      {
		     cout << buf+4 << endl;
		     continue;
	      }
	      else if (strncmp(buf,"noop",4)==0)
		     continue;
	      else if (strncmp(buf,"srch",4)==0)
	      {
		  Move moves[10];
		  int move_count = 0;
		  for (char *p = buf+5;*p;)
		  {
		      while (isspace(*p) && *p != '\0') ++p;
		      if (*p == '\0')
		         break;
		      char tmp[10];
		      int i = 0;
		      char *q = tmp;
		      while (!isspace(*p) && *p != '+' && *p != '\0' && i < 10)
		      {
			     *q++ = *p++;
			     ++i;
		      }
		      *q = '\0'; 
		      Move m = Notation::Value(board,board.Side(),tmp);
		      if (!m.IsNull())
		      {
			     ASSERT(move_count < 10);
			     moves[move_count++] = m;
		      }
		      if (*p == '+') ++p;
		  }
		  ++count;
		  Search::Statistics stats;
		  Move m = search(board,limit,type,stats);
		  int correct = 0;
		  for (int i = 0; i < move_count; ++i)
		  {
		      if (moves[i] == m)
		      {
			     ++correct; 
                             total_correct++;
                             break;
		      }
		  }
		  if (correct)
		      cout << "\t++ correct" << endl;
		  else
		      cout << "\t** error" << endl;
		  
		  for (int j = 0; j < Constants::MaxPly; ++j)
		  {
		      char buf[30];
		      Move m = stats.best_line[j];
		      if (m.IsNull())
			     break;
		      Notation::Image(board,m,buf);
		      cout << buf << " ";
		      board.MakeMove(ExtendedMove(board,m));
		  }
		  cout << endl;
	      }
	      else if (strncmp(buf,"rtrn",4)==0)
		  break;
	      else
	      {
		  cout << "Warning: unrecognized command: " << buf << endl;
	      }

	      char c;
	      while (!pos_file.eof())
	      {
		    c = pos_file.get();
		    if (!isspace(c) && c != '\n')
		    {
		       if (!pos_file.eof())
			   pos_file.putback(c);
		       break;
		    }
	      }
	   }
	   pos_file.close();
           cout << endl; 
           cout << "total time     : " << total_time << " seconds."
               <<endl;
           cout << "total nodes    : " << total_nodes << endl;
           cout << "number correct : " << total_correct << endl;
}

static void calc_performance(ifstream &pos_file)
{
      Board b;
      pos_file >> b;
      if (!pos_file.good())
      {
          fprintf(stderr,"File does not contain a valid FEN position.\n");
          return;
      }
      
      static Move moves[Move_Generator::MaxMoves];
      Move_Generator mg(b, 1, Move::NullMove());
      struct _timeb start;
      _ftime(&start);
      int move_count;
      int i;
      for (i = 0; i < 10000; i++)
      {
          move_count = mg.Generate_Moves(moves);
      }
      struct _timeb end;
      _ftime(&end);
      time_t elapsed = (end.time-start.time)*1000 + (end.millitm-start.millitm);
      cout << "-- move generation time:" << endl;
      cout << move_count << " moves generated in " << (float)elapsed/10000.0
           << " millisec. ( " << 10000000.0*(float)move_count/(float)elapsed
           << " moves/sec.)" << endl;
      _ftime(&start);
      for (i = 0; i < 10000; i++)
      {
          for (int j=0; j<move_count; j++)
          {  
             ExtendedMove emove(b,moves[j]);
             ReversibleMove rmove(b,emove);
             b.MakeMove(rmove);
             b.UndoMove(rmove);
          }
      }
      _ftime(&end);
      elapsed = (end.time-start.time)*1000 + (end.millitm-start.millitm);
      cout << "-- move make/unmake time: " << endl;
      cout << (float)elapsed/((float)move_count*10000.0) << " millisec." << 
              " ( " << (10000000.0*(float)move_count)/(float)elapsed << " moves/sec.)" << endl;
      Scoring::init();
      _ftime(&start);
      for (i = 0; i < 10000; i++)
      {
         Scoring::evalu8(b);
      }
      _ftime(&end);
      elapsed = (end.time-start.time)*1000 + (end.millitm-start.millitm);
      cout << "-- evaluation time: " << endl;
      cout << (float)elapsed/(10000.0) << " millisec." << 
              " ( " << (10000000.0)/(float)elapsed << " positions/sec.)" << endl;
     
}

int main(int argc, char **argv)
{
   Board board;
   Search_Limit limit;
   Search_Type type = Fixed_Ply;
   limit.max_ply = 2;
   int arg = 1;
   int do_perf = 0;

   programPath = new char[256];
   if (argc ==1)
   {
     show_usage();
	 return -1;
   }
   else
   {
        // save path to program
        strcpy(programPath,argv[0]);
        global_options = new Options();

        while (arg < argc && *(argv[arg]) == '-')
        {
	   char c = *(argv[arg]+1);
	   switch (c)
	   {
	   case 'p':
	      c = tolower(*(argv[arg]+2));
              if (c == 'p')
              {
   	         c = tolower(*(argv[arg]+3));
                 BOOL pawn_push_extensions = (c=='\0' || c=='+');
                 SearchOps ops;
                 global_options->get_search_ops(ops);
                 ops.pawn_push_extensions = pawn_push_extensions;
                 global_options->update_search_ops(ops);
              }
              else
              {
	         type = Fixed_Ply; 
                 ++arg;
      	         limit.max_ply = atoi(argv[arg]);
              }
              break;
           case 'm':
              do_perf++;
              break;
	   case 't':
	      type = Time_Limit; 
              ++arg;
    	      limit.seconds = atol(argv[arg]);
              break;
           case 'v':
              verbose++; break;
           case 'n':
              ++arg;
              if (arg < argc)
              {
                 int null_depth = -1;
                 if (argv[arg][0] == '0')
                   null_depth = 0;
                 else if (argv[arg][0] == '1')
                   null_depth = 1;
                 else if (argv[arg][0] == '2')
                   null_depth = 2;
                 if (null_depth >= 0)
                 {
                    SearchOps ops;
                    global_options->get_search_ops(ops);
                    ops.null_depth = null_depth;
                    global_options->update_search_ops(ops);
                 }
              }
              break;
           case 'H':
              {
                 ++arg;
                 SearchOps ops;
 	         global_options->get_search_ops(ops);
                 ops.hash_table_size = atol(argv[arg]);
                 global_options->update_search_ops(ops);
              }
              break;
           case 'k':
              ++arg;
              if (arg < argc)
              {
                 int killers = -1;
                 if (argv[arg][0] == '0')
                   killers = 0;
                 else if (argv[arg][0] == '1')
                   killers = 1;
                 else if (argv[arg][0] == '2')
                   killers = 2;
                 if (killers >= 0)
                 {
                    SearchOps ops;
                    global_options->get_search_ops(ops);
                    ops.killers = killers;
                    global_options->update_search_ops(ops);
                 }
              }
              break;
           case 'c':
	      c = tolower(*(argv[arg]+2));
              {
                 BOOL check_extensions = (c=='\0' || c=='+');
                 SearchOps ops;
                 global_options->get_search_ops(ops);
                 ops.check_extensions = check_extensions;
                 global_options->update_search_ops(ops);
              }
              break;
           case 'h':
	      c = tolower(*(argv[arg]+2));
              {
                 BOOL history = (c=='\0' || c=='+');
                 SearchOps ops;
                 global_options->get_search_ops(ops);
                 ops.history = history;
                 global_options->update_search_ops(ops);
              }
              break;
          case 'f':
	      c = tolower(*(argv[arg]+2));
              {
                 BOOL forced_extensions = (c=='\0' || c=='+');
                 SearchOps ops;
                 global_options->get_search_ops(ops);
                 ops.forced_extensions = forced_extensions;
                 global_options->update_search_ops(ops);
              }
              break;
	   default:
              show_usage();
	      return -1;
	   }
          ++arg;
       }
	
       if (arg >= argc)
       {
          show_usage();
	  return -1;
       }
    }

	ifstream pos_file( argv[arg], ios::in | ios::nocreate);
	
	if (pos_file.good())
	{
           if (do_perf)
              calc_performance(pos_file);
           else
              do_search(pos_file,board,type,limit);	   
	}
	else
	{
	  cout << "file not found: " << argv[arg] << endl;
	  return -1;
	}
    //}
    delete global_options;
    delete programPath;
    return 0;
}
