/* 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 "moves.h"

#define CCH_ADD_MOVE(b, list, i, s, e, p) do {						\
		(list)[i].start = (s);										\
		(list)[i].end = (e);										\
		(list)[i].promote = (p);									\
		if(pseudo_legal || cch_is_pseudo_legal_move_legal((b), &(list)[i])) ++i; \
	} while(0)

/* Add move if moving to empty square or enemy piece */
#define CCH_ADD_MOVE_CHK(b, list, i, s, e, p) do {						\
		if(CCH_IS_SQUARE_INVALID((e)) || CCH_IS_OWN_PIECE((b), CCH_GET_SQUARE((b), (e)))) break; \
		CCH_ADD_MOVE((b), (list), (i), (s), (e), (p));						\
	} while(0)

/* Add pawn move if moving to empty square */
#define CCH_ADD_MOVE_EMP(b, list, i, s, e, p) do {		\
		if(CCH_GET_SQUARE((b), (e))) break;	\
		CCH_ADD_MOVE((b), (list), (i), (s), (e), (p));			\
	} while(0)

/* Add pawn move if capturing an enemy piece */
#define CCH_ADD_MOVE_CAP(b, list, i, s, e, p) do {						\
		if(CCH_IS_SQUARE_INVALID((e))) break;								\
		cch_piece_t tgtp = CCH_GET_SQUARE((b), (e));					\
		/* moving to an ally piece OR moving to empty piece that isn't en passant */ \
		if(CCH_IS_OWN_PIECE((b), tgtp) || (!tgtp && (e) != (b)->en_passant)) break; \
		CCH_ADD_MOVE((b), (list), (i), (s), (e), (p));					\
	} while(0)

/* Add "ray" moves */
#define CCH_ADD_RAY(b, list, i, s, DIRECTION) do {						\
		for(cch_square_t nsq = DIRECTION((s)); CCH_IS_SQUARE_VALID(nsq); nsq = DIRECTION(nsq)) { \
			cch_square_t tsq = CCH_GET_SQUARE((b), nsq);				\
			if(CCH_IS_OWN_PIECE((b), tsq)) break;						\
			CCH_ADD_MOVE((b), (list), (i), (s), (nsq), 0);				\
			if(CCH_IS_ENEMY_PIECE((b), tsq)) break;						\
		}																\
	} while(0)

static unsigned char cch_generate_piece_moves(cch_board_t* b, cch_square_t sq, cch_piece_t p, bool pseudo_legal, bool in_check, cch_movelist_t list, unsigned char i) {
	cch_square_t tsq;

	switch(CCH_PURE_PIECE(p)) {
	case CCH_KING:
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_NORTH(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_SOUTH(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_WEST(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_EAST(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_NORTHWEST(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_NORTHEAST(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_SOUTHWEST(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_SOUTHEAST(sq), 0);
		if(in_check) break;
		if(CCH_CAN_CASTLE_KINGSIDE(b) && !CCH_GET_SQUARE(b, sq + 8) && !CCH_GET_SQUARE(b, sq + 16) && !cch_is_square_checked(b, sq + 8)) {
			CCH_ADD_MOVE(b, list, i, sq, sq + 16, 0);
		}
		if(CCH_CAN_CASTLE_QUEENSIDE(b) && !CCH_GET_SQUARE(b, sq - 8) && !CCH_GET_SQUARE(b, sq - 16) && !CCH_GET_SQUARE(b, sq - 24) && !cch_is_square_checked(b, sq - 8)) {
			CCH_ADD_MOVE(b, list, i, sq, sq - 16, 0);
		}
		break;

	case CCH_KNIGHT:
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_NNW(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_NNE(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_SSW(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_SSE(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_WWN(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_WWS(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_EEN(sq), 0);
		CCH_ADD_MOVE_CHK(b, list, i, sq, CCH_EES(sq), 0);
		break;

	case CCH_PAWN:
		switch(p) {
		case CCH_PAWN_W:
			switch(CCH_RANK(sq)) {
			case 1:
				tsq = sq + 1;
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_WEST(tsq), 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_EAST(tsq), 0);
				if(CCH_GET_SQUARE(b, tsq)) break;
				CCH_ADD_MOVE(b, list, i, sq, tsq, 0);
				CCH_ADD_MOVE_EMP(b, list, i, sq, tsq + 1, 0);
				break;

			case 6:
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq + 1, CCH_QUEEN);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq + 1, CCH_KNIGHT);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq + 1, CCH_ROOK);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq + 1, CCH_BISHOP);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHWEST(sq), CCH_QUEEN);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHWEST(sq), CCH_KNIGHT);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHWEST(sq), CCH_ROOK);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHWEST(sq), CCH_BISHOP);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHEAST(sq), CCH_QUEEN);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHEAST(sq), CCH_KNIGHT);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHEAST(sq), CCH_ROOK);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHEAST(sq), CCH_BISHOP);
				break;

			case 7:
				assert(0);

			default:
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq + 1, 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHWEST(sq), 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_NORTHEAST(sq), 0);
			}
			break;

		case CCH_PAWN_B:
			switch(CCH_RANK(sq)) {
			case 6:
				tsq = sq - 1;
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_WEST(tsq), 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_EAST(tsq), 0);
				if(CCH_GET_SQUARE(b, tsq)) break;
				CCH_ADD_MOVE(b, list, i, sq, tsq, 0);
				CCH_ADD_MOVE_EMP(b, list, i, sq, tsq - 1, 0);
				break;

			case 1:
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq - 1, CCH_QUEEN);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq - 1, CCH_KNIGHT);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq - 1, CCH_ROOK);
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq - 1, CCH_BISHOP);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHWEST(sq), CCH_QUEEN);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHWEST(sq), CCH_KNIGHT);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHWEST(sq), CCH_ROOK);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHWEST(sq), CCH_BISHOP);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHEAST(sq), CCH_QUEEN);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHEAST(sq), CCH_KNIGHT);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHEAST(sq), CCH_ROOK);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHEAST(sq), CCH_BISHOP);
				break;

			case 0:
				assert(0);

			default:
				CCH_ADD_MOVE_EMP(b, list, i, sq, sq - 1, 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHWEST(sq), 0);
				CCH_ADD_MOVE_CAP(b, list, i, sq, CCH_SOUTHEAST(sq), 0);
			}
			break;

		default:
			assert(0);
		}
		break;

	case CCH_ROOK:
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTH);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTH);
		CCH_ADD_RAY(b, list, i, sq, CCH_WEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_EAST);
		break;

	case CCH_BISHOP:
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTHWEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTHEAST);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTHWEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTHEAST);
		break;

	case CCH_QUEEN:
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTH);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTH);
		CCH_ADD_RAY(b, list, i, sq, CCH_WEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_EAST);
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTHWEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_NORTHEAST);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTHWEST);
		CCH_ADD_RAY(b, list, i, sq, CCH_SOUTHEAST);
		break;

	case 0:
		break;

	default:
		assert(0);
	}

	return i;
}

