#include <clib/alib_protos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "common.h"
#include "book.h"

#define MAXMOVES 200
#define MAXMATCH 100

static int bookcnt;
static HashType posshash[MAXMOVES];

static int book_allocated=0;

#define MAGIC_LENGTH 5

static const char magic_str[]="\x42\x23\x08\x15\x03";

static int check_magic(FILE *f)
{
char buf[MAGIC_LENGTH];
return fread(&buf,1,MAGIC_LENGTH,f)==MAGIC_LENGTH&&memcmp(buf,magic_str,MAGIC_LENGTH)==0;
}

static int write_magic(FILE *f)
{
return (fwrite(&magic_str,1,MAGIC_LENGTH,f)!=MAGIC_LENGTH)?BOOK_EIO:BOOK_SUCCESS;
}

static int write_size(FILE *f,unsigned int size)
{
unsigned char sizebuf[4];
int k;
for(k=0;k<4;k++) sizebuf[k]=(size>>((3-k)*8))&0xff;
if(fwrite(&sizebuf,sizeof(sizebuf),1,f)==1) return BOOK_SUCCESS;
return BOOK_EIO;
}

static unsigned int read_size(FILE *f)
{
unsigned char sizebuf[4];
unsigned int size=0;
int k;
if(fread(&sizebuf,sizeof(sizebuf),1,f)!=1) return 0;
for(k=0;k<4;k++) size=(size<<8)|sizebuf[k];
return size;
}

#define MAX_DIGEST_BITS 20

static int digest_bits;

#define DIGEST_SIZE (1UL<<digest_bits)
#define DIGEST_MASK (DIGEST_SIZE-1)

static struct hashtype
{
unsigned short wins;
unsigned short losses;
unsigned short draws;
HashType key;
} *bookpos;

#ifdef __STORM__
__inline int is_empty(unsigned int index)
#else
static inline int is_empty(unsigned int index)
#endif
{
return bookpos[index].key==0&&bookpos[index].wins==0&&bookpos[index].draws==0&&bookpos[index].losses==0;
}

#define DIGEST_START(i,key) ((i)=(key)&DIGEST_MASK)
#define DIGEST_MATCH(i,the_key) ((the_key)==bookpos[i].key)
#define DIGEST_EMPTY(i)is_empty(i)
#define DIGEST_COLLISION(i,key) (!DIGEST_MATCH(i,key)&&!DIGEST_EMPTY(i))
#define DIGEST_NEXT(i,key) ((i)=((i)+(((key)>>digest_bits)|1))&DIGEST_MASK)

static int bookhashcollisions=0;

#define DIGEST_LIMIT (0.95*DIGEST_SIZE)

static unsigned char buf[2+2+2+8];

static const int wins_off=0;
static const int losses_off=2;
static const int draws_off=4;
static const int key_off=6;

static void buf_to_book()
{
HashType key;
unsigned int i;
key=((unsigned long long)buf[key_off]<<56)|
    ((unsigned long long)buf[key_off+1]<<48)|
    ((unsigned long long)buf[key_off+2]<<40)|
    ((unsigned long long)buf[key_off+3]<<32)|
    ((unsigned long long)buf[key_off+4]<<24)|
    ((unsigned long long)buf[key_off+5]<<16)|
    ((unsigned long long)buf[key_off+6]<<8)|
    ((unsigned long long)buf[key_off+7]);
for(DIGEST_START(i,key);DIGEST_COLLISION(i,key);DIGEST_NEXT(i,key)) bookhashcollisions++;
bookpos[i].wins+=(buf[wins_off]<<8)|buf[wins_off+1];
bookpos[i].draws+=(buf[draws_off]<<8)|buf[draws_off+1];
bookpos[i].losses+=(buf[losses_off]<<8)|buf[losses_off+1];
bookpos[i].key=key;
}

static void book_to_buf(unsigned int index)
{
int k;
for(k=0;k<2;k++)
	{
	int k1=(1-k)*8;
	buf[wins_off+k]=(bookpos[index].wins>>k1)&0xff;
	buf[draws_off+k]=(bookpos[index].draws>>k1)&0xff;
	buf[losses_off+k]=(bookpos[index].losses>>k1)&0xff;
	}
for(k=0;k<8;k++) buf[key_off+k]=((bookpos[index].key)>>((7-k)*8))&0xff;
}

static int compare(const void *aa,const void *bb)
{
int ret;
const leaf *a=(const leaf *)aa;
const leaf *b=(const leaf *)bb;
if(b->score>a->score) ret=1;
else if(b->score<a->score) ret=-1;
else ret=0;
return ret;
}

