/* 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.
 */

#pragma once
#ifndef INCLUDE_GUMBLE_H
#define INCLUDE_GUMBLE_H

#ifndef NDEBUG
#define CCH_HASHTABLE_STATS 1
#endif

#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

/* 0..63, 0x00FFFRRR, higher values are invalid;
 * 0 = A1, 1 = A2, ..., 8 = B1, ..., 63 = H8 */
typedef unsigned char cch_square_t;

/* 1 bit per square */
typedef unsigned long long int cch_board_bitmap_t;

/* Zobrist hashes */
typedef unsigned long long int cch_hash_t;

typedef enum __attribute__((packed)) {
	CCH_OK = 0,
	CCH_ILLEGAL_MOVE,
	CCH_PARSE_ERROR,
	CCH_OVERFLOW,
} cch_return_t;

typedef enum __attribute__((packed)) {
	CCH_WHITE_TO_PLAY,
	CCH_BLACK_TO_PLAY,
	CCH_CHECKMATE_WHITE, /* White won */
	CCH_CHECKMATE_BLACK, /* Black won */
	CCH_STALEMATE,
	CCH_DRAW_THREEFOLD, /* XXX: todo */
	CCH_DRAW_FIFTY_MOVE_RULE,
} cch_game_state_t;

typedef enum __attribute__((packed)) {
	CCH_EMPTY = 0,
	CCH_PAWN = 1,
	CCH_BISHOP = 2,
	CCH_KNIGHT = 3,
	CCH_ROOK = 4,
	CCH_QUEEN = 5,
	CCH_KING = 6,
} cch_pure_piece_t;

typedef enum __attribute__((packed)) {
	CCH_PAWN_W = 2,
	CCH_PAWN_B = 3,
	CCH_BISHOP_W = 4,
	CCH_BISHOP_B = 5,
	CCH_KNIGHT_W = 6,
	CCH_KNIGHT_B = 7,
	CCH_ROOK_W = 8,
	CCH_ROOK_B = 9,
	CCH_QUEEN_W = 10,
	CCH_QUEEN_B = 11,
	CCH_KING_W = 12,
	CCH_KING_B = 13,
} cch_piece_t;

typedef enum __attribute__((packed)) {
	CCH_LEGAL = 0,
	CCH_PSEUDO_LEGAL,
} cch_move_legality_t;

typedef struct {
	cch_piece_t board[64];
	cch_board_bitmap_t pieces[14];
	cch_hash_t hash;
	unsigned int turn; /* starts at one */
	unsigned int smoves; /* moves since last capture or pawn advance */
	cch_square_t kings[2];
	cch_square_t en_passant; /* position "behind" pawn, or 255 if n/a */
	unsigned char castles; /* 0b0000KQkq */
	enum __attribute__((packed)) {
		CCH_BLACK = 0,
		CCH_WHITE = 1,
	} side;
} cch_board_t;



/* ----- hashtable.c ----- */

typedef struct {
	void* data;
	cch_hash_t mask;
	size_t entry_size;
#if CCH_HASHTABLE_STATS
	unsigned int gets;
	unsigned int hits;
	unsigned int sets;
	unsigned int overwrites;
	unsigned int collisions;
#endif
} cch_hashtable_t;

/* Allocate a hash table; 2nd parameter is log2 of number of entries,
 * 3rd parameter is the size of one stored entry. */
void cch_hashtable_init(cch_hashtable_t*, unsigned char, size_t);

/* Release a hashtable previously allocated by
 * cch_hashtable_init(). */
void cch_hashtable_free(cch_hashtable_t*);

/* Retreive a record from the hashtable. */
const void* cch_hashtable_get(cch_hashtable_t*, cch_hash_t);

/* Store a record in the hashtable. */
void cch_hashtable_set(cch_hashtable_t*, cch_hash_t, const void*);



/* ----- board.c ----- */

