#include "defs.h"
#include "search.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.h>

#define BOOK

char line[256];
extern int console;
extern int ehashsize;
extern sEHASHENTRY * ehashtable;
extern int hashsize;
extern sHASHENTRY * hashtable;
extern int scores[16];
extern int board[256];
extern int stm;
extern sMOVES * last_move[MAXPLY];
extern int ply;
extern sHISTDATA * mhs;
extern UI64 key;
extern uMOVE pv[MAXPLY][MAXPLY];
extern sHISTDATA move_history[512];
extern sMOVES move_list[4096];
extern FILE * debuglog;
extern FILE * trace;
extern char ms[128];

extern int feature[2][nbFeatures];
extern const char * Feature_name[nbFeatures];

int inCheck(int c);
void gen(void);
int genc(void);
void domove(const uMOVE *);
void unmove(void);
void show_pst(void);
void display(void);
void setup(char *);
void flip_board(void);
void printmvw(const uMOVE *m);
void printmoves(void);
void init_zobrist(void);
void init_delta_table(void);
void init_pst(void);
void init_iso(void);
void init_p_curve(void);
void init_pp_scores(void);
void check_pst(void);
void bubblemvs(void);
int think(int maxtime, int reserve, int maxdeep);
int quies(int alpha, int beta);
UI64 perft(int);
void histograph(void);
UI64 makehashkey(void);
int tallyhash(void);
void runsuite(char * epd, int ms, int deep);
void test(void);
void readbook(void);
sHASHENTRY * probe(void);
void move2str(uMOVE * m, char * s);
int nsee(int start, int target);
int move_is_legal(uMOVE *mv);
void hclear();

uMOVE * s2move(char * s)
{
	static uMOVE mm;
	int f, d, p, t;
	sMOVES * m;

	mm.asint = 0;

	f = A1 + ((7-(s[1]-'1')) << 4) + ((s[0] - 'a') & 7);
	d = A1 + ((7-(s[3]-'1')) << 4) + ((s[2] - 'a') & 7);
	p = board[f];
	t = 0;

	if(s[4] == 'q' || s[4] == 'Q')
	{
		t = PRO;
		p = BQ|stm;
	}
	else if(s[4] == 'r' || s[4] == 'R')
	{
		t = PRO;
		p = BR|stm;
	}
	else if(s[4] == 'b' || s[4] == 'B')
	{
		t = PRO;
		p = BB|stm;
	}
	else if(s[4] == 'n' || s[4] == 'N')
	{
		t = PRO;
		p = BN|stm;
	}

	for(m = last_move[ply-1]; m < last_move[ply]; m++)
	{
		if(m->move.data.from == f)
			if(m->move.data.dest == d)
			{
				if(t)
				{
					if(m->move.data.type == t && m->move.data.piece == p)
						return(&m->move);
				}
				else
					return(&m->move);
			}
	}

	return &mm;
}