static int read_book(FILE *f)
{
if(book_allocated)
	{
	free(bookpos);
	book_allocated=0;
	}
bookpos=(struct hashtype *)calloc(DIGEST_SIZE,sizeof(struct hashtype));
if(!bookpos) return BOOK_ENOMEM;
book_allocated=1;
bookcnt=0;
bookhashcollisions=0;
while(fread(&buf,sizeof(buf),1,f)==1)
	{
	buf_to_book();
	bookcnt++;
	}
return BOOK_SUCCESS;
}

int BookBuilderOpen()
{
FILE *rfp,*wfp;
int res;
char text[100];
if(rfp=fopen(BOOKRUN,"rb"))
	{
	DoMethod(mui_app,MUIM_Chess_ShowThinking,"Opened existing book!");
	if(!check_magic(rfp))
		{
/*		fprintf(stderr,"File %s does not conform to the current format.\nConsider rebuilding your book.\n",BOOKRUN); */
		return BOOK_EFORMAT;
		}
	digest_bits=MAX_DIGEST_BITS;
	read_size(rfp);
	res=read_book(rfp);
	fclose(rfp);
	if(res!=BOOK_SUCCESS)
		{
		fclose(rfp);
		return res;
		}
	sprintf(text,"Read %d book positions. Got %d hash collisions.",bookcnt,bookhashcollisions);
	DoMethod(mui_app,MUIM_Chess_ShowThinking,text);
	}
else
	{
	wfp=fopen(BOOKRUN,"w+b");
	if(wfp==NULL)
		{
/*		fprintf(stderr,"Could not create %s file: %s\n",BOOKRUN,strerror(errno)); */
		return BOOK_EIO;
		}
	if(write_magic(wfp)!=BOOK_SUCCESS)
		{
/*		fprintf(stderr,"Could not write to %s: %s\n",BOOKRUN,strerror(errno)); */
		return BOOK_EIO;
		}
	if(fclose(wfp))
		{
/*		fprintf(stderr,"Could not write to %s: %s\n",BOOKRUN,strerror(errno)); */
		return BOOK_EIO;
		}
	sprintf(text,"Created new book %s!",BOOKRUN);
	DoMethod(mui_app,MUIM_Chess_ShowThinking,text);
	if(!(rfp=fopen(BOOKRUN,"rb")))
		{
/*		fprintf(stderr,"Could not open %s for reading: %s\n",BOOKRUN,strerror(errno)); */
		return BOOK_EIO;
		}
	digest_bits=MAX_DIGEST_BITS;
	if(read_book(wfp)==BOOK_ENOMEM) return BOOK_ENOMEM;
	}
return BOOK_SUCCESS;
}

int BookBuilder(short result,short side)
{
unsigned int i;
if(GameCnt/2+1>=BOOKDEPTH) return BOOK_EMIDGAME;
CalcHashKey();
for(DIGEST_START(i,HashKey);;DIGEST_NEXT(i,HashKey))
	{
	if(DIGEST_MATCH(i,HashKey))
		{
		existpos++;
		break;
		}
	else if(DIGEST_EMPTY(i))
		{
		if(bookcnt>DIGEST_LIMIT) return BOOK_EFULL;
		bookpos[i].key=HashKey;
		newpos++;
		bookcnt++;
		break;
		}
	else bookhashcollisions++;
	}
if(side==white)
	{
	if(result==R_WHITE_WINS) bookpos[i].wins++;
	else if(result==R_BLACK_WINS)
	bookpos[i].losses++;
	else if(result==R_DRAW) bookpos[i].draws++;
	}
else
	{
	if(result==R_WHITE_WINS) bookpos[i].losses++;
	else if(result==R_BLACK_WINS) bookpos[i].wins++;
	else if(result==R_DRAW) bookpos[i].draws++;
	}
return BOOK_SUCCESS;
}

int BookBuilderClose()
{
FILE *wfp;
unsigned int i;
int errcode=BOOK_SUCCESS;
char text[100];
if(!(wfp=fopen(BOOKRUN,"wb")))
	{
	errcode=BOOK_EIO;
	goto bailout_noclose;
	}
if(write_magic(wfp)!=BOOK_SUCCESS)
	{
	errcode=BOOK_EIO;
	goto bailout;
	}
if(write_size(wfp,bookcnt)!=BOOK_SUCCESS)
	{
	errcode=BOOK_EIO;
	goto bailout;
	}
for(i=0;i<DIGEST_SIZE;i++)
	{
	if(!is_empty(i))
		{
		book_to_buf(i);
		if(fwrite(&buf,sizeof(buf),1,wfp)!=1)
			{
			errcode=BOOK_EIO;
			goto bailout;
			}
		}
	}
sprintf(text,"Got %d hash collisions.",bookhashcollisions);
DoMethod(mui_app,MUIM_Chess_ShowThinking,text);

bailout:
if(fclose(wfp)) errcode=BOOK_EIO;

bailout_noclose:
free(bookpos);
book_allocated=0;
bookloaded=0;
return errcode;
}

