#include "book.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <list>
#include <algorithm>
//#include <windows.h>

bool substr( const char *src, const char *sub )
{
	while ( *src && *sub )
	{
		if ( *src != *sub )
			return false;
		src++;
		sub++;
	}
	return !*sub;
}

typedef struct
{
	i32 count;								// times played
	i32 score;								// score (cummulative)
	i32 scoreCount;							// score count (divisor)
	i32 win;								// win percentage
	std::vector< const char * > lines;		// text lines
} BookStat;

static void recordLine( std::map< u64, BookStat >::iterator &it, const char *tl )
{
	std::vector< const char * > &v = it->second.lines;
	i32 i;
	for (i=0; i<v.size(); i++)
	{
		if ( strcmp( v[i], tl ) == 0 )
			return;		// already recorded
	}
	i32 l = strlen( tl );
	char *c = new char[ l + 1 ];
	strcpy( c, tl );
	v.push_back( c );
}

void Book::clear()
{
	book.clear();
}

// purge this book
void Book::purge()
{
	std::map< u64, std::vector< BookMove > >::iterator it, itn;
	for (it = book.begin(); it != book.end(); )
	{
		itn = it;
		itn++;
		if ( it->second.empty() )
		{
			book.erase(it);
		}
		else
		{
			// sort!!!
			std::sort( it->second.begin(), it->second.end() );
		}
		it = itn;
	}
}

