/*--------------------------------------------------------------------------
    Pawny 0.3, chess engine (source code).
    Copyright (C) 2009 - 2011 by Mincho Georgiev.
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    contact: pawnychess@gmail.com 
    web: http://www.pawny.netii.net/
----------------------------------------------------------------------------*/

#include "data.h"
#include "bits.h"

#define PHT_NUM_ENTRY 1000000//~25MB pht size (1 000 000 entries by 24 bytes of size)
#define PHT_SIZE (PHT_NUM_ENTRY * sizeof(ph_entry_t))
#define HINDEX ((board->hash % PHT_NUM_ENTRY))

#define EVASIONS
#define BITS

static int pawnhits;
static int pawncollisions;

typedef struct ph_entry_t
{	int depth;
	uint64 nodes;
	uint64 key;
	uint64 pkey;
}ph_entry_t;
//global pointer:
ph_entry_t *pht;


void pht_init()
{
	pht = (ph_entry_t *)malloc(PHT_SIZE);
	if(!pht)
	{	
		printf("not enough memory.\n\n");
		return;
	}
	memset(pht, 0, (PHT_SIZE));
}
void pht_free()
{
	free(pht);
}

uint64 probe_hash(int depth)
{
	ph_entry_t *p = &pht[HINDEX];
	if(p->key == board->hash)
	{
		if(p->pkey == board->phash) pawnhits++;
		else pawncollisions++;
		
		if(p->depth == depth) return (p->nodes);
	}
	return 0;
}

void record_hash(int depth, uint64 nodes)
{	
	ph_entry_t *p = &pht[HINDEX];
	p->key = board->hash;
	p->pkey = board->phash;
	p->nodes = nodes;
	p->depth = depth;
}


uint64 perft(int depth)
{
	int i,move_count;
	move_t ms[MOVE_STACK];
	uint64 nodes;
	uint64 val;
	#ifdef EVASIONS
	char strbuff[256];
	move_t ms_test[MOVE_STACK];
	#endif
	#ifdef BITS
	bitboard_t bb;
	#endif
	
		if(depth == 0) return 1;
		if((val = probe_hash(depth)) != 0) 
			return val;

		nodes = 0;
		val = 0;
		
		#ifdef BITS
		bb = 0;
		for(i = PLS(WP); i <= H8; i = PLN(i))
			bitset(&bb, rsz[i]);
		if(bb != board->bb_pawns[W])
		{BREAKPOINT}
		bb = 0;
		for(i = PLS(BP); i <= H8; i = PLN(i))
			bitset(&bb, rsz[i]);
		if(bb != board->bb_pawns[B])
		{BREAKPOINT}
		#endif
	
		#ifdef EVASIONS
		if(is_in_check(board->side))
		{	move_count = move_gen_evasions(&ms[0]);
			if(move_count != move_gen_legal(&ms_test[0]))
			{	board_to_fen(&strbuff[0]);
				printf("error: \n %s \n",strbuff);
			}
		}
		else
		{	
			move_count = move_gen(&ms[0]);
		}
		#else
		move_count = move_gen(&ms[0]);
		#endif
		
		for(i = 0; i < move_count; i++)
		{
			if(!move_make(ms[i])) continue;
			
			if(depth == 1)
				nodes++;
			else
				nodes += perft(depth - 1);
			
			move_undo();
		}
		record_hash(depth,nodes);
		return (nodes);
}

void test_divide(int depth)
{
	int i,move_count;
	move_t ms[MOVE_STACK];
	uint64 nodes;
	uint64 total_nodes;
	int legal_moves;
	#ifdef EVASIONS
	char strbuff[256];
	move_t ms_test[MOVE_STACK];
	#endif
	
		if(!depth) return;
		nodes = 0;
		total_nodes = 0;
		legal_moves = 0;
	
		pht_init();
		depth -= 1;
		timer_start();
		
		#ifdef EVASIONS
		if(is_in_check(board->side))
		{	move_count = move_gen_evasions(&ms[0]);
			if(move_count != move_gen_legal(&ms_test[0]))
			{	board_to_fen(&strbuff[0]);
				printf("error: \n %s \n",strbuff);
			}
		}
		else
		{	
			move_count = move_gen(&ms[0]);
		}
		#else
		move_count = move_gen(&ms[0]);
		#endif
		
		for(i = 0; i < move_count; i++)
		{
			if(!move_make(ms[i])) continue;
			nodes = perft(depth);
			print_move(ms[i].p);
			legal_moves++;
			printf("%I64u",nodes);
			printf("\n");
			move_undo();
			total_nodes += nodes;
		}
		printf("\nNodes: %I64u",total_nodes);
		printf("\nMoves: %d",legal_moves);
		printf("\n\n");
		timer_stop();
		pht_free();
}


