
#define MAX_UCI_OPTIONS		 128
#define STARTPOS	         "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"

enum option_type_t {TYPE_BUTTON, TYPE_CHECK, TYPE_COMBO, TYPE_SPIN, TYPE_STRING};
char TypeToStr[][8] = {"button","check", "combo", "spin","string"};

struct option_t {
	char name[128];
	int type;
	char value[128];
	char parameter[128];
	bool visible;
};

class UCI {
private:
	int options_nb;
public:	
	void ParseCommand(Board *b, Searcher *s);
	void ParseGo(char *command, Board *b, Searcher *s);
	void ParseSearchmoves(char *str, MoveList *sm, Board *b);
	void ParsePosition(char *str, Board *b);
	void ResetSearchOptions(Searcher *s);
	char* BuildMinMax(int min, int max);
	 
	// Options

	void ParseSetoption(char *str);
	void SetOption(char *name, char *value);
			
	option_t option[MAX_UCI_OPTIONS];
	void OptionsInit();

	option_t* FindOption(char *name) {
		for(int i = 0; i < options_nb; i++)
			if(!strcmp(name, (option+i)->name))
				return option+i;
		ASSERT(false);
		return NULL;
	} 
	void ResetButton(char *name) {
		option_t *opt = FindOption(name);
		if(opt) strcpy(opt->value, "false");		
	}
	void NewOption(char *name, int type, char* def_value, char *parameter, bool view) {
		option_t *opt = option + options_nb++;
		opt->type = type;
		opt->visible = view;
		strcpy(opt->name, name);
		strcpy(opt->value, def_value);
		strcpy(opt->parameter, parameter);
		ASSERT(options_nb < MAX_UCI_OPTIONS);
		// now print this option
		if(view) {
			if(type == TYPE_BUTTON)    printf("option name %s type %s %s\n", name, TypeToStr[type], parameter);
			else printf("option name %s type %s default %s %s\n", name, TypeToStr[type], def_value, parameter);
		}
	}
	char* GetOptionString(char *name) {
		option_t *opt = FindOption(name);
		return opt->value;		
	}
	bool GetOptionBool(char *name) {
		option_t *opt = FindOption(name);
		return *opt->value == 't';
	}
	int GetOptionInt(char *name) {
		option_t *opt = FindOption(name);
		return atoi(opt->value);
	}
};

char* UCI::BuildMinMax(int min, int max) {
	char *result = new char[256];
	strcpy(result, "min ");
	itoa(min, result+4, 10);
	strcat(result, " max ");
	int len = strlen(result);
	itoa(max, result + len, 10);
	return result;
}

// OptionsInit()

void UCI::OptionsInit() {
	// fill 'options' array
	options_nb = 0;

	char trans_default[16],
		default_multipv_str[64];		
	itoa(TRANS_DEFAULT_SIZE, trans_default, 10);
	itoa(DEFAULT_MULTIPV, default_multipv_str, 10);
		
	//         name                  type         value						parameter										view   
	NewOption("Hash",                TYPE_SPIN,   trans_default,			BuildMinMax(TRANS_MIN_SIZE, TRANS_MAX_SIZE),	true);
	NewOption("Clear Hash",          TYPE_BUTTON, "false",					"",												true);	
	NewOption("MultiPV",             TYPE_SPIN,   default_multipv_str,		BuildMinMax(1, MAX_MULTIPV),					true);
	NewOption("Ponder",              TYPE_CHECK,  "false",					"",												true);
	NewOption("Rate of play",        TYPE_COMBO,  "normal",  				"var slow var normal var fast",					true);	
	NewOption("UCI_Chess960",        TYPE_CHECK,  "false",					"",												false);	
	NewOption("UCI_ShowCurrLine",    TYPE_CHECK,  "false",					"",												true);
	NewOption("UCI_EngineAbout",     TYPE_STRING, "Bison by Ivan Bonkin",	"",												true);	
}

// UCI commands

// setoption name
// ucinewgame
// ponderhit
// position
// isready
// stop
// quit
// uci
// go    [  wtime    winc   depth     mate     movetime   infinite   ]
//       [  btime    binc	nodes	movestogo   ponder   searchmoves ]

#define STRING_START_WITH(command)    (strstr(string, command) == string)

//

void UCI::ParseCommand(Board *b, Searcher *s) {

	// init
	const int string_size = 65536;
	char string[string_size];	

	for(EVER) {
		if(fgets(string, string_size, stdin) == NULL) {
			return;		
		} else if(STRING_START_WITH("go")) {
			ParseGo(string + 3, b, s);
		} else if(STRING_START_WITH("position")) {
			ParsePosition(string + 9, b);
		} else if(STRING_START_WITH("stop")) {
			if(DEBUG)	return;
		} else if(STRING_START_WITH("ucinewgame")) {
			s->trans->NewGame();
		} else if(STRING_START_WITH("uci")) {
			printf("id name Bison %s\n", Version());
			printf("id author Ivan Bonkin\n");
			OptionsInit();
			ResetSearchOptions(s);
			printf("uciok\n");
		} else if(STRING_START_WITH("setoption name")) {
			ParseSetoption(string + 14 + 1);
			ResetSearchOptions(s);
		} else if(STRING_START_WITH("isready")) {
			printf("readyok\n");
		} else if(STRING_START_WITH("ponderhit")) {
		} else if(STRING_START_WITH("quit")) {
			exit(EXIT_SUCCESS);
		}
	}
}

//

