/* 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 <assert.h>
#include <stdio.h>
#include <string.h>
#include "gumble.h"
#include "zobrist.h"

cch_return_t cch_load_fen(cch_board_t* b, const char* fen) {
	memset(b->board, 0, 64 * sizeof(cch_piece_t));
	memset(b->pieces, 0, 14 * sizeof(cch_board_bitmap_t));
	b->hash = 0;

	/* Piece placement */
	unsigned char rank = 7, file = 0;
	while(*fen != '\0' && *fen != ' ') {
		if(*fen != '/' && file > 7) return CCH_PARSE_ERROR;

		switch(*fen) {
		case '/':
			--rank;
			file = 0;
			if(rank < 0) return CCH_PARSE_ERROR;
			++fen;
			continue;

		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
			if((*fen - '0') > (8 - file)) return CCH_PARSE_ERROR;
			for(unsigned char i = 0; i < (*fen - '0'); ++i) {
				CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), 0);
				++file;
			}
			++fen;
			continue;

		case 'p':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_PAWN_B);
			break;

		case 'P':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_PAWN_W);
			break;

		case 'k':
			b->kings[CCH_BLACK] = CCH_SQUARE(file, rank);
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_KING_B);
			break;

		case 'K':
			b->kings[CCH_WHITE] = CCH_SQUARE(file, rank);
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_KING_W);
			break;

		case 'q':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_QUEEN_B);
			break;

		case 'Q':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_QUEEN_W);
			break;

		case 'b':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_BISHOP_B);
			break;

		case 'B':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_BISHOP_W);
			break;

		case 'n':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_KNIGHT_B);
			break;

		case 'N':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_KNIGHT_W);
			break;

		case 'r':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_ROOK_B);
			break;

		case 'R':
			CCH_SET_SQUARE_WITH_HASH(b, CCH_SQUARE(file, rank), CCH_ROOK_W);
			break;
		}

		++file;
		++fen;
	}

	if(*fen != ' ' || rank != 0 || file != 8) return CCH_PARSE_ERROR;

	++fen; /* read space */

	/* Whose turn is it? */
	if(*fen == '\0' || (*fen != 'w' && *fen != 'b')) return CCH_PARSE_ERROR;
	b->side = (*fen == 'w');
	if(b->side) b->hash ^= cch_zobrist_side;

	++fen;
	if(*fen != ' ') return CCH_PARSE_ERROR;

	/* Castling rights */
	++fen;
	b->castles = 0;
	while(*fen != '\0' && *fen != ' ') {
		switch(*fen) {
		case '-':
			break;

		case 'K':
			b->castles |= 8;
			b->hash ^= cch_zobrist_castles[3];
			break;

		case 'k':
			b->castles |= 2;
			b->hash ^= cch_zobrist_castles[1];
			break;

		case 'Q':
			b->castles |= 4;
			b->hash ^= cch_zobrist_castles[2];
			break;

		case 'q':
			b->castles |= 1;
			b->hash ^= cch_zobrist_castles[0];
			break;

		default:
			return CCH_PARSE_ERROR;
		}

		++fen;
	}

	if(*fen != ' ') return CCH_PARSE_ERROR;

	/* En passant */
	++fen;
	if(*fen == '-') {
		b->en_passant = 255;
		++fen;
	} else {
		if(*fen > 'h' || *fen < 'a') return CCH_PARSE_ERROR;
		if(fen[1] > '8' || fen[1] < '1') return CCH_PARSE_ERROR;
		b->en_passant = CCH_SQUARE(fen[0] - 'a', fen[1] - '1');
		b->hash ^= cch_zobrist_ep[CCH_FILE(b->en_passant)];
		fen += 2;
	}

	if(*fen == '\0') {
		/* lichess.org board editor gives a "shortened" FEN, omitting the last two fields */
		b->smoves = 0;
		b->turn = 1;
		return CCH_OK;
	}

	if(*fen != ' ') return CCH_PARSE_ERROR;

	/* Move counter since last pawn move/capture */
	++fen;
	b->smoves = 0;
	while(*fen >= '0' && *fen <= '9') {
		b->smoves *= 10;
		b->smoves += (*fen - '0');
		++fen;
	}

	if(*fen != ' ') return CCH_PARSE_ERROR;

	/* Turn counter */
	++fen;
	b->turn = 0;
	while(*fen >= '0' && *fen <= '9') {
		b->turn *= 10;
		b->turn += (*fen - '0');
		++fen;
	}

	if(*fen != '\0') return CCH_PARSE_ERROR;

	return CCH_OK;
}

#define SAFE_APPEND(c, str, len) do { if(len) { *(str) = (c); --(len); ++str; } else return CCH_OVERFLOW; } while(0)

cch_return_t cch_save_fen(const cch_board_t* b, char* out, unsigned char len) {
	static const char piece_table[] = {
		'\0', '\0', 'P', 'p', 'B', 'b', 'N', 'n', 'R', 'r', 'Q', 'q', 'K', 'k'
	};

	char r;
	unsigned char f, blanks;
	cch_piece_t p;

	for(r = 7; r >= 0; --r) {
		blanks = 0;

		for(f = 0; f < 8; ++f) {
			if((p = CCH_GET_SQUARE(b, CCH_SQUARE(f, r)))) {
				if(blanks) {
					SAFE_APPEND('0' + blanks, out, len);
					blanks = 0;
				}

				assert(p >= 2 && p <= 13);
				SAFE_APPEND(piece_table[p], out, len);
			} else {
				++blanks;
			}
		}

		if(blanks) {
			SAFE_APPEND('0' + blanks, out, len);
		}

		if(r) SAFE_APPEND('/', out, len);
	}

	SAFE_APPEND(' ', out, len);
	SAFE_APPEND(b->side ? 'w' : 'b', out, len);

	SAFE_APPEND(' ', out, len);
	if(CCH_CAN_WHITE_CASTLE_KINGSIDE(b)) SAFE_APPEND('K', out, len);
	if(CCH_CAN_WHITE_CASTLE_QUEENSIDE(b)) SAFE_APPEND('Q', out, len);
	if(CCH_CAN_BLACK_CASTLE_KINGSIDE(b)) SAFE_APPEND('k', out, len);
	if(CCH_CAN_BLACK_CASTLE_QUEENSIDE(b)) SAFE_APPEND('q', out, len);
	if(!b->castles) {
		SAFE_APPEND('-', out, len);
	}

	SAFE_APPEND(' ', out, len);
	if(b->en_passant < 64) {
		SAFE_APPEND('a' + CCH_FILE(b->en_passant), out, len);
		SAFE_APPEND('1' + CCH_RANK(b->en_passant), out, len);
	} else {
		SAFE_APPEND('-', out, len);
	}

	if(snprintf(out, len, " %u %u", b->smoves, b->turn) >= len) {
		return CCH_OVERFLOW;
	}

	return CCH_OK;
}