void test_perft(int depth)
{//itterative perft calling and output:
	
	int i;
	uint64 nodes;
		
		pht_init();		
		printf("\n");
		printf("-----------------------\n");
		printf(" depth   nodes \n");
		printf("-----------------------\n");
		depth += 1;
		for(i = 1; i < depth; i++)
		{
			nodes = perft(i);
			printf("%6d   %I64u", i,nodes);
			printf("\n");
		}
		printf("-----------------------\n");
		pht_free();
}



uint64 xperft(int depth, uint64 *castles, uint64 *captures, uint64 *ep, uint64 *promotions)
{//recursive performance test
	
	int i,move_count;
	move_t ms[MOVE_STACK];
	uint64 nodes = 0;
	
		if(depth == 0) return 1;
	
		move_count = move_gen(&ms[0]);
		for(i = 0; i < move_count; i++)
		{
			if(!move_make(ms[i])) continue;
			nodes += xperft(depth - 1, castles, captures, ep, promotions);
			if(ms[i].type & CAP) *captures += 1;
			if(ms[i].type & PROM) *promotions += 1;
			if(ms[i].type & EP) *ep += 1;
			if(ms[i].type & CASTLE) *castles += 1;
			move_undo();
		}
		return nodes;
}

void test_xperft(int depth)
{//itterative perft calling and output:
	
	int i;
	uint64 nodes;
	uint64 castles;
	uint64 captures;
	uint64 ep;
	uint64 promotions;
	depth += 1;
	
		printf("\n");
		printf("--------------------------------------------------------------------\n");
		printf(" depth           nodes     captures     e.p.    castles  promotions\n");
		printf("--------------------------------------------------------------------\n");
		
		for(i = 1; i < depth; i++)
		{
			castles = 0;
			captures = 0;
			ep = 0;
			promotions = 0;
			nodes = xperft(i, &castles, &captures, &ep, &promotions);
			printf("%6d %15I64u %12I64u %8I64u %10I64u %11I64u", i,\
				nodes, captures, ep, castles, promotions );
			printf("\n");
		}
		printf("--------------------------------------------------------------------\n");

}



bool test_epd(char *filename)
{
	FILE *f;
	char c,*p;
	char line[255];
	char buff[255];
	uint64 nodes,result;
	int x,depth,position,errors,sum;

		f = fopen(filename, "r");
		if(f == NULL) return false;
		pht_init();
		timer_start();
		sum = 0;position = 0;result = 0;
		while(fgets(&line[0], sizeof(line), f))
		{				
			x = 0; p = &line[0];
			while(*p != ';'){	
				buff[x] = *p;
				p++; x++;
			}
			buff[x] = '\0';
			position++;
			
			if(!board_from_fen(&buff[0])){
				printf ("position %d - FEN error!\n\n", position);
				continue;
			}
			printf("\nposition %d\n",position);
			printf("----------------------\n");
			printf("%s\n",&buff[0]);
			printf("----------------------\n");
			
			depth = 0; errors = 0;
			while((*p != '\n') && (*p != 0))
			{
				if(*p == 'D'){
					depth++;
					sscanf(p,"%s %I64u",&c,&result);
					printf("depth %d: ",depth);
					nodes = perft(depth);
					if(nodes != result){
						printf("error\n");
						errors++;
					}
					else printf("done\n");
				}
				p++;
			}
			printf("----------------------\n");
			printf("unmatched: %d \n\n", errors);
			sum+=errors;
		}
		printf("----------------------\nend_of_file.\n");
		printf("total mismatched depths: %d\n",sum);
		timer_stop();
		fclose(f);
		pht_free();
		return true;
}


void test_pawn_divide(int depth)
{
	int i,move_count;
	move_t ms[MOVE_STACK];
	uint64 nodes = 0;
	uint64 total_nodes = 0;
	int legal_moves = 0;
	
		if(!depth) return;
		pawnhits = 0;
		pawncollisions = 0;
		pht_init();
		depth -= 1;
		timer_start();
		move_count = move_gen(&ms[0]);
		for(i = 0; i < move_count; i++)
		{
			if(!move_make(ms[i])) continue;
			nodes = perft(depth);
			print_move(ms[i].p);
			legal_moves++;
			printf("%I64u",nodes);
			printf("\n");
			move_undo();
			total_nodes += nodes;
		}
		printf("\nNodes: %I64u",total_nodes);
		printf("\nMoves: %d",legal_moves);
		printf("\nPawnHits: %d",pawnhits);
		printf("\nPawnCollisions: %d",pawncollisions);
		printf("\n\n");
		timer_stop();
		pht_free();
}