void UCI::ParseGo(char *command, Board *b, Searcher *s) {

	// init
	char *strings[] = {"wtime", "btime", "winc", "binc", "depth",
		               "nodes", "mate", "movestogo", "movetime"};
	int str_len[]   = {5+1, 5+1, 4+1, 4+1, 5+1, 5+1, 4+1, 9+1, 8+1}; // '+1' for space
	enum {              wtime,   btime,   winc,   binc,   depth, 
		                nodes,   mate,   movestogo,   movetime,
						ponder, infinite, searchmoves
	};
	MoveList sm[1]; // searchmoves
	int options[16];
	int opt_value;
	char *ptr;
	int i;

	// loop
	for(i = 0; i < 9; i++) {
		opt_value = 0;
		ptr = strstr(command, strings[i]);
		if(ptr != NULL) opt_value = atoi(ptr + str_len[i]);
		options[i] = opt_value;		
	}
	// ponder, infinite, searchmoves
	options[ponder] = strstr(command, "ponder") != NULL;
	options[infinite] = strstr(command, "infinite") != NULL;

	sm->Clear();
	ptr = strstr(command, "searchmoves");
	if(ptr)	ParseSearchmoves(ptr + 12, sm, b);
	
	s->RootSearch(b, options[wtime], options[btime], options[winc], options[binc], options[depth], options[nodes],
				   options[mate], options[movestogo], options[movetime], options[ponder], options[infinite], sm);	
	s->PrintBestMove();	
}

// ParseSearchmoves()

void UCI::ParseSearchmoves(char *str, MoveList *sm, Board *b) {

	Move m[1];
	char *moves, move_str[8], *symbol;

	moves = str;
	while (*moves == ' ')	   moves++;
	while(*moves != '\0' && *moves != '\n') {
		symbol = move_str;
		while(*moves != ' ' && *moves != '\0' && *moves != '\n') 			
			*symbol++ = *moves++;
		*symbol = '\0';	
		m->Parse(move_str, b->type);
		sm->Add(m);
		while (*moves == ' ')  moves++;
	}
}

//

void UCI::ParsePosition(char *str, Board *b) {

	char *startpos, *fen, *moves, move_str[8], *symbol;	
	Move m[1];
	undo_t u[1];

	// todo:
	// heauristic (*) compare strings here		
	// create  char array of moves we have
	// made in current position from start
	// and compare it with array sended by 
	// shell. If we have sucsessed no need
	// to do all this moves
	// position startpos moves f2f4 d7d5 e2e3 e7e6 g1f3 g8f6 f1e2 f8d6 b1c3 e8g8 e1g1 c7c5 c3b5 d6e7 f3e5 c8d7 e5d7 d8d7 c2c4 b8c6 d2d3 d5d4 e3e4	
	// position fen rn1q1bnr/1bP1kp1P/1p2p3/p7/8/8/PP1pKPpP/RNBQ1BNR w - - 0 1 moves c7d8b e7e8 h7g8q d2c1n d1c1 b7a6 e2e3 g2f1r g8h8 f1c1	

	moves = strstr(str, "moves ");
    // STARTPOS or FEN
	if(startpos = strstr(str, "startpos")) {
        b->FromFEN(STARTPOS);  		
	} else {		
		if(moves)   *(moves-1) = '\0';
		fen = strstr(str, "fen ") + 4;
		b->FromFEN(fen); 		
	}
	
	if(moves) {
		moves += 6;		
		while(*moves != '\0' && *moves != '\n') {
			symbol = move_str;
			while(*moves != ' ' && *moves != '\0' && *moves != '\n') 			
				*symbol++ = *moves++;			
			*symbol = '\0';	

			m->Parse(move_str, b->type); 			
			b->MakeMove(m, u);
			
			while (*moves == ' ')	   moves++;
		} 		
	}
}

// ParseSetoption()

void UCI::ParseSetoption(char *str) {
	char *value;
	// isn`t it a button?
	value = strstr(str, "value ");

	if(value == NULL) {
		value = "true";
	} else  {
		// split string
		str[ value-str-1 ] = '\0'; 
		value += 6;
	}
	SetOption(str, value);	
}

// SetOption()

void UCI::SetOption(char *name, char *value) {
	char *newline;
	newline = strchr(value, '\n');
	if(newline) *newline = '\0';
	newline = strchr(name, '\n');
	if(newline) *newline = '\0';
	
	option_t * opt = FindOption(name);
	if(opt && opt->visible)	 strcpy(opt->value, value);		
}

//

void UCI::ResetSearchOptions(Searcher *s) {
	
	// options

	// boolean
	s->options->chess_960		= GetOptionBool("UCI_Chess960");
	s->options->clear_hash		= GetOptionBool("Clear Hash");	
	s->options->ponder			= GetOptionBool("Ponder");	
	s->options->show_curr_line  = GetOptionBool("UCI_ShowCurrLine");

	// integer
	s->options->multipv			= GetOptionInt("MultiPV");

	// trans
	if(s->options->hash != GetOptionInt("Hash")) {
		s->options->hash = GetOptionInt("Hash");
		s->trans->Alloc(s->options->hash);
	} if(s->options->clear_hash) {
		s->trans->Clear();
		printf("info string Hash has been cleared\n");
	}
	
	//  string
	char *str					= GetOptionString("Rate of play");
	if(strstr(str, "slow"))		s->options->rate_of_play = SLOW_RATE_OF_PLAY;
	if(strstr(str, "normal"))	s->options->rate_of_play = NORMAL_RATE_OF_PLAY;
	if(strstr(str, "fast"))		s->options->rate_of_play = FAST_RATE_OF_PLAY;
		
	// update root data
	s->data->multipv = s->options->multipv;
	s->data->rate_of_play = s->options->rate_of_play; 
}