// compile book from pgn
void Book::compile( const char *pgn )
{
	return;
#if 0
	enum {
		maxLineSize = 50			// record up to 25 full moves (50 plies)
	};

	const char *buf, *buftop;
	HANDLE handle = CreateFileA( pgn, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 );
	if ( handle == INVALID_HANDLE_VALUE )
		return;
	long sz = SetFilePointer( handle, 0, 0, FILE_END );

	HANDLE hmap = CreateFileMapping( handle, 0, PAGE_READONLY, 0, 0, 0 );
	if ( !hmap )
	{
		CloseHandle( handle );
		return;
	}
	buf = (const char *)MapViewOfFile( hmap, FILE_MAP_READ, 0, 0, 0 );
	if ( !buf )
	{
		CloseHandle( hmap );
		CloseHandle( handle );
		return;
	}
	buftop = buf + sz;
/*	FILE *f = fopen( pgn, "rb");
	if ( !f )
		return;
	fseek(f, 0, SEEK_END);
	long sz = ftell( f );
	if ( sz <= 0 )
	{
		fclose(f);
		return;
	}
	fseek(f, 0, SEEK_SET);
	buf = new char[ sz + 1 ];
	sz = fread( buf, 1, sz, f );
	if ( sz <= 0 )
	{
		fclose(f);
		delete[] buf;
		return;
	}
	buf[sz] = 0;
*/
	// start parsing
	std::map< u64, BookStat > stat;
	std::map< u64, BookStat >::iterator ci;

	const char *c;
	i32 comm = 0;			// comments (end_char)
	
	std::vector< u64 > line;	// current line
	char textline[ 65536 ];		// current line as text
	textline[0] = 0;
	Board b;
	b.reset();
	i32 turn = white;
	i32 length = 0;			// length in halfmoves (plies)
	Move m[ maxMoves ];
	i32 vm = b.moves( m, turn );
	bool bad = false;		// bad line (ignore rest)
	i32 curgame = 0;
	FILE *f2 = fopen("compiled.book", "w");
	fprintf(f2, "; file: %s\n", pgn);
	for ( c = buf; c < buftop && *c; c++ )
	{
		if ( comm > 0 )
		{
			if ( *c == comm )
			{
				comm = 0;
			}
			continue;
		}
		if ( *c <= 32 || *c == '.' || *c == '?' || *c == '!' || *c == '+' || *c == '#' )
			continue;
		else
		if ( *c == '[' || *c == '{' )
		{
			comm = (*c == '[') ? ']' : '}';
			continue;
		}
		if ( !bad && *c == '*' || strncmp( c, "1/2-1/2", 7 ) == 0 )
		{
			// endgame, equal... (record score if *c != '*')
			if ( *c != '*' )
			{
				// record score...
				i32 i;
				for (i=0; i<line.size(); i++)
				{
					ci = stat.find( line[i] );
					if ( ci != stat.end() )
					{
						ci->second.score += 0;
						ci->second.scoreCount++;
						ci->second.win = ci->second.score * 1000 / ci->second.scoreCount;
					}					
				}
			}
			b.reset();
			turn = white;
			length = 0;
			line.clear();
			textline[0] = 0;
			vm = b.moves( m, turn );
			c += (*c == '*') ? 0 : 7-1;
			continue;
		}
		if ( !bad && strncmp( c, "1-0", 3 ) == 0 )
		{
			// endgame, win for white (record score)
			{
				// record score...
				i32 i;
				for (i=0; i<line.size(); i++)
				{
					ci = stat.find( line[i] );
					if ( ci != stat.end() )
					{
						ci->second.score += (i & 1) ? -1 : +1;
						ci->second.scoreCount++;
						ci->second.win = ci->second.score * 1000 / ci->second.scoreCount;
					}					
				}
			}
			b.reset();
			turn = white;
			length = 0;
			line.clear();
			textline[0] = 0;
			vm = b.moves( m, turn );
			c += 3-1;
			continue;
		}
		if ( !bad && strncmp( c, "0-1", 3 ) == 0 )
		{
			// endgame, win for black (record score)
			{
				i32 i;
				for (i=0; i<line.size(); i++)
				{
					ci = stat.find( line[i] );
					if ( ci != stat.end() )
					{
						ci->second.score += (i & 1) ? +1 : -1;
						ci->second.scoreCount++;
						ci->second.win = ci->second.score * 1000 / ci->second.scoreCount;
					}					
				}
		}
			b.reset();
			turn = white;
			length = 0;
			line.clear();
			textline[0] = 0;
			vm = b.moves( m, turn );
			c += 3-1;
			continue;
		}
		if ( *c == '1' && c[1] == '.' )
		{
			// new game starts here...
			if ( !(curgame++ % 2000) )
			{
				// PURGE_NOW!
				printf("\tpurging... %d -> ", (i32)stat.size());
				std::map< u64, BookStat >::iterator it, itn;
				bool full = !((curgame-1) % 5000);
				for (it = stat.begin(); it != stat.end();)
				{
					itn = it;
					itn++;
					if ( it->second.count < (full ? 3 : 2) )
					{
						i32 i;
						for (i=0; i < it->second.lines.size(); i++)
						{
							delete[] it->second.lines[i];
						}
						stat.erase( it );
					}
					it = itn;
				}
				printf("%d\n", (i32)stat.size());
			}
			if ( !((curgame) % 250 ) )
			{
				printf("\nnew game(%d; %0.3lf%%)\n", curgame, (c - buf)*100.0/sz );
			}
			b.reset();
			turn = white;
			length = 0;
			line.clear();
			textline[0] = 0;
			vm = b.moves( m, turn );
			bad = false;
			c++;
			if ( curgame > 513000 )
			{
				printf("too many games -> quitting\n");
				break;
			}
			continue;
		}
		if ( isdigit(*c) && (isdigit(c[1]) || c[1] == '.' || c[1] <= 32 || c[1] == '{') )
		{
			// skip
			c += 2-1;
			continue;
		}
		if ( bad )
			continue;
		// search for SAN moves...
		i32 i;
		i32 bestlen = 0;
		i32 besti = -1;
		for (i=0; i<vm; i++)
		{
			char buf[32];
			m[i].describeSAN( turn, b, buf, false );
			if ( substr( c, buf ) )
			{
				if ( strlen( buf ) > bestlen )
				{
					bestlen = strlen( buf );
					besti = i;
				}
			}
		}
		if ( besti >= 0 )
		{
			// accept move!
			if ( line.size() < maxLineSize )
			{
				line.push_back( b.curHash );
				{
					char buf[32];
					m[ besti ].describeSAN( turn, b, buf );
					strcat( textline, buf );
					strcat( textline, " ");
				}
			}
			b.move( m[ besti ] );

			ci = stat.find( b.curHash );
			length = line.size();
			if ( length <= maxLineSize )
			{
				if ( ci == stat.end() )
				{
					// record new position
					BookStat st;
					st.count = 1;
					st.score = 0;
					st.scoreCount = 0;
					st.win = 0;
					stat[ b.curHash ] = st;			
				}
				else
				{
					stat[ b.curHash ].count++;
				}

				ci = stat.find( b.curHash );
				if ( ci != stat.end() )
				{
					recordLine( ci, textline );
				}
			}

			c += bestlen - 1;
			turn ^= 1;
			vm = b.moves( m, turn );
		}
		else
		{
			char tbuf[32];
			memset( tbuf, 0, sizeof(tbuf) );
			strncpy( tbuf, c, 32 );
//			printf("bad move found! (%s...)\n", tbuf);
			printf("!");
			bad = true;
		}
	}

//	fclose(f);
//	delete[] buf;
	UnmapViewOfFile( (void *)buf );
	buf = 0;
	CloseHandle( hmap );
	CloseHandle( handle );

	printf("dumping results:\n");
	clear();
	{
		Board tb;
		Move m[ maxMoves ];
		i32 i, turn = white;

		std::list< std::pair< i32, Board > > q;				// queue

		tb.reset();
		q.push_back( std::make_pair( turn, tb ) );

		while ( !q.empty() )
		{
			std::pair< i32, Board > bp = q.front();
			q.pop_front();

			turn = bp.first;
			tb = bp.second;

			std::map< u64, std::vector< BookMove > >::const_iterator bci;
			bci = book.find( tb.curHash );
			if ( bci == book.end() )
			{
				book[ tb.curHash ].clear();			// initialize!!!
			}
			std::vector< BookMove > *bm = &book[ tb.curHash ];
			i32 vm = tb.moves( m, turn );
			printf("hash:%I64x turn:%d ... ", tb.curHash, turn );
			for (i=0; i<vm; i++)
			{
				Board tmp( tb );
				tmp.move( m[i] );
				u64 h = tmp.curHash;
				ci = stat.find( h );
				if ( ci == stat.end() )
				{
					continue;					// not in book!
				}
				if ( ci->second.count <= 2 )
					continue;					// low quality -> skip!
				i32 w = ci->second.win;
				i32 c = ci->second.count;
				if ( c > 65535 )
					c = 65535;
				if ( turn != black )
				{
					w = -w;
				}
/*				if ( w < 0 )
					continue;					// low quality -> skip!*/
				i32 j;
				for (j=0; j<(i32)bm->size(); j++)
				{
					if ( m[i].equal( (*bm)[j].m ) )
						break;
				}
				if ( j < (i32)bm->size() )
					continue;					// already added!

				char buf[32];
				m[i].describeSAN( turn, tb, buf );
				printf("%s ", buf);

				q.push_back( std::make_pair( turn ^ 1, tmp ) );

				BookMove bkm;
				bkm.m = m[i];
				bkm.w = w;						//ci->second.win;
				bkm.c = c;
				bm->push_back( bkm );
			}
			printf("\n");
		}
	}
	purge();
	exportBin("cheng.binbook");
	u64 count = 0;
	for ( ci = stat.begin(); ci != stat.end(); ci++ )
	{
		if ( ci->second.count > 2 )
		{
////			printf("pos: %I64x played: %d times\n", ci->first, ci->second.count );
			if ( !ci->second.lines.empty() )
			{
				if ( ci->second.win >= 0 )
				{
					count++;
					i32 i;
					for (i=0; i<ci->second.lines.size(); i++ )
					{
						////					printf( "\t%s\n", ci->second.lines[i] );
						fprintf( f2, "%s ; win:%0.1lf%%\n", ci->second.lines[i], (double)ci->second.win / 10.0 );
					}
				}
			}
		}
	}
	printf("\t%I64d positions\n", count);
	fclose(f2);
#endif
}

