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



static inline void cch_check_castling(cch_board_t* b, cch_square_t sq) {
	switch(sq) {
	case 7:
		if(~b->castles & 1) break;
		b->hash ^= cch_zobrist_castles[0];
		b->castles &= ~1;
		break;

	case 63:
		if(~b->castles & 2) break;
		b->hash ^= cch_zobrist_castles[1];
		b->castles &= ~2;
		break;

	case 0:
		if(~b->castles & 4) break;
		b->hash ^= cch_zobrist_castles[2];
		b->castles &= ~4;
		break;

	case 56:
		if(~b->castles & 8) break;
		b->hash ^= cch_zobrist_castles[3];
		b->castles &= ~8;
		break;

	case 39:
		if(b->castles & 1) b->hash ^= cch_zobrist_castles[0];
		if(b->castles & 2) b->hash ^= cch_zobrist_castles[1];
		b->castles &= ~3;
		break;

	case 32:
		if(b->castles & 4) b->hash ^= cch_zobrist_castles[2];
		if(b->castles & 8) b->hash ^= cch_zobrist_castles[3];
		b->castles &= ~12;
		break;
	}
}

cch_return_t cch_play_legal_move(cch_board_t* b, const cch_move_t* move, cch_undo_move_state_t* undo) {
	cch_piece_t moving = CCH_GET_SQUARE(b, move->start);
	cch_piece_t prev = CCH_GET_SQUARE(b, move->end);

	if(undo) {
		undo->prev_hash = b->hash;
		undo->prev_smoves = b->smoves;
		undo->prev_piece = prev;
		undo->prev_en_passant = b->en_passant;
		undo->prev_castles = b->castles;
	}

	if(CCH_PURE_PIECE(moving) == CCH_KING) {
		b->kings[b->side] = move->end;
		switch(move->end - move->start) {
		case -16:
			/* Castle long */
			CCH_SET_SQUARE_WITH_HASH(b, move->start - 8, CCH_GET_SQUARE(b, move->start - 32));
			CCH_SET_SQUARE_WITH_HASH(b, move->start - 32, 0);
			break;

		case 16:
			/* Castle short */
			CCH_SET_SQUARE_WITH_HASH(b, move->start + 8, CCH_GET_SQUARE(b, move->start + 24));
			CCH_SET_SQUARE_WITH_HASH(b, move->start + 24, 0);
			break;
		}
	}

	if(CCH_PURE_PIECE(moving) == CCH_PAWN && move->end == b->en_passant) {
		switch(moving) {
		case CCH_PAWN_B:
			assert(CCH_RANK(move->end) == 2 && CCH_GET_SQUARE(b, move->end + 1) == CCH_PAWN_W);
			CCH_SET_SQUARE_WITH_HASH(b, move->end + 1, 0);
			break;

		case CCH_PAWN_W:
			assert(CCH_RANK(move->end) == 5 && CCH_GET_SQUARE(b, move->end - 1) == CCH_PAWN_B);
			CCH_SET_SQUARE_WITH_HASH(b, move->end - 1, 0);
			break;

		default: assert(0);
		}
	}

	if(move->promote) {
		cch_piece_t to = CCH_MAKE_OWN_PIECE(b, move->promote);
		CCH_SET_SQUARE_WITH_HASH(b, move->end, to);
		CCH_SET_SQUARE_WITH_HASH(b, move->start, 0);
	} else {
		CCH_SET_SQUARE_WITH_HASH(b, move->end, moving);
		CCH_SET_SQUARE_WITH_HASH(b, move->start, 0);
	}

	if(b->castles) {
		cch_check_castling(b, move->start);
		cch_check_castling(b, move->end);
	}

	if(b->en_passant < 255) {
		b->hash ^= cch_zobrist_ep[CCH_FILE(b->en_passant)];
	}
	if(moving == CCH_PAWN_W && move->end - move->start == 2) {
		b->en_passant = move->start + 1;
		b->hash ^= cch_zobrist_ep[CCH_FILE(b->en_passant)];
	} else if(moving == CCH_PAWN_B && move->start - move->end == 2) {
		b->en_passant = move->start - 1;
		b->hash ^= cch_zobrist_ep[CCH_FILE(b->en_passant)];
	} else {
		b->en_passant = 255;
	}

	if(!b->side) ++b->turn;
	b->side = !b->side;
	b->hash ^= cch_zobrist_side;

	if(prev > 0 || CCH_PURE_PIECE(moving) == CCH_PAWN) {
		b->smoves = 0;
	} else {
		++b->smoves;
	}

	return CCH_OK;
}

cch_return_t cch_play_pseudo_legal_move(cch_board_t* b, const cch_move_t* move, cch_undo_move_state_t* undo) {
	assert(cch_is_move_pseudo_legal(b, move));
	if(!cch_is_pseudo_legal_move_legal(b, move)) return CCH_ILLEGAL_MOVE;
	return cch_play_legal_move(b, move, undo);
}

cch_return_t cch_play_move(cch_board_t* b, const cch_move_t* move, cch_undo_move_state_t* undo) {
	if(!cch_is_move_pseudo_legal(b, move)) return CCH_ILLEGAL_MOVE;
	return cch_play_pseudo_legal_move(b, move, undo);
}

cch_return_t cch_undo_move(cch_board_t* b, const cch_move_t* move, const cch_undo_move_state_t* undo) {
	assert(CCH_GET_SQUARE(b, move->start) == 0);
	cch_piece_t moving = CCH_GET_SQUARE(b, move->end);

	b->hash = undo->prev_hash;
	b->smoves = undo->prev_smoves;
	b->en_passant = undo->prev_en_passant;
	b->castles = undo->prev_castles;

	if(b->side) --b->turn;
	b->side = !b->side;


	if(CCH_PURE_PIECE(moving) == CCH_KING) {
		b->kings[b->side] = move->start;
		switch(move->end - move->start) {
		case -16:
			CCH_SET_SQUARE(b, move->start - 32, CCH_GET_SQUARE(b, move->start - 8));
			CCH_SET_SQUARE(b, move->start - 8, 0);
			break;

		case 16:
			CCH_SET_SQUARE(b, move->start + 24, CCH_GET_SQUARE(b, move->start + 8));
			CCH_SET_SQUARE(b, move->start + 8, 0);
			break;
		}
	}

	if(CCH_PURE_PIECE(moving) == CCH_PAWN && move->end == b->en_passant) {
		switch(moving) {
		case CCH_PAWN_B:
			CCH_SET_SQUARE(b, move->end + 1, CCH_PAWN_W);
			break;

		case CCH_PAWN_W:
			CCH_SET_SQUARE(b, move->end - 1, CCH_PAWN_B);
			break;

		default: assert(0);
		}
	}

	if(move->promote) {
		cch_piece_t from = CCH_MAKE_OWN_PIECE(b, CCH_PAWN);
		CCH_SET_SQUARE(b, move->start, from);
		CCH_SET_SQUARE(b, move->end, undo->prev_piece);
	} else {
		CCH_SET_SQUARE(b, move->start, moving);
		CCH_SET_SQUARE(b, move->end, undo->prev_piece);
	}

	return CCH_OK;
}