#define CCH_FILE(sq) ((sq) >> 3)
#define CCH_RANK(sq) ((sq) & 7)
#define CCH_DIAG1(sq) (7 + CCH_FILE(sq) - CCH_RANK(sq))
#define CCH_DIAG2(sq) (CCH_FILE(sq) + CCH_RANK(sq))
#define CCH_SQUARE(file, rank) (((file) << 3) | (rank))
#define CCH_IS_SQUARE_INVALID(sq) ((sq) & 192)
#define CCH_IS_SQUARE_VALID(sq) (!CCH_IS_SQUARE_INVALID(sq))

#define CCH_GET_SQUARE(b, sq) ((b)->board[sq])
#define CCH_SET_SQUARE(b, sq, p) do {					\
		(b)->pieces[(b)->board[sq]] &= ~(1ULL << (sq));	\
		(b)->pieces[p] |= (1ULL << (sq));				\
		(b)->board[sq] = (p);							\
	} while(0)
#define CCH_SET_SQUARE_WITH_HASH(b, sq, p) do {							\
		if((b)->board[sq]) {											\
			(b)->hash ^= cch_zobrist_pieces[(b)->board[sq] - 2][sq];	\
		}																\
		CCH_SET_SQUARE(b, sq, p);										\
		if((p) >= 2) {													\
			(b)->hash ^= cch_zobrist_pieces[(p) - 2][sq];				\
		}																\
	} while(0)
#define CCH_IS_OWN_PIECE(b, p) (p > 0 && ((p & 1) ^ (b)->side))
#define CCH_IS_ENEMY_PIECE(b, p) (p > 0 && (~p & 1) ^ (b)->side)
#define CCH_PURE_PIECE(p) ((p) >> 1)
#define CCH_MAKE_OWN_PIECE(b, p) ((p) << 1 | !(b)->side)
#define CCH_MAKE_ENEMY_PIECE(b, p) ((p) << 1 | (b)->side)
#define CCH_OWN_KING(b) ((b)->kings[(b)->side])

#define CCH_CAN_WHITE_CASTLE_KINGSIDE(b) ((b)->castles & 8)
#define CCH_CAN_WHITE_CASTLE_QUEENSIDE(b) ((b)->castles & 4)
#define CCH_CAN_BLACK_CASTLE_KINGSIDE(b) ((b)->castles & 2)
#define CCH_CAN_BLACK_CASTLE_QUEENSIDE(b) ((b)->castles & 1)
#define CCH_CAN_CASTLE_KINGSIDE(b) ((b)->castles & (2 << ((b)->side << 1)))
#define CCH_CAN_CASTLE_QUEENSIDE(b) ((b)->castles & (1 << ((b)->side << 1)))

/* Set up starting position of pieces, etc. */
void cch_init_board(cch_board_t*);

/* Get the current status of the game. */
cch_game_state_t cch_game_state(cch_board_t*);



/* ----- fen.c ----- */

#define SAFE_FEN_LENGTH 90

/* Load board setup from FEN string. */
cch_return_t cch_load_fen(cch_board_t*, const char*);

/* Save current board to FEN string. */
cch_return_t cch_save_fen(const cch_board_t*, char*, unsigned char);



/* ----- move.c ----- */

typedef struct {
	cch_square_t start;
	cch_square_t end;
	cch_pure_piece_t promote;
	unsigned char padding;
} cch_move_t;

typedef struct {
	cch_hash_t prev_hash;
	unsigned int prev_smoves;
	cch_piece_t prev_piece;
	cch_square_t prev_en_passant;
	unsigned char prev_castles;
} cch_undo_move_state_t;

/* Play a move that is assumed legal. */
cch_return_t cch_play_legal_move(cch_board_t*, const cch_move_t*, cch_undo_move_state_t*);

/* Play a move that is assumed pseudo-legal. Fails gracefully if the move is not legal. */
cch_return_t cch_play_pseudo_legal_move(cch_board_t*, const cch_move_t*, cch_undo_move_state_t*);