void Book::parse( const char *fnm )
{
	FILE *f = fopen(fnm, "r");
	if ( !f )
		return;
	char buf[4096];
	buf[ 4094 ] = buf[ 4095 ] = 0;
	i32 line = 0;
	while ( fgets(buf, 4095, f) != 0 )
	{
		line++;
		Board b;
		b.reset();
		Move m[ maxBook ];
		i32 nm = 0;
		while ( buf[0] && buf[ strlen(buf) - 1 ] < 32 )
			buf[ strlen(buf) - 1 ] = 0;
		if ( buf[0] == ';' || buf[0] == '[' || buf[0] <= 32 )
			continue;
		Move tm;
		const char *c = buf;
		i32 turn = white;
		printf(".");
		char buf[32];

		while ( *c && *c != ';' )
		{
////			tm.parse( c );
			Move v[ maxMoves ];
			i32 vm = b.moves( v, turn );
			i32 i;
			i32 besti = -1;
			i32 bestlen = 0;
			for (i=0; i<vm; i++)
			{
				char buf[32];
				v[i].describeSAN( turn, b, buf );
				if ( substr( c, buf ) )
				{
					if ( strlen( buf ) > bestlen )
					{
						bestlen = strlen( buf );
						besti = i;
					}
				}
/*				if ( v[i].fx == tm.fx && v[i].fy == tm.fy && v[i].tx == tm.tx && v[i].ty == tm.ty )
					break;*/
			}
			i = besti;
			if ( i < 0 )
				i = vm;
			else
				c += bestlen;
			if ( i >= vm )
			{
				i32 ch = *c | 32;
				if ( 0&&ch >= 'a' && ch <= 'z' )
				{
					printf("\nbad book line %d at: %s\n", line, c);
					printf("valid SAN moves:\n\t");
					for (i=0; i<vm; i++)
					{
						char buf[32];
						v[i].describeSAN( turn, b, buf );
						printf("%d %s ", i, buf);
					}
					u64 m = b.mask;
					b.update( turn );
/*					{
						char buf[32];
						v[25].describeSAN( turn, b, buf );
					}
					printf("\n");*/
					abort();
				}
				c++;
				continue;
/*				printf("bad book line: %s\n", buf);
				break;*/
			}

			v[i].describeSAN( turn, b, buf );
//			printf("%s ", buf);

			u64 h = b.hash( turn );
			BookMove bm;
			bm.m = v[i];
			book[h].push_back( bm );
			b.move( v[i] );
			turn ^= 1;
		}
//		printf("\n");
	}
	fclose(f);
	printf("\n");
}

