/* Copyright 2018 Romain "Artefact2" Dal Maso <artefact2@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>
#include <assert.h>
#include "gumble.h"

/* XXX: this is extremely inefficient. keep an index of pieces in cch_board_t ? */
static cch_square_t cch_find_piece(const cch_board_t* b, cch_pure_piece_t type,
								   const cch_move_t* m, cch_square_t offset,
								   cch_movelist_t ml, unsigned char stop) {
	cch_square_t sq_start = offset, sq_stop = 64, sq_increment = 1;
	cch_move_t cm = (cch_move_t){ .end = m->end, .promote = m->promote };

	if((m->start & 192) == 0) {
		if(m->start < sq_start || m->start >= sq_stop) return 255;
		sq_start = m->start;
		sq_stop = sq_start + 1;
	} else if(~m->start & 128) {
		cch_square_t file = m->start & 56;
		if(sq_start < file) {
			sq_start = file;
		}
		sq_stop = file + 8;
	} else if(~m->start & 64) {
		cch_square_t rank = m->start & 7;
		sq_increment = 8;
		if(CCH_RANK(sq_start) > rank) {
			sq_start = CCH_SQUARE(CCH_FILE(sq_start) + 1, rank);
		} else {
			sq_start = CCH_SQUARE(CCH_FILE(sq_start), rank);
		}
	}

	cch_piece_t wanted = CCH_MAKE_OWN_PIECE(b, type);
	for(unsigned int i = sq_start; i < sq_stop; i += sq_increment) {
		if(CCH_GET_SQUARE(b, i) != wanted) continue;
		cm.start = i;
		if(cch_is_move_in_list(&cm, ml, stop)) {
			return i;
		}
	}

	return 255;
}

cch_return_t cch_parse_san_move(cch_board_t* b, const char* str, cch_move_t* out) {
	/* https://en.wikipedia.org/wiki/Algebraic_notation_(chess) */

	cch_pure_piece_t type = 0;
	bool should_capture = false;
	cch_move_t m;

	/* 1st bit = do we have a file? 2nd bit = do we have a rank? bits 3-4-5 = file, bits 6-7-8 = rank */
	m.start = 192;
	m.end = 192;
	m.promote = 0;

	while(*str != '\0' && *str != ' ' && *str != '\n') {
		switch(*str) {
		case 'a':
		case 'b':
		case 'c':
		case 'd':
		case 'e':
		case 'f':
		case 'g':
		case 'h':
			if(!type) {
				/* No piece before file, this is a pawn move */
				/* XXX: possible impromevent, only search 3 ranks instead of 8 */
				type = CCH_PAWN;
			}
			if(m.end & 128) {
				/* First file of the notation, usually destination, ie Nc6 */
				m.end = (m.end & ~128) | (*str - 'a') << 3;
			} else if(m.start & 128) {
				/* Second file of the notation, definitely destination and previous one was source, ie cd5 */
				m.start = (m.start & ~128) | (m.end & 56);
				m.end = (m.end & ~56) | (*str - 'a') << 3;
			} else {
				/* Thid file ? */
				return CCH_PARSE_ERROR;
			}
			break;

		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
			/* Similar to files */
			if(m.end & 64) {
				m.end = (m.end & ~64) | (*str - '1');
			} else if(m.start & 64) {
				m.start = (m.start & ~64) | (m.end & 7);
				m.end = (m.end & ~7) | (*str - '1');
			} else {
				return CCH_PARSE_ERROR;
			}
			break;

		case 'N': type = CCH_KNIGHT; break;
		case 'B': type = CCH_BISHOP; break;
		case 'R': type = CCH_ROOK; break;
		case 'Q': type = CCH_QUEEN; break;
		case 'K': type = CCH_KING; break;
		case 'x': should_capture = true; break;

		case '0':
		case 'O':
			if(!strncmp("O-O-O", str, 5) || !strncmp("0-0-0", str, 5)) {
				/* Castle long */
				if(CCH_PURE_PIECE(CCH_GET_SQUARE(b, b->side ? 32 : 39)) != CCH_KING) return CCH_ILLEGAL_MOVE;
				m.start = b->side ? 32 : 39;
				m.end = m.start - 16;
			} else if(!strncmp("O-O", str, 3) || strncmp("0-0", str, 3)) {
				/* Castle short */
				if(CCH_PURE_PIECE(CCH_GET_SQUARE(b, b->side ? 32 : 39)) != CCH_KING) return CCH_ILLEGAL_MOVE;
				m.start = b->side ? 32 : 39;
				m.end = m.start + 16;
			} else {
				return CCH_PARSE_ERROR;
			}
			if(cch_is_move_legal(b, &m)) {
				out->start = m.start;
				out->end = m.end;
				out->promote = m.promote;
				return CCH_OK;
			} else return CCH_ILLEGAL_MOVE;

		case '=':
			if(type != CCH_PAWN) return CCH_PARSE_ERROR;
			++str;
			switch(*str) {
			case 'N': m.promote = CCH_KNIGHT; break;
			case 'B': m.promote = CCH_BISHOP; break;
			case 'R': m.promote = CCH_ROOK; break;
			case 'Q': m.promote = CCH_QUEEN; break;
			default: return CCH_PARSE_ERROR;
			}
			break;

		default:
			return CCH_PARSE_ERROR;

		}
		++str;
	}

	if(type == 0 || m.end & 192) {
		/* Way too ambiguous */
		return CCH_PARSE_ERROR;
	}

	if(should_capture && !CCH_GET_SQUARE(b, m.end) && m.end != b->en_passant) {
		/* Nothing to capture? */
		return CCH_PARSE_ERROR;
	} /* NB: captures are possible without "x", but "x" makes a capturing move mandatory. */

	cch_movelist_t ml;
	unsigned char stop = cch_generate_moves(b, ml, CCH_LEGAL, 0, 64);
	cch_square_t pos = cch_find_piece(b, type, &m, 0, ml, stop);
	if(pos < 64) {
		if(cch_find_piece(b, type, &m, pos + 1, ml, stop) < 64) {
			/* Another match? This move is too ambiguous */
			return CCH_PARSE_ERROR;
		}

		out->start = pos;
		out->end = m.end;
		out->promote = m.promote;
		return CCH_OK;
	}

	return CCH_ILLEGAL_MOVE;
}