int BookQuery()
{
int i,j,k,icnt=0,mcnt,found,tot,maxdistribution;
int matches[MAXMATCH] ;
leaf m[MAXMOVES];
leaf pref[MAXMOVES];
struct {
unsigned short wins;
unsigned short losses;
unsigned short draws;
} r[MAXMOVES];
FILE *rfp;
leaf *p;
short side,xside,temp;
unsigned int booksize;
int res;
char text[100];
if(bookloaded&&!book_allocated) return BOOK_ENOBOOK;
if(!bookloaded)
	{
	bookloaded=1;
	if(!(rfp=fopen(BOOKBIN,"rb"))) return BOOK_ENOBOOK;
	sprintf(text,"Read opening book (%s)...",BOOKBIN);
	DoMethod(mui_app,MUIM_Chess_ShowThinking,text);
	if(!check_magic(rfp))
		{
/*		fprintf(stderr," File %s does not conform to the current format.\n Consider rebuilding the book.\n\n",BOOKBIN); */
		return BOOK_EFORMAT;
		}
	booksize=read_size(rfp)*1.06;
	for(digest_bits=1;booksize;booksize>>=1) digest_bits++;
	res=read_book(rfp);
	if(res!=BOOK_SUCCESS) return res;
	sprintf(text,"%d hash collisions... ",bookhashcollisions);
	DoMethod(mui_app,MUIM_Chess_ShowThinking,text);
	}
mcnt=-1;
side=board.side;
xside=1^side;
TreePtr[2]=TreePtr[1];
GenMoves(1);
FilterIllegalMoves(1);
for(p=TreePtr[1];p<TreePtr[2];p++)
	{
	MakeMove(side,&p->move);
	m[icnt].move=p->move;
	posshash[icnt]=HashKey;
	icnt++;
	UnmakeMove(xside,&p->move);
	}
for(i=0;i<icnt;i++)
	{
	for(DIGEST_START(j,posshash[i]);!DIGEST_EMPTY(j);DIGEST_NEXT(j,posshash[i]))
		{
		if(DIGEST_MATCH(j,posshash[i]))
			{
			found=0;
			for(k=0;k<mcnt;k++) if(matches[k]==i)
				{
				found=1;
				break;
				}
			if(!found)
				{
				matches[++mcnt]=i;
				pref[mcnt].move=m[matches[mcnt]].move;
				r[i].losses=bookpos[j].losses;
				r[i].wins=bookpos[j].wins;
				r[i].draws=bookpos[j].draws;
				pref[mcnt].score=m[i].score=100*(r[i].wins+(r[i].draws/2))/(MAX(r[i].wins+r[i].losses+r[i].draws,1))+r[i].wins/2;
				}
			if(mcnt>=MAXMATCH)
				{
				sprintf(text,"Too many matches in book.");
				DoMethod(mui_app,MUIM_Chess_ShowThinking,text);
				goto fini;
				}
			break;
			}
		}
	}

fini:  
if(mcnt==-1) return BOOK_ENOMOVES;
k=0;
if(mcnt+1)
	{
	if(bookmode==BOOKRAND)
		{
		k=rand();
		k=k%(mcnt+1);
		RootPV=m[matches[k]].move;
		printf("\n(Random picked move #%d %s%s from above list)\n",k,algbr[FROMSQ(RootPV)],algbr[TOSQ(RootPV)]);
		tot=r[matches[k]].wins+r[matches[k]].draws+r[matches[k]].losses;
		if(tot) printf("B p=%2.0f\n",100.0*(r[matches[k]].wins+r[matches[k]].draws)/tot);
		else printf("p=NO EXPERIENCES\n");
		}
	else if(bookmode==BOOKBEST)
		{
		qsort(&pref,mcnt+1,sizeof(leaf),compare);
		RootPV=pref[0].move;
		}
	else if(bookmode==BOOKWORST)
		{
		qsort(&pref,mcnt+1,sizeof(leaf),compare);
		RootPV=pref[mcnt].move;
		}
	else if(bookmode==BOOKPREFER)
		{
		qsort(&pref,mcnt+1,sizeof(leaf),compare);
		for(i=0;i<=mcnt;i++)
			{
			m[i].move=pref[i].move;
			}
		temp=(bookfirstlast>mcnt+1?mcnt+1:bookfirstlast);
		maxdistribution=0;
		for(i=0;i<temp;i++) maxdistribution+=pref[i].score;
		if(!maxdistribution) return BOOK_ENOMOVES;
		k=rand()%maxdistribution;
		maxdistribution=0;
		for(i=0;i<temp;i++)
			{
			maxdistribution+=pref[i].score;
			if(k>=maxdistribution-pref[i].score&&k<maxdistribution)
				{
				k=i;
				RootPV=m[k].move;
				break;
				}
			}
		}
	}
return BOOK_SUCCESS;
}
