/******************************************
* Needle - a bitboard based chess engine. *
* (C) 2004 Mikko Nummelin                 *
******************************************/

#define _EDIT_C

#include "needle.h"

char *n_strtok_r (char *s, const char *delim, char **save_ptr);

const char cftable[0x10][5] =
{"-" ,"K"  ,"Q"  ,"KQ",
 "k" ,"Kk" ,"Qk" ,"KQk",
 "q" ,"Kq" ,"Qq" ,"KQq",
 "kq","Kkq","Qkq","KQkq"};

void replace_newline(char *s) {
	char *sptr=s;
	while((*sptr!='\n')&&(*sptr!=0)){
		sptr++;
	}
	*sptr=0;
}

/* Tests, what square contains */
char testsquare(const Board *brd, const char *square){
	int i,bitno;
	Bitboard testbit_mask;
	const Bitboard *straight;

	/* Sets up pointers inside board and zkeyset structures */
	const char pieceorder[]="KQBNRPkqbnrp";
	straight     = &(brd->K);

	bitno              = bit_for_square(square);
	testbit_mask.whole = bpow(bitno);

	for(i=0;i<12;i++){
		if(straight[i].whole & testbit_mask.whole)
			return pieceorder[i];
	}

	if(brd->ep.whole & testbit_mask.whole)
		return 'e'; /* En passant square! */
	return ' '; /* Nothing special on this square */
}

/* Sets a piece to square. Does NOT clean previous content! */
void setsquare(Board *brd, const char *square, char new_content){
	int i,bitno,bitno_r,bitno_nw,bitno_ne;
	Bitboard setbit_mask,setbit_mask_r,setbit_mask_nw,setbit_mask_ne;
	Bitboard *straight, *rotated,*nw, *ne;
	Zobrist *zkeyset_keys;

	/* Sets up pointers inside board and zkeyset structures */
	const char pieceorder[]="KQBNRPkqbnrp";
	straight     = &(brd->K);
	rotated      = &(brd->Kr);
	nw           = &(brd->Knw);
	ne           = &(brd->Kne);
	zkeyset_keys = zk.K;

	bitno                = bit_for_square(square);
	bitno_r              = r_bit_for_square(square);
	bitno_nw             = nw_bit_for_square(square);
	bitno_ne             = ne_bit_for_square(square);
	setbit_mask.whole    = bpow(bitno);
	setbit_mask_r.whole  = bpow(bitno_r);
	setbit_mask_nw.whole = bpow(bitno_nw);
	setbit_mask_ne.whole = bpow(bitno_ne);

	for(i=0;i<12;i++){
		if(new_content == pieceorder[i]){
			straight[i].whole |= setbit_mask.whole;
			rotated[i].whole  |= setbit_mask_r.whole;
			nw[i].whole       |= setbit_mask_nw.whole;
			ne[i].whole       |= setbit_mask_ne.whole;
			brd->key.whole    ^= zkeyset_keys[(i<<6)|bitno].whole;
		}
	}

	if(new_content=='e'){
		brd->ep.whole   |= setbit_mask.whole;
		brd->epr.whole  |= setbit_mask_r.whole;
		brd->epnw.whole |= setbit_mask_nw.whole;
		brd->epne.whole |= setbit_mask_ne.whole;
		if((bitno>=020)&&(bitno<=027))
			brd->key.whole^=zk.ep[bitno-020].whole;
		else if((bitno>=050)&&(bitno<=057))
			brd->key.whole^=zk.ep[bitno-040].whole;
	}
}