int epd_get_score(char *moves)
{//note: move is already maded
	int i,j;
	int fambiguous,rambiguous;
	int file_from,file_to;
	int rank_from,rank_to;
	int pc,promoted;
	
	hist_t m = hs[0];
	char *p = moves;
	char buff[8][8] = {{0},{0}};
	int score[8] = {0};

	if(*p == '"') p++;
	
	i = 0;
	while(*p && *p != '"' && *p != '\n')
	{	
		j = 0;
		while(isalpha(*p) || isdigit(*p) || *p == '-'){
			buff[i][j] = *p;
			j++;
			p++;
		}
		while(!isalpha(*p)){
			if(*p == '"' || *p == '\0') break;
			if(*p == '='){
				while(!isdigit(*p)) p++;
				sscanf(p,"%d",&score[i]);
			}
			p++;
		}
		i++;
	}
	while(i >= 0)
	{	
		i--; if(i < 0) break;
		fambiguous = -1;
		rambiguous = -1;
		file_from = -1;
		file_to = -1;
		rank_from = -1;
		rank_to = -1;
		promoted  = 0;
		pc = 0;
		for(j = 0; j < 8; j++)
		{
			if(!buff[i][j]) break;
			switch(buff[i][j]){
			case 'Q':{
				if(j) promoted = QUEEN;
				else pc = QUEEN;
				break;
			}
			case 'R':{
				if(j) promoted = ROOK;
				else pc = ROOK;
				break;
			}
			case 'B':{
				if(j) promoted = BISHOP;
				else pc = BISHOP;
				break;
			}
			case 'N':{
				if(j) promoted = KNIGHT;
				else pc = KNIGHT;
				break;
			}
			case 'K': pc = KING; break;

			case 'a': case 'b': case 'c':
			case 'd': case 'e': case 'f':
			case 'g': case 'h':{
				fambiguous++;
				if(fambiguous)
					file_from = file_to;
				file_to = buff[i][j] - 0x60 - 1;
				break;
			}
			case 'x':{
				fambiguous = -1;
				break;
			}
			case '1':case '2':case '3':
			case '4':case '5':case '6':
			case '7':case '8':{
				rambiguous++;
				if(rambiguous)
					rank_from = rank_to;
				rank_to = buff[i][j] - 0x30 - 1;
			}
			}
		}
		if(fambiguous)
		{
			if(rambiguous){
				if(promoted){
					if(calc_file(m.m.from) == file_from
					&& calc_file(m.m.to) == file_to
					&& GetType(m.m.promoted) == promoted
					&& rank_from == calc_rank(m.m.from)
					&& rank_to == calc_rank(m.m.to))
							break;
				}
				else{	
					if(calc_file(m.m.from) == file_from
					&& calc_file(m.m.to) == file_to
					&& rank_from == calc_rank(m.m.from)
					&& rank_to == calc_rank(m.m.to)){
						if(pc){
							if(rank_to >= 0){
								if(rank_to == calc_rank(m.m.to)){	
									if(pc == GetType(m.moved)) break;
								}
							}
						}
						else
						{
							if(rank_to >= 0){
								if(rank_to == calc_rank(m.m.to)){	
									if(GetType(m.moved) == PAWN) break;
								}
							}
						}
					}
				}
			}
			else{
				if(promoted){
					if(calc_file(m.m.from) == file_from
					&& calc_file(m.m.to) == file_to
					&& GetType(m.m.promoted) == promoted)
							break;
				}
				else{	
					if(calc_file(m.m.from) == file_from
					&& calc_file(m.m.to) == file_to){
						if(pc){
							if(rank_to >= 0){
								if(rank_to == calc_rank(m.m.to)){	
									if(pc == GetType(m.moved)) break;
								}
							}
							else
							{
								if(pc == GetType(m.moved))
								break;
							}
						}
						else
						{
							if(rank_to >= 0){
								if(rank_to == calc_rank(m.m.to)){	
									if(GetType(m.moved) == PAWN) break;
								}
							}
							else{
								if(GetType(m.moved) == PAWN)
									break;
							}
						}
					}
				}
			}
		}
		else
		{
			if(rambiguous){
				if(calc_file(m.m.to) == file_to
				&& rank_from == calc_rank(m.m.from)
				&& rank_to == calc_rank(m.m.to)){
					if(pc){
						if(pc == GetType(m.moved))
							break;
					}
					else
					{
						if(GetType(m.moved) == PAWN)
							break;
					}
				}	
			}
			else{
				if(calc_file(m.m.to) == file_to){
					if(pc){
						if(rank_to >= 0){
							if(rank_to == calc_rank(m.m.to)){	
								if(pc == GetType(m.moved)) break;
							}
						}
						else{
							if(pc == GetType(m.moved))
							break;
						}
					}
					else
					{
						if(rank_to >= 0){
							if(rank_to == calc_rank(m.m.to)){	
								if(GetType(m.moved) == PAWN) break;
							}
						}
						else{
							if(GetType(m.moved) == PAWN)
								break;
						}
					}
				}
			}
		}
		if(!strncmp(&buff[i][0],"O-O-O", 5))
		{
			if(GetType(m.moved) == KING
			&& calc_file(m.m.from) == FILE_E
			&& calc_file(m.m.to) == FILE_C)
				break;
		}
		if(!strncmp(&buff[i][0],"O-O", 3))
		{
			if(GetType(m.moved) == KING
			&& calc_file(m.m.from) == FILE_E
			&& calc_file(m.m.to) == FILE_G)
				break;
		}
	}
	if(i < 0) return 0;
	return score[i];
}