void xboard()
{
	int computer_side;
	int played = 0;
	char command[256], utility[256];
	uMOVE * m;
	sMOVES * cm;
	sMOVES * bm;
	int inbook = 0;
	int ponder = 0;
	int self = 0;
	sHASHENTRY * hentry;
	int post = 0;
	int max_time = 10000, res_time = 0, max_depth = 63;
	int mpt = 0;
	int rmoves = 1;
	int rtime = 10000, otime = 10000;
	int tpm = 0;
	int inc = 0;
	int offer = 0;
	int d, i, temp, temp2;
	UI64 tt;
	sMOVES bookmvs[64];
	char oname[32];
	signal(SIGINT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	printf("\n");
	setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -");



#ifdef BOOK	
	readbook();
#endif	
	console = 1;
	computer_side = 2;
	for (;;)
	{
		if(computer_side == 2) self = 0;
		if(self) {
			rtime = 0;
			otime = 0;
			inc = 100;
			mpt = 0;
			computer_side = stm;
		}
		
		if (stm == computer_side)
		{

#ifdef BOOK			
			inbook = (int)(mhs-move_history < 24);

			if(inbook){
				inbook = 0;
				bm = bookmvs;
				bubblemvs();
				printf("( ");

				for(cm = last_move[ply-1];  cm < last_move[ply]; ++cm, ++bm){

					*bm = *cm;
					bm->score = -MATE;

					domove(&cm->move);

					if(inCheck(stm^1)){
						unmove();
						continue;
					}

					hentry = probe();
					if(hentry)
					{
						if(hentry->data == 0)
						{
							/*
							Done move [ie, stm == snm]
							value has outcome of game [+1 -1 0] from white's vantage
							so if 'player' is BLACK then it should prefer 0's & -1's
							*/

							temp = hentry->value;
							temp = (temp > -10) - (temp < 10); 
							if((((1-stm)<<1)-1) * temp >= 0)
							{
								bm->score = MATE;
								inbook = 1;

							}
							
							
							{
								unmove();
								printmv(&cm->move);
								printf("=%d ", hentry->value);
								domove(&cm->move);
							}
						}
					}

					unmove();

				}
				printf(")\n");
				fflush(stdout);

				pv[1][1].asint = 0;

				if(inbook){

					inbook = 0;

					
					do{
						bm = bookmvs;
						for(cm = last_move[ply-1]; cm < last_move[ply]; ++cm, ++bm){
							
							

							if(bm->score == MATE &&  rand() < (RAND_MAX/10)){
								pv[1][1] = cm->move;
								
								inbook = 1;
								break;
							}
						}
					}while(pv[1][1].asint == 0);
					
				}
				

			}        

#endif

			if(!inbook)
			{

				{
                    		
					if (isdrawn())
					{
						fprintf(stdout,"1/2-1/2 {Draw by Lack of Force}\n");
						computer_side = 2;
						fflush(stdout);
						continue;
					}

					if (rep3())
					{
						fprintf(stdout,"1/2-1/2 {Draw by Repetition}\n");
						computer_side = 2;
						fflush(stdout);
						continue;
					}
					if (fifty == 100)
					{
						fprintf(stdout,"1/2-1/2 {Draw by Fifty Move Rule}\n");
						computer_side = 2;
						fflush(stdout);
						continue;
					}
				}

				/*
				 * Time control is a pain!!!!
				 *
				 * 1. How many moves do we have to give time to? easy enough ...
				 * 2. How much 'extra' 'reserve' time can we allow? more in beginning but none at the end!
				 * 3. How to adjust for more/less time the opponent have? 
				 *    again, we can 'adjust' in the begin, but cannot do too much in the end!!!
				*/
				
				
				rtime -= 100;
				if(rtime < 0) rtime = 0;
				otime -= 100;
				if(otime < 0) otime = 0;

				if(mpt) 
				{
					
					
					rmoves = mpt - ((((mhs-move_history+played)/2)) % mpt);
					
					
					max_time = rtime / rmoves; 
					
					
					max_time += (rtime-otime) / 3;

				}
				else 
				{
					
					temp = abs(scores[0])/25;
					if(temp > 20) temp = 20;
					rmoves = 25 - temp;

					
					max_time = rtime/rmoves;

					
					max_time += inc;

					
					max_time += (rtime-otime) / 2;
					
				}
				
				
				
				if(max_time < 0)				
					max_time = rtime / (rmoves+1);

				if(max_time > rtime) max_time = rtime;		

				if(rmoves == 1)
					max_time = rtime;			
					
			
				if(rmoves > 1) {
					
					res_time = rmoves * max_time / 15; /* was 18 */
					if(max_time + res_time > rtime) res_time = 0;
					
					/*
					res_time = min(max_time*4,(rtime-max_time)/3);
					*/

					if(res_time < 100) res_time = 0;
				}
				else res_time = 0;

				
				temp = think(max_time, res_time, max_depth);
                		
				if(otime > 1000 && otime > rtime/4 && temp < -600 && scores[0] < -600 && scores[1] < -600)
				{
					printf("resign\n");
				}

				d = 0;

				for(i = 15; i > 0; --i)
				{
					d += abs(scores[i] - scores[i-1]);
					scores[i] = scores[i-1];
				}
				d += abs(scores[0] - temp);
				scores[0] = temp;

				if(offer < 4 && mhs - move_history > 40 && otime > 1000 && otime > rtime/4 && d < 10)
				{
					printf("offer draw\n");
					++offer;
				}


				if (!pv[1][1].asint)
				{
					if(temp < -MATE+1)
					{
						if(computer_side == WHITE)
							printf("0-1 {Black mates}\n");
						else
							printf("1-0 {White mates}\n");
					}
					else if(temp > MATE-1)
					{
						if(computer_side == WHITE)
							printf("1-0 {White mates}\n");
						else
							printf("0-1 {Black mates}\n");
					}
					else
						printf("1/2-1/2 {stalemate}\n");
					
					computer_side = 2;
					fflush(stdout);
					continue;
				}

				
			}

			
			
				

			printf("move ");
			printmvw(&pv[1][1]);
			printf("\n");
			fflush(stdout);

			domove(&pv[1][1]);
			if(console) display();

			
			ply = 1;
			mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
			gen();

			if(ponder && !inbook && abs(temp) < MATE-MAXPLY)
			{
				
				{
					
					
					
					temp = think(999999, 0, 63);
					
				}
			}

			continue;
		}
		
		
		
		{
			if (!fgets(line, 256, stdin))
			{
				//continue;
				return;
			}
		}

		
		
		sscanf(line, "%s", command);
		
		
		if (line[0] == '\n')
			continue;

		
		
		if (!strcmp(command, "time"))
		{
			sscanf(line, "time %d", &rtime);

			rtime *= 10;

			continue;
		}

		if (!strcmp(command, "otim"))
		{
			sscanf(line, "otim %d", &otime);
			otime *= 10;
			continue;
		}

		if (!strcmp(command, "new"))
		{
			setup("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -");
#ifdef BOOK
			readbook();
#endif
			offer = 0;
			//memset(scores, squies(-MATE, MATE), sizeof(scores));
			computer_side = BLACK;
			played = 0;
#ifdef LOG
			fprintf(debuglog, "\n------New Game-----  %s\n", oname);
			fprintf(debuglog, "Time   Reserv Mo   Depth    Knodes evals FH1   BF   Extend   Checks   recap    etd    rtd     pawn     end   seekT   1reps   mate   dangT    null   delta  futile xfutile   razor    hist  reducT     res hash  hit  WW P BB P   move  score  delta     ?  ? fen\n");
#endif			
#ifdef BLUNDERS
			fprintf(blunders, "\n------New Game-----  %s\n", oname);
			fprintf(blunders, "F  P  D  fen\n");			
#endif

			if(console) display();
			continue;
		}

		if (!strcmp(command, "quit") || !strcmp(command, "q"))
			return;

		if (!strcmp(command, "exit")) 
			continue;

		if (!strcmp(command, "force"))
		{
			computer_side = 2;
			continue;
		}
		if (!strcmp(command, "white"))
		{
			if(stm == BLACK)
				key = ~key;
			stm = WHITE;
			ply = 1;
			
			mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
			gen();
			computer_side = BLACK;
			continue;
		}
		if (!strcmp(command, "black"))
		{
			if(stm == WHITE)
				key = ~key;
			stm = BLACK;
			ply = 1;
			
			mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
			gen();
			computer_side = WHITE;
			continue;
		}
		if (!strcmp(command, "st"))
		{
			sscanf(line, "st %d", &max_time);
			max_time *= 1000;
			rtime = max_time;
			rmoves = 1;
			max_depth = 62;
			continue;
		}
		if (!strcmp(command, "sd"))
		{
			sscanf(line, "sd %d", &max_depth);
			max_time = 1 << 25;
			rtime = max_time;
			continue;
		}


		if (!strcmp(command, "go") || !strcmp(command, "g"))
		{
			computer_side = stm;
			continue;
		}

		if (!strcmp(command, "?"))
		{
			computer_side = 2;
			continue;
		}

		if (!strcmp(command, "."))
		{
			
			continue;
		}

		if(!strcmp(command, "result"))
		{
			computer_side = 2;
			continue;
		}

		if (!strcmp(command, "xboard"))
		{
			console = 0;
			continue;
		}

		if (!strcmp(command, "hint"))
		{
			think(max_time, 0, max_depth);
			if (!pv[1][1].asint)
				continue;
			printf("Hint: ");
			printmv(&pv[1][1]);
			printf("\n");
			continue;
		}

		if (!strcmp(command, "analyze"))
		{
			computer_side = 2;
			think(9999999, 0, 63);
			continue;
		}

		if (!strcmp(command, "undo"))
		{
			if (mhs > move_history)
			{
				unmove();
				ply = 1;
				
				mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
				gen();
				if(console) display();
			}
			continue;
		}
		if (!strcmp(command, "remove"))
		{
			if (mhs > move_history+1)
			{
				unmove();
				unmove();
				ply = 1;
				
				mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
				gen();
				if(console) display();
			}
			continue;
		}

		if (!strcmp(command, "post"))
		{
			post = 2;
			continue;
		}
		if (!strcmp(command, "nopost"))
		{
			post = 0;
			continue;
		}

		if (!strcmp(command, "protover"))
		{
			printf("feature sigint=0 sigterm=0 setboard=1 analyze=1 ping=1 name=1 myname=\"Xpdnt %s\" done=1\n", VERSION);

			fflush(stdout);
			continue;
		}

		if (!strcmp(command, "ping"))
		{
			sscanf(line, "ping %d", &temp);
			printf("pong %d\n", temp);
			fflush(stdout);
			continue;
		}

		if (!strcmp(command, "computer"))
		{
			continue;
		}

		if (!strcmp(command, "random"))
		{
			continue;
		}

		if (!strcmp(command, "accepted"))
		{
			continue;
		}
		/*
		normal tc == 40 5 0
		sudden death == 0 5 0
		incremental == 0 0 5  or  0 10 1

		so, where mps == 0, we use tpc/30 + inc
		otherwise, we keep track of moves left, and divide time accordingly
		*/
		if (!strcmp(command, "level"))
		{
			sscanf(line, "level %d %d %d", &mpt, &tpm, &inc);
			if(mpt) rmoves = mpt;
			else rmoves = 30;
			inc *= 10;
			continue;
		}

		if (!strcmp(command, "hard"))
		{
			ponder = 1;
			continue;
		}

		if (!strcmp(command, "easy"))
		{
			ponder = 0;
			continue;
		}

		if (!strcmp(command, "eval") || !strcmp(command, "e"))
		{
			eclear();
			memset(feature, 0, sizeof(feature));
			temp = evaluate();
			for (i=0; i<nbFeatures; ++i) {
				printf("WHITE %s: %d\tBLACK %s: %d\n", Feature_name[i], feature[WHITE][i], Feature_name[i], feature[BLACK][i]);
				}
			printf("TOTAL(stm):	%d\n", temp);	
			continue;
		}

		if (!strcmp(command, "d"))
		{
			display();
			continue;
		}

		if (!strcmp(command, "setboard"))
		{
			setup(line+9);
			temp=0;
			temp2=1;
			sscanf(line, "setboard %s %s %s %s %d %d", command, command, command, command, &temp, &temp2);
			fifty = temp;
			played = (stm == BLACK);
			offer = 0;
			continue;
		}

		if (!strcmp(command, "flip"))
		{
			flip_board();
			continue;
		}

		if (!strcmp(command, "bench"))
		{
			
			setup("8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - -");
			display();
			
			continue;
		}

		if (!strcmp(command, "list"))
		{
			bubblemvs();
			printmoves();
			continue;
		}

		if (!strcmp(command, "perft"))
		{
			sscanf(line, "perft %d", &temp);
			tt = clock();
			temp = perft(temp);
			tt = clock() - tt;
			printf("%d %d\n", temp, (int)tt);
			
			continue;
		}

		if (!strcmp(command, "hist"))
		{
			histograph();
			continue;
		}

		if (!strcmp(command, "tally"))
		{
			printf("%d\n", tallyhash());
			continue;
		}

		if (!strcmp(command, "hash"))
		{
			printf("%u\n", (unsigned int)makehashkey());
			continue;
		}

		if(!strcmp(command, "hashsize"))
		{
			sscanf(line, "hashsize %d", &temp);
			if(temp < 1 || temp > 1024)
			{
				printf("Please choose value between 1 & 1024\n");
			}
			else
			{
				temp = temp * 1024 * 1024 / sizeof(sHASHENTRY);
				temp2 = 1;
				while(temp2 < temp) temp2 <<= 1;
				hashsize = temp2;
				if(hashtable) free(hashtable);
				hashtable =(sHASHENTRY *) malloc(hashsize * sizeof(sHASHENTRY));
			}
			continue;
		}

		if(!strcmp(command, "ehashsize"))
		{
			sscanf(line, "ehashsize %d", &temp);
			if(temp < 1 || temp > 1024)
			{
				printf("Please choose value between 1 & 1024\n");
			}
			else
			{
				temp = temp * 1024 * 1024 / sizeof(sEHASHENTRY);
				temp2 = 1;
				while(temp2 < temp) temp2 <<= 1;
				ehashsize = temp2;
				if(ehashtable) free(ehashtable);
				ehashtable =(sEHASHENTRY *) malloc(ehashsize * sizeof(sEHASHENTRY));
			}
			continue;
		}

		if(!strcmp(command, "runsuite"))
		{
			sscanf(line, "runsuite %s %d %d", utility, &temp, &temp2);
			runsuite(utility, temp, temp2);
			continue;
		}

		if(!strcmp(command, "stress"))
		{
			runsuite("stress", 30000, 63);
			continue;
		}

		if(!strcmp(command, "test"))
		{
			test();
			continue;
		}

		if(!strcmp(command, "pst"))
		{
			show_pst();
			continue;
		}

		/*
		if(!strcmp(command, "walk"))
		{
		treewalk(line+5, 8);
		continue;
		}
		*/

		/*
		if(!strcmp(command, "see"))
		{
			m = s2move(line+4);
			
			printf("%d\n", nsee(m->data.from, m->data.dest));
			continue;
		}
		*/

		if(!strcmp(command, "name"))
		{
			strcpy(oname, line+5);
			continue;
		}
		
		if(!strcmp(command, "self"))
		{
			self = 1;
			computer_side = 0;
			continue;
		}

		
		if(strcspn(line, "ijklmopstuvwxyz90") < strlen(line))
		{
			printf("Error (unknown command): %s\n", command);
			fflush(stdout);
			continue;
		}

		/*
			At this point, the command better be a move
		*/

		m = s2move(line);
		if(m->asint)
		{
			assert(!checkmove(m));
			
			domove(m);
			if(inCheck(stm^1))
			{
				unmove();
				printf("Error (illegal move): %s King in check!\n", command);
				fflush(stdout);
				continue;
			}

			
			assert(!checkboard());
			
			ply = 1;
			mhs->threat = (mhs->threat & (ALL_THREATS-IN_CHECK)) | inCheck(stm);
			gen();
			if(console) display();

		}
		else
		{
			printf("Error (illegal move): %s\n", command);
			fflush(stdout);
		}

	}
}

int main(int argc, char * argv[])
{
#ifdef LOG
	char name[32];
#endif
	int temp, temp2;
	FILE * ini;

#ifndef NDEBUG
printf("debug version!");
#endif
	
	
	
	if ((ini = fopen("./settings.ini", "r"))==NULL)
	{
		printf("Cannot open settings.ini \n");

		
	}

	fscanf(ini, "hashsize=%d ehashsize=%d", &hashsize, &ehashsize);
	fclose(ini);


	temp=hashsize;
	if(temp < 1 || temp > 1024)
	{
		printf("Please choose value between 1 & 1024\n");
		exit(1);
	}
	else
	{
		temp = temp * 1024 * 1024 / sizeof(sHASHENTRY);
		temp2 = 1;
		while(temp2 < temp) temp2 <<= 1;
		hashsize = temp2;
		if(hashtable) free(hashtable);
		hashtable =(sHASHENTRY *) malloc(hashsize * sizeof(sHASHENTRY));
		//printf("%u\n", hashsize);
		//printf("%u\n", sizeof(sHASHENTRY));
		//printf("%u\n", sizeof(int));
	}

	temp=ehashsize;
	if(temp < 1 || temp > 1024)
	{
		printf("Please choose value between 1 & 1024\n");
		exit(1);
	}
	else
	{
		temp = temp * 1024 * 1024 / sizeof(sEHASHENTRY);
		temp2 = 1;
		while(temp2 < temp) temp2 <<= 1;
		ehashsize = temp2;
		if(ehashtable) free(ehashtable);
		ehashtable =(sEHASHENTRY *) malloc(ehashsize * sizeof(sEHASHENTRY));
		//printf("%u\n", ehashsize);
		//printf("%u\n", sizeof(sEHASHENTRY));
	}
	
	

	srand((unsigned)(time(NULL)));
	init_zobrist();
	init_delta_table();
	init_iso();
	init_pst();
	//init_curves();
	
	/* memset (history, 0, sizeof (history)); */
	hclear();
	/* memset (histcnt, 1, sizeof (histcnt)); */
	
/*
	hashtable = (sHASHENTRY *) malloc(sizeof(sHASHENTRY)* hashsize);
	ehashtable = (sEHASHENTRY *) malloc(sizeof(sEHASHENTRY) * ehashsize);
*/
	
	
	mhs = move_history;
	last_move[0] = move_list;
	



#ifdef LOG
	
	
	
	strcpy(name, "./debug.log");

	/*
	if ((debuglog = fopen("logs/name.txt", "r+"))==NULL)
	{
		printf("Cannot open file \n");
		exit(1);
	}
	fscanf(debuglog, "%d", &temp);
	rewind(debuglog);
	fprintf(debuglog, "%d", temp+1);

	fclose(debuglog);
	
	itoa(temp+1, name+5, 10);
	strcat(name, ".log");
	*/

	if ((debuglog = fopen(name, "a"))==NULL)
	{
		printf("Cannot open %s\n", name);
		exit(1);
	}
#endif

#ifdef TRACE
	
	if ((trace = fopen("./logs/trace.log", "a"))==NULL)
	{
		printf("Cannot open trace.log \n");
		exit(1);
	}
	
#endif

#ifdef BLUNDERS
	if ((blunders = fopen("./blunders.epd", "a"))==NULL)
	{
		printf("Cannot open blunders.epd \n");
		exit(1);
	}
#endif


	/*
	for(temp = 1; temp < argc; ++temp)
	{
		
		printf("%d %s\n", temp, argv[temp]);
		sscanf(argv[temp], "%s", line);
		
	};
	*/

	xboard();
	

	
#ifdef LOG
	fclose(debuglog);
#endif

#ifdef BLUNDERS
	fclose(blunders);
#endif
	
	free(hashtable);
	free(ehashtable);
	
#ifdef TRACE	
	fclose(trace);
#endif

	return (0);
}