cch_return_t cch_parse_lan_move(const char* s, cch_move_t* m) {
	if(s[0] < 'a' || s[0] > 'h') return CCH_PARSE_ERROR;
	if(s[1] < '1' || s[1] > '8') return CCH_PARSE_ERROR;
	if(s[2] < 'a' || s[2] > 'h') return CCH_PARSE_ERROR;
	if(s[3] < '1' || s[3] > '8') return CCH_PARSE_ERROR;

	m->start = CCH_SQUARE(s[0] - 'a', s[1] - '1');
	m->end = CCH_SQUARE(s[2] - 'a', s[3] - '1');

	switch(s[4]) {
	case 'q': m->promote = CCH_QUEEN; break;
	case 'r': m->promote = CCH_ROOK; break;
	case 'b': m->promote = CCH_BISHOP; break;
	case 'n': m->promote = CCH_KNIGHT; break;
	case '\0': m->promote = 0; break;
	default: return CCH_PARSE_ERROR;
	}

	return CCH_OK;
}

cch_return_t cch_format_lan_move(const cch_move_t* m, char* s, unsigned char len) {
	if(len < 5 || (m->promote && len < 6)) return CCH_OVERFLOW;
	assert(CCH_IS_SQUARE_VALID(m->start));
	assert(CCH_IS_SQUARE_VALID(m->end));
	s[0] = CCH_FILE(m->start) + 'a';
	s[1] = CCH_RANK(m->start) + '1';
	s[2] = CCH_FILE(m->end) + 'a';
	s[3] = CCH_RANK(m->end) + '1';

	switch(m->promote) {
	case CCH_QUEEN: s[4] = 'q'; break;
	case CCH_ROOK: s[4] = 'r'; break;
	case CCH_BISHOP: s[4] = 'b'; break;
	case CCH_KNIGHT: s[4] = 'n'; break;

	case 0:
		s[4] = '\0';
		return CCH_OK;

	default: assert(0);
	}

	s[5] = '\0';
	return CCH_OK;
}