/* Deletes a piece from square. */
void resetsquare(Board *brd, const char *square, char old_content){
	int i,bitno,bitno_r,bitno_nw,bitno_ne;
	Bitboard resetbit_mask,resetbit_mask_r,resetbit_mask_nw,resetbit_mask_ne;
	Bitboard *straight, *rotated, *nw, *ne;
	Zobrist *zkeyset_keys;

	/* Sets up pointers inside board and zkeyset structures */
	const char pieceorder[]="KQBNRPkqbnrp";
	straight     = &(brd->K);
	rotated      = &(brd->Kr);
	nw           = &(brd->Knw);
	ne           = &(brd->Kne);
	zkeyset_keys = zk.K;

	bitno                  = bit_for_square(square);
	bitno_r                = r_bit_for_square(square);
	bitno_nw               = nw_bit_for_square(square);
	bitno_ne               = ne_bit_for_square(square);
	resetbit_mask.whole    = bpow(bitno);
	resetbit_mask_r.whole  = bpow(bitno_r);
	resetbit_mask_nw.whole = bpow(bitno_nw);
	resetbit_mask_ne.whole = bpow(bitno_ne);

	for(i=0;i<12;i++) {
		if(old_content == pieceorder[i]){
			straight[i].whole &= ~resetbit_mask.whole;
			rotated[i].whole  &= ~resetbit_mask_r.whole;
			nw[i].whole       &= ~resetbit_mask_nw.whole;
			ne[i].whole       &= ~resetbit_mask_ne.whole;
			brd->key.whole    ^= zkeyset_keys[(i<<6)|bitno].whole;
		}
	}

	if(old_content=='e') {
		brd->ep.whole   &= ~resetbit_mask.whole;
		brd->epr.whole  &= ~resetbit_mask_r.whole;
		brd->epnw.whole &= ~resetbit_mask_nw.whole;
		brd->epne.whole &= ~resetbit_mask_ne.whole;
		if((bitno>=020)&&(bitno<=027))
			brd->key.whole^=zk.ep[bitno-020].whole;
		else if((bitno>=050)&&(bitno<=057))
			brd->key.whole^=zk.ep[bitno-040].whole;
	}
}

/* Bitboard-safe but more costly versions of previous */
void setsafe(Board *brd, const char *square, char new_content){
	char prev_content;
	prev_content=testsquare(brd,square);
	resetsquare(brd,square,prev_content);
	setsquare(brd,square,new_content);
}

void resetsafe(Board *brd, const char *square){
	char prev_content;
	prev_content=testsquare(brd,square);
	resetsquare(brd,square,prev_content);
}

const char *test_castling_flags(Board *brd){
	return cftable[brd->castling_flags&0xf];
}

void set_castling_flags(Board *brd, char *newcf){
	int i;
	/* Resets them all and builds them then from scratch */
	for(i=0;i<4;i++){
		/* Reset zobrist key only if it was previously set */
		if(brd->castling_flags&(0x1<<i)){
			brd->key.whole^=zk.castle[i].whole;
		}
	}
	brd->castling_flags=0x00;
	for(i=0;(i<4)&&(newcf[i]!=0);i++){
		switch(newcf[i]){
		case 'K':
			brd->castling_flags|=0x1;
			brd->key.whole^=zk.castle[0].whole;
			break;
		case 'Q':
			brd->castling_flags|=0x2;
			brd->key.whole^=zk.castle[1].whole;
			break;
		case 'k':
			brd->castling_flags|=0x4;
			brd->key.whole^=zk.castle[2].whole;
			break;
		case 'q':
			brd->castling_flags|=0x8;
			brd->key.whole^=zk.castle[3].whole;
			break;
		}
	}
}

const char *get_ep_square(Board *brd){
	if(brd->ep.whole){
		return square_for_bit(my_bsf(brd->ep.whole));
	}
	return NULL;
}

void set_ep_square(Board *brd, char *epsquare){
	setsquare(brd, epsquare,'e');
}

void reset_ep_square(Board *brd){
	const char *old_ep_square=get_ep_square(brd);
	if(old_ep_square){
		resetsquare(brd,old_ep_square,'e');
	}
}

void null_move(Board *brd) {
	brd->whosmove=OTHERCOLOR(brd->whosmove);
	brd->key.whole^=zk.blacks_move.whole;
}

void set_side_to_move(Board *brd, char whosmove){
	if(brd->whosmove!=whosmove)
		null_move(brd);
}

void absolutely_clear_board(Board *brd){
	int i,bsize;
	unsigned char *bptr;
	bsize=sizeof(Board)/sizeof(unsigned char);
	bptr = (unsigned char *) brd;
	for(i=0;i<bsize;i++){
		bptr[i]=0x00;
	}
	/* well, these must be initialized to avoid errors */
	brd->whosmove='w';
	brd->half_moves=1;
}

void boardcpy(const Board *original_board, Board *new_board){
	int i,bsize;
	unsigned char *bptr_o,*bptr_n;
	bsize=sizeof(Board)/sizeof(unsigned char);
	bptr_o = (unsigned char *) original_board;
	bptr_n = (unsigned char *) new_board;
	for(i=0;i<bsize;i++){
		bptr_n[i]=bptr_o[i];
	}
}

/* Appends a decimal number to string and
 * auto-increases the position pointer */
void numtostring(int number, char **ptrptr){
	if(number>9) {
		numtostring(number/10,ptrptr);
	}
	**ptrptr='0'+number%10;
	(*ptrptr)++;
}

