/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * This file is part of SanKit -- A multiplatform set of C code
 * for reading and writing PGN formatted chess games. In addition
 * it can be used as an engine to determine in real time if proposed
 * moves are legal or not. It appears to generate near perfect PGN
 * from incorrectly formatted files.
 *
 * Copyright 2012 David Hoerl All Rights Reserved.
 *
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY David Hoerl ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL David Hoerl OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <iostream>

#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cctype>
#include <cassert>
#include <unistd.h>	// sleep()

#include "PGN_Parser.h"

using namespace std;

namespace 
{
	static const size_t one_file = 8*2 + 1;
	static const size_t half_move = one_file * 8;	// (8*2 + 1) * 8 = 136
	static const size_t full_move = half_move*2;	// = 272
	static const char *fresh_board =
		"bRbNbBbQbKbBbNbR\nbPbPbPbPbPbPbPbP\n  ::  ::  ::  ::\n::  ::  ::  ::  \n::  ::  ::  ::  \n  ::  ::  ::  ::\nwPwPwPwPwPwPwPwP\nwRwNwBwQwKwBwNwR\n";		

	static const int num_pieces = 14;
	static const char *pieces[num_pieces] = { "  ", "wP", "wR", "wN", "wB", "wQ", "wK", "bP", "bR", "bN", "bB", "bQ", "bK", "::" };

	std::string string_format(const char *fmt, ...);
	void append_format(std::string& str, const char *fmt, ...);
}

namespace SANKit
{
	Game::Game(FILE *pgnFP, FILE *boardFP) :
		header(),
		plays(),
		boards(),
		success(false),
		forfeit(false)
	{
		success = ParseGame(pgnFP, boardFP);
	}
	Game::~Game()
	{
		for(Plays_Itor_t it = plays.begin(); it != plays.end(); ++it) {
			delete *it;
		}
		for(Boards_Itor_t it = boards.begin(); it != boards.end(); ++it) {
			delete *it;
		}
	}
	
	string Game::GetNewBoard() const
	{
		string str;
		AppendNewBoard(str);;
		return str;
	}

	string Game::GetBoard(size_t full_move, bool white) const
	{
		string str;
		AppendBoard(str, full_move, white);
		return str;
	}

	void Game::AppendNewBoard(std::string& str) const
	{
		DumpGame(str, fresh_board);
	}
	
	void Game::AppendBoard(std::string& str, size_t full_move, bool white) const
	{
		const char *ptr = boards[full_move]->c_str();
		if(!white) ptr += half_move;
	
		DumpGame(str, ptr);
	}

	piece Game::GetPiece(size_t full_move, bool white, const char *locator) const
	{
		assert((full_move*2 + (white ? 0 : 1)) < GetHalfMoveCount() );

		assert(strlen(locator) == 2);
		size_t file = locator[0] - 'a';
		assert(file >= 0 && file < 8);
		size_t rank = 7 - (locator[1] - '1'); // our string starts at the board top, not the bottom
		assert(rank >= 0 && rank < 8);
		size_t location = file*2 + rank*one_file;
//printf("locator=%s rank=%zd file=%zd location=%zd\n", locator, rank, file, location);

//printf("FullMove %zd\n", full_move);
		const char *this_board = boards[full_move]->c_str();
		if(!white) this_board += half_move;

		const char *this_piece = this_board + location;
		for(unsigned int idx=empty; idx<=null; ++idx) {
			if(!strncmp(this_piece, pieces[idx], 2)) {
				return (piece)idx == null ? empty : (piece)idx;
			}
		}
		fprintf(stderr, "Cannot find proper piece - something is very wrong.\n");
		return empty;
	}

	const char *Game::StringForPiece(piece p) const
	{
		return p < num_pieces ? pieces[p] : "??";
	}

	void Game::DumpGame(string& str, const char *ptr) const
	{
		const char *p = ptr;
		for(int j=0; j<8; ++j) {
			//description.append("\n");
			for(int i=0; i<8; ++i) {
				if(i) str.append(" ");
				str.append(p, 2);
				p += 2;
			}
			str.append("\n");
			++p; // eat '\n'
		}
	}