/* Play a move without any assumptions. Fails gracefully if the move is not legal. */
cch_return_t cch_play_move(cch_board_t*, const cch_move_t*, cch_undo_move_state_t*);

/* Undo a played move. */
cch_return_t cch_undo_move(cch_board_t*, const cch_move_t*, const cch_undo_move_state_t*);



/* ----- algebraic.c ----- */

#define SAFE_ALG_LENGTH 9

/* Parse a move in standard algebraic notation (SAN). If this returns
 * OK, the move is legal and can be used with cch_play_legal_move(). */
cch_return_t cch_parse_san_move(cch_board_t*, const char*, cch_move_t*);

/* Parse a move in long algebraic notation. */
cch_return_t cch_parse_lan_move(const char*, cch_move_t*);

/* Write a move in long algebraic notation. */
cch_return_t cch_format_lan_move(const cch_move_t*, char*, unsigned char);

/* Write a move in short algebraic notation. */
cch_return_t cch_format_san_move(cch_board_t*, const cch_move_t*, char*, unsigned char, bool);



/* ----- movegen.c ----- */

#define CCH_MOVELIST_LENGTH 256
typedef cch_move_t cch_movelist_t[CCH_MOVELIST_LENGTH];

/* Generate all legal or pseudo-legal moves from current
 * position. Returns the upper limit index of the move list. */
unsigned char cch_generate_moves(cch_board_t*, cch_movelist_t, cch_move_legality_t, cch_square_t, cch_square_t);

/* Is a move in a list generated by cch_generate_moves() ? */
bool cch_is_move_in_list(const cch_move_t*, const cch_movelist_t, unsigned char);

/* Is a certain move pseudo legal? This function is slow, use
 * cch_generate_moves() and cch_is_move_in_list() if you plan to do
 * lots of checks. */
bool cch_is_move_pseudo_legal(cch_board_t*, const cch_move_t*);

/* Same, but for legal moves. */
bool cch_is_move_legal(cch_board_t*, const cch_move_t*);



/* ----- check.c ----- */

#define CCH_IS_OWN_KING_CHECKED(b) cch_is_square_checked(b, CCH_OWN_KING(b))

/* Is a square pinned ? That is, if a piece on this square were to
 * move, would it produce a check on our king ? */
bool cch_is_square_pinned(const cch_board_t*, cch_square_t);

/* Is a square in check ? */
bool cch_is_square_checked(const cch_board_t*, cch_square_t);

/* Check if a known pseudo-legal move is legal. */
bool cch_is_pseudo_legal_move_legal(cch_board_t* b, const cch_move_t* m);



/* ----- eval.c ----- */

#define CCH_CHECKMATE_SCORE 1000000000

/* Evaluate the current position. */
int cch_evaluate_position(const cch_board_t*, cch_movelist_t, unsigned char);



/* ----- search.c ----- */

typedef struct {
	cch_board_t* b;
	cch_hashtable_t* tt;
	cch_movelist_t pv;
	unsigned int nodes;
	int eval;
	unsigned char start_depth;
	unsigned char pv_len;
} cch_search_context_t;

enum __attribute__((packed)) {
		CCH_EVAL_EXACT = 192,
		CCH_EVAL_LOWER_BOUND = 128,
		CCH_EVAL_UPPER_BOUND = 64,
} cch_eval_type_t;

typedef struct {
	cch_move_t best;
	int eval;
	/* Depth is best->padding & 64 */
	/* Eval type is best->padding & 192 */
} cch_transposition_entry_t;
static_assert(sizeof(cch_transposition_entry_t) == 8, "structure packing is messed up");

/* Init the search context with default values. Use it before searching. */
void cch_init_search_context(cch_search_context_t*, cch_board_t*, cch_hashtable_t* tt);

/* Search the game tree for the best move. Reuse the search context in deeper searches. */
void cch_search_best_move(unsigned char, cch_search_context_t*);

#endif