/* Appends a null terminated string to another
 * string and increases the position pointer */
void appendtostring(const char *string_p, char **ptrptr){
	char *newchr=(char *)string_p;
	while(*newchr) {
		**ptrptr=*newchr;
		(*ptrptr)++;
		newchr++;
	}
}

void dump_bitboard_bits (Bitboard *bitboard) {
	int i,j;
	for(i=7;i>=0;i--){
		for(j=7;j>=0;j--){
			if(bitboard->whole & bpow((i<<3)|j)) {
				printf("1");
			} else {
				printf("0");
			}
		}
		printf("\n");
	}
	printf("\n");
}

void make_move(Board *brd, Move_node *mvn) {
	char from_square[3],to_square[3],ep_captured_square[3];
	char ep_captured_pawn;

	from_square[0] = mvn->move[0];
	from_square[1] = mvn->move[1];
	from_square[2] = 0;
	to_square[0]   = mvn->move[2];
	to_square[1]   = mvn->move[3];
	to_square[2]   = 0;

	/* This should be done anyway */
	reset_ep_square(brd);

	/* Basic move action */
	resetsquare(brd,from_square,mvn->moving_piece);
	if((mvn->moving_piece=='P')&&(to_square[1]=='8')){
		setsquare(brd,to_square,toupper(mvn->move[4]));
	} else if((mvn->moving_piece=='p')&&(to_square[1]=='1')){
		setsquare(brd,to_square,tolower(mvn->move[4]));
	} else {
		setsquare(brd,to_square,mvn->moving_piece);
	}

	if(strchr("QBNRPqbnrp",mvn->captured_piece)){
		/* Capture */
		resetsquare(brd,to_square,mvn->captured_piece);
	} else if(mvn->captured_piece=='e'){
		/* En passant */
		strncpy(ep_captured_square,to_square,3);
		ep_captured_pawn='p';
		if(ep_captured_square[1]=='6'){
			ep_captured_square[1]='5';
		} else {
			ep_captured_square[1]='4';
			ep_captured_pawn='P';
		}
		resetsquare(brd,ep_captured_square,ep_captured_pawn);
	}

	/* Sets up ep square if necessary */
	if((mvn->moving_piece=='P')&&(to_square[1]-from_square[1]==2)){
		ep_captured_square[0]=from_square[0];
		ep_captured_square[1]='3';
		set_ep_square(brd,ep_captured_square);
	} else if((mvn->moving_piece=='p')&&(from_square[1]-to_square[1]==2)){
		ep_captured_square[0]=from_square[0];
		ep_captured_square[1]='6';
		set_ep_square(brd,ep_captured_square);
	}

	if(mvn->moving_piece=='K'){
		if(!strncmp(mvn->move,"e1g1",4)){
			/* Short castling */
			resetsquare(brd,"h1",'R');
			setsquare(brd,"f1",'R');
		}
		if(!strncmp(mvn->move,"e1c1",4)){
			/* Long castling */
			resetsquare(brd,"a1",'R');
			setsquare(brd,"d1",'R');
		}
	}

	if(mvn->moving_piece=='k'){
		if(!strncmp(mvn->move,"e8g8",4)){
			/* Short castling */
			resetsquare(brd,"h8",'r');
			setsquare(brd,"f8",'r');
		}
		if(!strncmp(mvn->move,"e8c8",4)){
			/* Long castling */
			resetsquare(brd,"a8",'r');
			setsquare(brd,"d8",'r');
		}
	}

	if(mvn->moving_piece=='K'){
		/* Clear white's castling flags if they were present */
		if(brd->castling_flags&0x1){
			brd->castling_flags &= ~0x1;
			brd->key.whole^=zk.castle[0].whole;
		}
		if(brd->castling_flags&0x2){
			brd->castling_flags &= ~0x2;
			brd->key.whole^=zk.castle[1].whole;
		}
	}
	if(((mvn->moving_piece=='R')&&(mvn->move[0]=='h')&&(mvn->move[1]=='1'))||
	((mvn->captured_piece=='R')&&(mvn->move[2]=='h')&&(mvn->move[3]=='1'))){
		/* Clear white's K castling flag if it was present */
		if(brd->castling_flags&0x1){
			brd->castling_flags &= ~0x1;
			brd->key.whole^=zk.castle[0].whole;
		}
	}
	if(((mvn->moving_piece=='R')&&(mvn->move[0]=='a')&&(mvn->move[1]=='1'))||
	((mvn->captured_piece=='R')&&(mvn->move[2]=='a')&&(mvn->move[3]=='1'))){
		/* Clear white's Q castling flag if it was present */
		if(brd->castling_flags&0x2){
			brd->castling_flags &= ~0x2;
			brd->key.whole^=zk.castle[1].whole;
		}
	}
	if(mvn->moving_piece=='k'){
		/* Clear black's castling flags if they were present */
		if(brd->castling_flags&0x4){
			brd->castling_flags &= ~0x4;
			brd->key.whole^=zk.castle[2].whole;
		}
		if(brd->castling_flags&0x8){
			brd->castling_flags &= ~0x8;
			brd->key.whole^=zk.castle[3].whole;
		}
	}
	if(((mvn->moving_piece=='r')&&(mvn->move[0]=='h')&&(mvn->move[1]=='8'))||
	((mvn->captured_piece=='r')&&(mvn->move[2]=='h')&&(mvn->move[3]=='8'))){
		/* Clear black's K castling flag if it was present */
		if(brd->castling_flags&0x4){
			brd->castling_flags &= ~0x4;
			brd->key.whole^=zk.castle[2].whole;
		}
	}
	if(((mvn->moving_piece=='r')&&(mvn->move[0]=='a')&&(mvn->move[1]=='8'))||
	((mvn->captured_piece=='r')&&(mvn->move[2]=='a')&&(mvn->move[3]=='8'))){
		/* Clear black's Q castling flag if it was present */
		if(brd->castling_flags&0x8){
			brd->castling_flags &= ~0x8;
			brd->key.whole^=zk.castle[3].whole;
		}
	}

	if(strchr("QBNRPqbnrpe",mvn->captured_piece)||
	strchr("Pp",mvn->moving_piece)){
		brd->half_moves=0; /* Zeroes the half-moves after pmove or capt */
	} else {
		brd->half_moves++;
	}

	if(strchr("kqbnrp",mvn->moving_piece)){
		brd->full_moves++; /* Full moves are completed by black */
	}

	/* Give the move to the other player */
	null_move(brd);
}