	bool Game::ParseGame(FILE *fp, FILE *moves)
	{
		while(true) {
			char c = fgetc(fp);
			if(c == '[') {
				ungetc(c, fp);
				break;
			}
			if(c == EOF) return false;
		}

		bool do_header = true;
		string pgn;
		char * const str = (char *)malloc(10000);	// some really big number
		char full_move_str[full_move];

		while(1) {
			char *ret = fgets(str, 1024, fp);	// another absurd number
			str[1024] = '\0';
			if(!ret) break;

			size_t size = strlen(ret);

			while(size) {
				char c = ret[size-1];
				if(c == ' ' || c == '\r' || c == '\n') {
					--size;
					continue;
				} else {
					break;
				}
			}
			str[size] = '\0';
		
			if(do_header) {
				if(plays.size() == 0 && size >= 2 && (ret[0] == '0' || ret[0] == '1')) {
					if(size == 3 && (strcmp(ret, "0-1") || strcmp(ret, "1-0"))) {
						plays.push_back(new string(ret));
						string *two_boards = new string(full_move, ' ');
						boards.push_back(two_boards);
						free(str);
						forfeit = true;
						return true;
					}
					if(ret[1] == '.') {
						do_header = false;
					}
				} else
				if(size == 0) {
					continue;
				} else {
					header.append(ret, size);
					header.append("\n");
				}
			}
			if(!do_header) {
				if(size) {
					char x = 0;
					for(size_t i=0; i<size; ++i) {
						if(ret[i] == ';') {
							x = 0x80;
							continue;
						}
						if(x) ret[i] |= x;
					}
					pgn.append(ret, size);
					pgn.append(" ");
				} else {
					//printf("DONE0: %s\n", pgn.c_str() );
					// Done: remove double spaces then make each half-move its own string
					char x = 0;
					size_t offset = 0;
					char c = pgn[offset];
					++offset;
					while(offset < pgn.size()) {
						char nc = pgn[offset];
						if(c == ' ' && nc == ' ') {
							pgn.erase(offset, 1);
							continue;
						} else {
							c = nc;
						}
						if(nc == '{') {
							x = 0x80;
						} else
						if(nc == '}') {
							x = 0;
						}
						if(x) {
							pgn.replace(offset, (size_t)1, (size_t)1, (char)(nc | 0x80));
						}
						++offset;
					}
					if(pgn.size()) {
						pgn.erase(pgn.size() - 1, 1);	// remove the trailing space
					}
					
					strcpy(str, pgn.c_str());
					
					size_t len = pgn.size() - 4;
					char *ptr = str;
					// skip "1. "
					for(offset=3; offset < len; ++offset) {
						char c = str[offset];
						if(c & 0x80) {
							c &= 0x7F;
							str[offset] = c;
							continue;
						}
							
						if(isdigit(c) && 
							(
								str[offset+1] == '.'													||
								(isdigit(str[offset+1]) && str[offset+2] == '.')						||
								(isdigit(str[offset+1]) && isdigit(str[offset+2]) && str[offset+3] == '.')
							) 
						) {
							// move
							string *move = new string(ptr, &str[offset] - ptr - 1); // minus one to eat the space
							plays.push_back(move);
						
							size_t ret = fread(full_move_str, full_move, 1, moves);
							assert(ret == 1);
							string *two_boards = new string(full_move_str, full_move);
							boards.push_back(two_boards);

							ptr = &str[offset];
							while(str[offset] != '.') ++offset;
						}
					}

					while(offset < pgn.size()) {
						char c = str[offset];
						if(c & 0x80) {
							c &= 0x7F;
							str[offset] = c;
						}
						++offset;
					}
					plays.push_back(new string(ptr));
					
					size_t ret = fread(full_move_str, full_move, 1, moves);
					assert(ret == 1);
					string *two_boards = new string(full_move_str, full_move);
					boards.push_back(two_boards);
					
					bool is_half = false;
					for(int i=half_move; i<full_move; ++i) {
						if(full_move_str[i] != ' ') {
							is_half = true;
							break;
						}
					}
					half_moves = plays.size() * 2;
					if(is_half) half_moves--;

					free(str);
					return true;
				}
			}
		}
		free(str);
		return false;
	}
}

namespace
{
	// Two really useful routines
	std::string string_format(const char *fmt, ...)
	{
		char *ret;
		va_list ap;

		va_start(ap, fmt);
		vasprintf(&ret, fmt, ap);
		std::string str(ret);
		free(ret);
		va_end(ap);

		return str;
	}
	void append_format(std::string& str, const char *fmt, ...)
	{
		va_list ap;
		char *ret;

		va_start(ap, fmt);
		vasprintf(&ret, fmt, ap);
		va_end(ap);

		str.append(ret);
		free(ret);
	}

}