void epd_file_test(FILE *f, int seconds)
{
	char *p;
	char line[255],buff[255];
	char fen[255];
	int i,position=0;
	int score = 0,resolved = 0;
	static int total = 0;
	float percents = 0;
 
	if(!seconds || seconds >= 10000) 
	{	printf("\ndefault time selected - 10 seconds per position\n");
		seconds = 10;
	}
	else printf("\ntime selected - %d seconds per position\n",seconds);
	
	printf("hash - %d MB %I64u entries\n", (int)(opt->tt_size)/1024/1024, opt->tt_numentries);
	printf("pawn hash - %d MB %I64u entries\n\n", (int)(opt->pnt_size)/1024/1024, opt->pnt_numentry);
		
	while(fgets(&line[0], sizeof(line), f))
	{
		//collecting FEN position:
		p = strstr(&line[0], "bm");
		if(!p){
			printf("error: epd different format\n\n");
			return;
		}
		i = p - &line[0];
		memset(&fen[0],0,sizeof(fen));
		strncpy(&fen[0], &line[0], i);
		buff[i] = '\0'; //null terminating
		
		position++;
		//setting the position
		if(!board_from_fen(&fen[0])){
			printf("position %d - FEN error!\n\n", position);
				continue;
		}
		printf("\n------------------------------------------\n");
		
		//processing the ID string:
		p = strstr(&line[0], "id");
		if(p)
		{
			while(*p != '"') p++;
			i = strlen(&line[0]) - (p - &line[0]);
			strncpy(&buff[0], p, i);
			i = 0;
			while(*p != ';'){p++;i++;}
			buff[i] = '\0';
			printf("%s\n",&buff[0]);
		}
		printf("------------------------------------------\n");
		printf("%s\n\n",fen);

		opt->time_high = seconds*1000 - 15;//tiny decrease to avoid delays:
		opt->time_fixed = true;
		think();
			
		//getting the best moves array:
		p = strstr(&line[0], "c0");
		if(p)
		{
			while(*p != '"') p++;
			i = strlen(&line[0]) - (p - &line[0]);
			strncpy(&buff[0], p, i);
			i = 0;
			while(*p != ';'){p++;i++;}
			buff[i] = '\0';
			printf("%s ",&buff[0]);
			score = epd_get_score(&buff[0]);
			if(score == 10) resolved++;
			total += score;
			printf("score = %d\n",score);
		}
		else printf("error: position %d - no results\n",position);
	}
	//total score result:
	if(total) percents = (((float)total/(float)(position * 10))*(float)100);
	printf("\n------------------------------------------\n");
	printf("total score: %d/%d (%.1f%s)\n", total, (position * 10), percents,"%");
	if(resolved) percents = (((float)resolved/(float)(position))*(float)100);
	printf("resolved positions (with bm): %d/%d (%.1f%s)\n",resolved,position, percents,"%");
	printf("------------------------------------------\n\n");
	opt->time_fixed = false;
}