/* Returns 0 if successful */
int make_move_from_list(Board *brd, Movelist *mvl, char *move) {
	int i;
	for(i=0;i<mvl->size;i++){
		if((tolower(mvl->moves[i].move[0])==tolower(move[0])) &&
		(mvl->moves[i].move[1]==move[1]) &&
		(tolower(mvl->moves[i].move[2])==tolower(move[2])) &&
		(mvl->moves[i].move[3]==move[3]) &&
		(tolower(mvl->moves[i].move[4])==tolower(move[4]))) {
			make_move(brd,&(mvl->moves[i]));
			return 0;
		}
	}
	return 1;
}

/* Reverts a move back but does NOT preserve zobrist key
 * nor ep square nor castling flags nor half-moves nor full-moves,
 * i.e. backup them in search! */
void unmake_move(Board *brd, Move_node *mvn) {
	char from_square[3],to_square[3],ep_captured_square[3];
	char ep_captured_pawn;

	from_square[0] = mvn->move[0];
	from_square[1] = mvn->move[1];
	from_square[2] = 0;
	to_square[0]   = mvn->move[2];
	to_square[1]   = mvn->move[3];
	to_square[2]   = 0;

	/* Basic move action backwards */
	setsquare(brd,from_square,mvn->moving_piece);
	if((mvn->moving_piece=='P')&&(to_square[1]=='8')){
		resetsquare(brd,to_square,toupper(mvn->move[4]));
	} else if((mvn->moving_piece=='p')&&(to_square[1]=='1')){
		resetsquare(brd,to_square,tolower(mvn->move[4]));
	} else {
		resetsquare(brd,to_square,mvn->moving_piece);
	}

	if(strchr("QBNRPqbnrp",mvn->captured_piece)){
		/* Capture in reverse */
		setsquare(brd,to_square,mvn->captured_piece);
	} else if(mvn->captured_piece=='e'){
		/* En passant in reverse */
		strncpy(ep_captured_square,to_square,3);
		ep_captured_pawn='p';
		if(ep_captured_square[1]=='6'){
			ep_captured_square[1]='5';
		} else {
			ep_captured_square[1]='4';
			ep_captured_pawn='P';
		}
		setsquare(brd,ep_captured_square,ep_captured_pawn);
	}

	if(mvn->moving_piece=='K'){
		if(!strncmp(mvn->move,"e1g1",4)){
			/* Short castling */
			setsquare(brd,"h1",'R');
			resetsquare(brd,"f1",'R');
		}
		if(!strncmp(mvn->move,"e1c1",4)){
			/* Long castling */
			setsquare(brd,"a1",'R');
			resetsquare(brd,"d1",'R');
		}
	}

	if(mvn->moving_piece=='k'){
		if(!strncmp(mvn->move,"e8g8",4)){
			/* Short castling */
			setsquare(brd,"h8",'r');
			resetsquare(brd,"f8",'r');
		}
		if(!strncmp(mvn->move,"e8c8",4)){
			/* Long castling */
			setsquare(brd,"a8",'r');
			resetsquare(brd,"d8",'r');
		}
	}

	/* Give the move to the other player */
	null_move(brd);
}

