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

#ifndef BULK
#error "BULK not defined"
#endif

typedef unsigned long long int biguint;

typedef struct {
	biguint nodes;
#if !BULK
	biguint captures;
	biguint ep;
	biguint castles;
	biguint promotions;
	biguint checks;
#endif
} perft_context_t;

static cch_board_t board;
static cch_hashtable_t ht;
static unsigned int init_depth;
static bool divide;

/* https://chessprogramming.wikispaces.com/Perft */
static void perft(unsigned int depth, perft_context_t* out_ctx) {
	cch_movelist_t ml;
	unsigned char i, stop;
	cch_undo_move_state_t undo;

	const perft_context_t* ht_ctx = cch_hashtable_get(&ht, board.hash ^ depth);
	if(ht_ctx) {
		out_ctx->nodes += ht_ctx->nodes;
#if !BULK
		out_ctx->captures += ht_ctx->captures;
		out_ctx->ep += ht_ctx->ep;
		out_ctx->castles += ht_ctx->castles;
		out_ctx->promotions += ht_ctx->promotions;
		out_ctx->checks += ht_ctx->checks;
#endif
		return;
	}

	perft_context_t ctx;
	memset(&ctx, 0, sizeof(perft_context_t));

	stop = cch_generate_moves(&board, ml, CCH_LEGAL, 0, 64);

	if(BULK && depth == 1) {
		out_ctx->nodes += stop;
		return;
	}

	for(i = 0; i < stop; ++i) {
		cch_play_legal_move(&board, &ml[i], &undo);
		if(depth == 1) {
			++ctx.nodes;
#if !BULK
			if(undo.prev_piece) ++ctx.captures;
			if(ml[i].promote) ++ctx.promotions;
			switch(CCH_PURE_PIECE(CCH_GET_SQUARE(&board, ml[i].end))) {
			case CCH_KING:
				if(ml[i].start + 16 == ml[i].end || ml[i].start - 16 == ml[i].end) {
					++ctx.castles;
				}
				break;
			case CCH_PAWN:
				if(ml[i].end == undo.prev_en_passant) {
					++ctx.ep;
					++ctx.captures;
				}
				break;
			default:
				break;
			}
			if(CCH_IS_OWN_KING_CHECKED(&board)) ++ctx.checks;
#endif
		} else {
			if(divide && depth == init_depth) {
				memset(&ctx, 0, sizeof(perft_context_t));
			}
			perft(depth - 1, &ctx);
			if(divide && depth == init_depth) {
				char alg[SAFE_ALG_LENGTH];
				cch_format_lan_move(&(ml[i]), alg, SAFE_ALG_LENGTH);
#if BULK
				printf(
					"%s: perft(%u)=%llu nodes\n",
					alg, depth - 1, ctx.nodes);
#else
				printf(
					"%s: perft(%u)=%llu nodes, %llu captures, %llu ep, %llu castles, %llu promotions, %llu checks\n",
					alg, depth - 1, ctx.nodes, ctx.captures, ctx.ep, ctx.castles, ctx.promotions, ctx.checks);
#endif
			}
		}
		cch_undo_move(&board, &ml[i], &undo);
	}

	out_ctx->nodes += ctx.nodes;
#if !BULK
		out_ctx->captures += ctx.captures;
		out_ctx->ep += ctx.ep;
		out_ctx->castles += ctx.castles;
		out_ctx->promotions += ctx.promotions;
		out_ctx->checks += ctx.checks;
#endif
	cch_hashtable_set(&ht, board.hash ^ depth, &ctx);
}

int main(int argc, char** argv) {
	unsigned int depth;
	perft_context_t ctx;
	memset(&ctx, 0, sizeof(perft_context_t));
	cch_hashtable_init(&ht, 20, sizeof(perft_context_t));

	if(argc < 3 || argc > 4 || cch_load_fen(&board, argv[1]) != CCH_OK || !(depth = strtoul(argv[2], NULL, 10))) {
		fprintf(stderr, "Usage: %s <initial-fen> <depth> [1]\n", argv[0]);
		return 1;
	}
	if(argc == 4) {
		divide = (strtoul(argv[3], NULL, 10) == 1);
	} else divide = false;

	init_depth = depth;
	perft(depth, &ctx);
	if(!divide) {
#if BULK
		printf("perft(%u)=%llu nodes\n", depth, ctx.nodes);
#else
		printf("perft(%u)=%llu nodes, %llu captures, %llu ep, %llu castles, %llu promotions, %llu checks\n",
			   depth, ctx.nodes, ctx.captures, ctx.ep, ctx.castles, ctx.promotions, ctx.checks);
#endif
	}

	cch_hashtable_free(&ht);
	return 0;
}
