// 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 "bearing.h"
#include "bititer.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>

int count = 0;
int verbose = 0;
long total_nodes = 0L;
long total_correct = 0L;
long total_time = 0L;
int early_exit_plies = Constants::MaxPly;
BOOL early_exit = FALSE;
int solution_move_count = 0;
int last_iteration_depth = -1;
int iterations_correct = 0;
Move solution_moves[10];
BOOL avoid = FALSE;

static void post(const Search::Statistics &stats)
{
    if (avoid) return;
    for (int i = 0; i < solution_move_count; ++i)
    {
	if (solution_moves[i] == stats.best_line[0])
        {
             if ((int)stats.depth > last_iteration_depth)
             {
                ++iterations_correct;
                last_iteration_depth = stats.depth;
             }
             if (iterations_correct >= early_exit_plies)
  	        early_exit = TRUE;
             return;
	}
    }
    iterations_correct = 0;
}

static BOOL terminate(const Search::Statistics &stats)
{
    post(stats);
    return early_exit;
}

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

    Time_Info ti(type,limit);
    int time_limit = 99999, ply_limit = 99;
    if (type == Fixed_Ply)
        ply_limit = limit.max_ply;
    else
        time_limit = limit.centiseconds;
    searcher.register_post_function(post);
    searcher.register_terminate_function(terminate);
    
    Move move = searcher.find_best_move(NULL, board, 
        type, 
        time_limit, ply_limit,
        FALSE, stats,
        verbose ? Search::Debug : Search::Silent);

    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/100 <<
     " seconds.\tscore:" << val << "\t"  <<
     stats.num_nodes << " nodes.";
    total_time += stats.elapsed_time;
    total_nodes += stats.num_nodes;
 
    game_moves->clear();
//    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 << "  -e           - show root evaluation only" << 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 << "  -x [plies]   - terminate if correct move holds for <plies>" << endl;
    cerr << "  -v           - be verbose\n" << endl;
}

static void do_eval(ifstream &pos_file, Board &board)
{
      pos_file >> board;
      if (!pos_file.good())
      {
	    cout << "File not found, or bad format." << endl;
	    return;
      }
      int position[2];
      int material[2];
      int king_safety[2];
      Scoring::init(board);
      Scoring::scores(board,material,position,king_safety);
      cout << "side   material\t\tposition\tking safety" << endl;
      char line[256];
      sprintf(line,"White  %5.2f\t\t%5.2f\t\t%5.2f\n",
         material[White]/64.0,position[White]/64.0,
         king_safety[White]/64.0);
      cout << line;
      sprintf(line,"Black  %5.2f\t\t%5.2f\t\t%5.2f\n",
         material[Black]/64.0,position[Black]/64.0,
         king_safety[Black]/64.0);
      cout << line;
      sprintf(line,"total  %5.2f\t\t%5.2f\t\t%5.2f\n",
         Scoring::material_score(board)/64.0,
         Scoring::positional_score(board)/64.0,
         Scoring::king_safety(board)/64.0);
     cout << line;
     sprintf(line,"total score = %5.2f\n",Scoring::evalu8(board)/64.0);
     cout << line << 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 ||
                strncmp(buf,"avoid",5) == 0)
	      {
		  solution_move_count = 0;
                  last_iteration_depth = -1;
                  iterations_correct = 0;
                  early_exit = FALSE;
                  avoid = (*buf == 'a');
                  int incr;
                  if (avoid) incr = 6; else incr = 5;
		  for (char *p = buf+incr;*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(solution_move_count < 10);
			     solution_moves[solution_move_count++] = m;
		      }
		      if (*p == '+') ++p;
		  }
		  ++count;
		  Search::Statistics stats;
		  Move m = search(board,limit,type,stats);
		  int correct = avoid ? 1 : 0;
		  for (int i = 0; i < solution_move_count; ++i)
		  {
		      if (solution_moves[i] == m)
		      {
			     correct = (avoid) ? 0 : 1;
                             break;
		      }
		  }
		  if (correct)
                  {
                      total_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/100 << " seconds."
               <<endl;
           cout << "total nodes    : " << total_nodes << endl;
           cout << "number correct : " << total_correct << endl;
}

int __cdecl main(int argc, char **argv)
{
   init_globals(argv[0],FALSE,FALSE);
   atexit(cleanup_globals);

   Board board;
   Search_Limit limit;
   Search_Type type = Fixed_Ply;
   limit.max_ply = 2;
   int arg = 1;
   int show_eval = 0;
   
   if (argc ==1)
   {
       show_usage();
       return -1;
   }
   else
   {
        while (arg < argc && *(argv[arg]) == '-')
        {
	   char c = *(argv[arg]+1);
	   switch (c)
	   {
           case 'e':
              ++show_eval;
              break;
	   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 't':
	      type = Time_Limit; 
              ++arg;
    	      limit.centiseconds = 100*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 'x':
              {
              ++arg;
              if (arg < argc)
              {
                 early_exit_plies = atoi(argv[arg]);
              }
              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 (show_eval)
           do_eval(pos_file,board);
        else
           do_search(pos_file,board,type,limit);	   
    }	
    else
    {
        cout << "file not found: " << argv[arg] << endl;
    	return -1;
    }
    return 0;
}