cch_return_t cch_format_san_move(cch_board_t* b, const cch_move_t* m, char* s, unsigned char len, bool with_check) {
	if(len < 6) return CCH_OVERFLOW;
	assert(CCH_IS_SQUARE_VALID(m->start));
	assert(CCH_IS_SQUARE_VALID(m->end));

	unsigned char i = 0;

	switch(CCH_PURE_PIECE(CCH_GET_SQUARE(b, m->start))) {
	case CCH_KING:
		switch(m->end - m->start) {
		case 16:
			s[i++] = 'O';
			s[i++] = '-';
			s[i++] = 'O';
			goto check_check;
		case -16:
			s[i++] = 'O';
			s[i++] = '-';
			s[i++] = 'O';
			s[i++] = '-';
			s[i++] = 'O';
			goto check_check;
		default:
			s[i++] = 'K';
			break;
		}
		break;
	case CCH_QUEEN: s[i++] = 'Q'; break;
	case CCH_ROOK: s[i++] = 'R'; break;
	case CCH_BISHOP: s[i++] = 'B'; break;
	case CCH_KNIGHT: s[i++] = 'N'; break;
	case CCH_PAWN: break; /* Pawn captures are dealt with later */
	case CCH_EMPTY: assert(0);
	default: assert(0);
	}

	if(i == 1 && s[0] != 'K') {
		/* Move might be ambiguous, see if other legal moves match */
		/* XXX: highly inefficient */
		bool include_rank = false, include_file = false;
		cch_movelist_t ml;
		unsigned char stop = cch_generate_moves(b, ml, CCH_LEGAL, 0, 64);
		for(unsigned char i = 0; i < stop; ++i) {
			if(ml[i].end != m->end) continue;
			if(ml[i].start == m->start) continue;
			if(CCH_GET_SQUARE(b, ml[i].start) != CCH_GET_SQUARE(b, m->start)) continue;

			if(CCH_FILE(ml[i].start) == CCH_FILE(m->start)) include_rank = true;
			else include_file = true;
		}

		if(include_file) s[i++] = CCH_FILE(m->start) + 'a';
		if(include_rank) s[i++] = CCH_RANK(m->start) + '1';
	}

	if(CCH_GET_SQUARE(b, m->end)) {
		if(CCH_PURE_PIECE(CCH_GET_SQUARE(b, m->start)) == CCH_PAWN) {
			s[i++] = CCH_FILE(m->start) + 'a';
		}

		s[i++] = 'x';
	}

	s[i++] = CCH_FILE(m->end) + 'a';
	s[i++] = CCH_RANK(m->end) + '1';

	if(m->promote) {
		if(i + 2 > len) return CCH_OVERFLOW;
		s[i++] = '=';

		switch(m->promote) {
		case CCH_QUEEN: s[i++] = 'Q'; break;
		case CCH_ROOK: s[i++] = 'R'; break;
		case CCH_BISHOP: s[i++] = 'B'; break;
		case CCH_KNIGHT: s[i++] = 'N'; break;
		default: assert(0);
		}
	}

check_check:

	if(with_check) {
		if(i + 1 > len) return CCH_OVERFLOW;
		cch_undo_move_state_t undo;
		cch_return_t ret;
		ret = cch_play_move(b, m, &undo);
		if(ret == CCH_ILLEGAL_MOVE) return CCH_ILLEGAL_MOVE;
		assert(ret == CCH_OK);

		if(CCH_IS_OWN_KING_CHECKED(b)) {
			cch_movelist_t ml;
			unsigned char stop = cch_generate_moves(b, ml, CCH_LEGAL, 0, 64);
			s[i++] = (stop == 0) ? '#' : '+';
		}

		ret = cch_undo_move(b, m, &undo);
		assert(ret == CCH_OK);
	}

	s[i] = '\0';
	return CCH_OK;
}