// try book move. returns -1 if not found, otherwise move index!
i32 Book::check( i32 turn, const Board &b, u64 h, i32 nm, Move *m ) const
{
	std::map< u64, std::vector<BookMove> >::const_iterator ci = book.find( h );
	if ( ci == book.end() )
		return -1;				// not found
	Board nb;
	nb.reset();
	bool initial = (turn == white && nb.curHash == h);
	if ( initial )
	{
//		printf("initial book move!\n");
	}
	{
		const std::vector<BookMove> *bm = &ci->second;
		i32 i;
		for (i=0; i<bm->size(); i++)
		{
			char buf[32];
			const Move &m = (*bm)[i].m;
			i32 w = (*bm)[i].w;
			i32 c = (*bm)[i].c;
			m.describeSAN( turn, b, buf );
/*			printf("book move found: %s win: %0.1lf%% played: %d times => %0.1lf combined\n", buf, (double)w / 10.0, c,
			 (double)w / 1000.0 * c	);*/
		}
	}

	i32 j;
	for (j=0; j<32; j++)
	{
		i32 ind = (rand() & 0x7fff) % ci->second.size();
		i32 i;
		const BookMove &tm = ci->second[ ind ];
		if ( tm.w < 0 )
			continue;
//		double comb = (double)tm.w / 1000.0 * tm.c;
/*		if ( tm.w < 0 || (initial && tm.c < 500) )
			continue;*/
/*		if ( comb < 20 || (initial && comb < 500) )
			continue;*/
		for (i=0; i<nm; i++)
		{
			if ( m[i].from == tm.m.from && m[i].to == tm.m.to )
				return i;
		}
	}
	return -1;
}

// parse binary book
void Book::parseBin( const char *fnm )
{
	FILE *f = fopen( fnm, "rb" );
	if ( !f )
		return;

	book.clear();

	i32 sz;
	if ( fread( &sz, 4, 1, f) != 1 )
		sz = 0;
		
	std::map< u64, std::vector<Move> >::iterator it;
	i32 j;
	for (j=0; j<sz; j++)
	{
		u64 h;
		if ( fread( &h, 8, 1, f ) != 1 )
			break;		// done
		i32 num;
		if ( fread( &num, 4, 1, f ) != 1 )
			break;		// done
		i32 i;
		for (i=0; i<num; i++)
		{
			BookMove m;
			if ( fread( &m, sizeof(BookMove), 1, f ) != 1 )
			{
				fclose(f);
				return;	// done
			}
			book[ (posHash)h ].push_back( m );
		}
	}
	fclose(f);
}

// export binary book
void Book::exportBin( const char *fnm )
{
	if ( sizeof( posHash ) != 8 )
		return;
	FILE *f = fopen( fnm, "wb" );
	if ( !f )
		return;

	i32 sz = book.size();
	fwrite( &sz, 4, 1, f );
	std::map< u64, std::vector<BookMove> >::const_iterator ci;
	for (ci = book.begin(); ci != book.end(); ci++)
	{
		const std::vector<BookMove> &m = ci->second;
		if ( m.empty() )
			continue;
		u64 h = ci->first;
		fwrite( &h, 8, 1, f );
		i32 num = m.size();
		fwrite( &num, 4, 1, f );
		i32 i;
		for (i=0; i<m.size(); i++)
		{
			fwrite( &m[i], sizeof(BookMove), 1, f );
		}
	}
	fclose(f);
}