void import_fen(Board *brd, const char *fen_string) {
	char copy_of_fen_string[128];
	char *rank;
	char *ptrptr;
	int i,j,k,pos;

	/* Clear the board before processing */
	absolutely_clear_board(brd);

	/* We make a copy of the string as strtok_r and
	 * similar ones changes its argument */
	strncpy(copy_of_fen_string,fen_string,127);

	rank=n_strtok_r(copy_of_fen_string,"/ ",&ptrptr); /* Gets 8th rank */
	for(i=7;i>=0;i--) { /* Loops through the ranks */
		pos=7; /* Start with a-file */
		for(j=0;rank[j]!=0;j++) { /* Loops through the description letters */
			if(isalpha(rank[j])) { /* Set up a piece */
				setsafe(brd,square_for_bit((i<<3)|pos),rank[j]);
				pos--; /* Next file */
			} else if(isdigit(rank[j])) { /* Set up empty squares */
				for(k=0;k<(int)(rank[j]-'0');k++){
					resetsafe(brd,square_for_bit((i<<3)|pos)); /* Empty square here */
					pos--; /* Next file */
				}
			}
		}
		rank=n_strtok_r(NULL,"/ ",&ptrptr); /* Gets next rank */
	}

	set_side_to_move(brd,rank[0]);
	rank=n_strtok_r(NULL,"/ ",&ptrptr);
	set_castling_flags(brd,rank);
	rank=n_strtok_r(NULL,"/ ",&ptrptr);
	if(rank[0]!='-'){
		setsafe(brd,rank,'e'); /* En passant square */
	} else if(brd->ep.whole) {
		resetsafe(brd,get_ep_square(brd)); /* Resets en-passant square */
	}
  	rank=n_strtok_r(NULL,"/ ",&ptrptr);
  	brd->half_moves=atoi(rank);
  	rank=ptrptr;
  	brd->full_moves=atoi(rank);
}

void export_fen(Board *brd, char *fen_string) {
	int i,j,empty_square_count;
	char *fen_string_ptr=fen_string;
	for(i=7;i>=0;i--) { /* Loops through the ranks */
		empty_square_count=0;
		for(j=7;j>=0;j--) { /* Loops through the files */
			if((testsquare(brd,square_for_bit((i<<3)|j))!=' ') &&
				(testsquare(brd,square_for_bit((i<<3)|j))!='e')) {
				/* Here is a piece */
				if(empty_square_count>0) {
					*(fen_string_ptr++)='0'+empty_square_count;
				}
				*(fen_string_ptr++)=testsquare(brd,square_for_bit((i<<3)|j));
				empty_square_count=0;
			} else {
				empty_square_count++;
			}
		}
		if(empty_square_count>0) {
		/* 'flush' the remaining empty squares */
		*(fen_string_ptr++)='0'+empty_square_count;
		}
		if(i>0) {
			*(fen_string_ptr++)='/';
		}
	}
	*(fen_string_ptr++)=' ';
	*(fen_string_ptr++)=brd->whosmove;
	*(fen_string_ptr++)=' ';
	appendtostring(test_castling_flags(brd),&fen_string_ptr);
	*(fen_string_ptr++)=' ';
	if(brd->ep.whole) {
		appendtostring(get_ep_square(brd),&fen_string_ptr);
	} else {
		appendtostring("-",&fen_string_ptr);
	}
	*(fen_string_ptr++)=' ';
	numtostring(brd->half_moves,&fen_string_ptr);
	*(fen_string_ptr++)=' ';
	numtostring(brd->full_moves,&fen_string_ptr);
	*fen_string_ptr=0;
}

/* Sets the initial position */
void reset_board(Board *brd){
	import_fen(brd,"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
}