unsigned char cch_generate_moves(cch_board_t* b, cch_movelist_t list, cch_move_legality_t type, cch_square_t start, cch_square_t stop) {
	unsigned char i = 0;
	cch_square_t sq;
	cch_piece_t p;
	bool in_check = CCH_IS_OWN_KING_CHECKED(b);
	bool pseudo_legal = (type != CCH_LEGAL);

	if(pseudo_legal || in_check) {
		for(sq = start; sq < stop; ++sq) {
			p = CCH_GET_SQUARE(b, sq);
			if(!CCH_IS_OWN_PIECE(b, p)) continue;
			i = cch_generate_piece_moves(b, sq, p, pseudo_legal, in_check, list, i);
		}
	} else {
		/* Not in check, only check legality of king moves and pinned pieces */
		cch_square_t king = CCH_OWN_KING(b);
		for(sq = start; sq < stop; ++sq) {
			p = CCH_GET_SQUARE(b, sq);
			if(!CCH_IS_OWN_PIECE(b, p)) continue;
			i = cch_generate_piece_moves(b, sq, p, sq != king && (CCH_PURE_PIECE(p) != CCH_PAWN || CCH_IS_SQUARE_INVALID(b->en_passant)) && !cch_is_square_pinned(b, sq), in_check, list, i);
		}
	}

	return i;
}

/* Assumes that movelist is sorted by start square */
static bool cch_is_move_in_list_r(const cch_movelist_t ml, const cch_move_t* m, unsigned char start, unsigned char stop) {
	if(start >= stop) return false;

	/* Avoid oveflow */
	unsigned char mid = stop - 1 - ((stop - 1 - start) >> 1);

	if(ml[mid].start < m->start) {
		return cch_is_move_in_list_r(ml, m, mid + 1, stop);
	} else if(ml[mid].start > m->start) {
		return cch_is_move_in_list_r(ml, m, start, mid);
	}

	unsigned char i;
	for(i = mid; i < stop && ml[i].start == m->start; ++i) {
		if(ml[i].end == m->end && ml[i].promote == m->promote) return true;
	}
	for(i = mid - 1; i != 255 && ml[i].start == m->start; --i) {
		if(ml[i].end == m->end && ml[i].promote == m->promote) return true;
	}

	return false;
}

bool cch_is_move_in_list(const cch_move_t* m, const cch_movelist_t ml, unsigned char stop) {
	return cch_is_move_in_list_r(ml, m, 0, stop);
}

bool cch_is_move_pseudo_legal(cch_board_t* b, const cch_move_t* m) {
	cch_movelist_t list;
	unsigned char stop = cch_generate_moves(b, list, CCH_PSEUDO_LEGAL, m->start, m->start + 1);
	return cch_is_move_in_list_r(list, m, 0, stop);
}

bool cch_is_move_legal(cch_board_t* b, const cch_move_t* m) {
	cch_movelist_t list;
	unsigned char stop = cch_generate_moves(b, list, CCH_LEGAL, m->start, m->start + 1);
	return cch_is_move_in_list_r(list, m, 0, stop);
}
