/*
 ============================================================================
 Name        : fast-chess.c
 Author      : Frederico Jordan <fredericojordan@gmail.com>
 Version     :
 Copyright   : Copyright (c) 2016 Frederico Jordan
 Description : Simple chess game!
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#include "fast_chess.h"

char FILES[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
char RANKS[8] = {'1', '2', '3', '4', '5', '6', '7', '8'};

Bitboard FILES_BB[8] = {FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H};
Bitboard RANKS_BB[8] = {RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8};

char INITIAL_FEN[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

Board EMPTY_BOARD = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Board INITIAL_BOARD = {
    FILE_E & RANK_1, // whiteKing;
    FILE_D& RANK_1, // whiteQueens;
    (FILE_A | FILE_H) & RANK_1, // whiteRooks;
    (FILE_B | FILE_G) & RANK_1, // whiteKnights;
    (FILE_C | FILE_F) & RANK_1, // whiteBishops;
    RANK_2, // whitePawns;

    FILE_E& RANK_8, // blackKing;
    FILE_D& RANK_8, // blackQueens;
    (FILE_A | FILE_H) & RANK_8, // blackRooks;
    (FILE_B | FILE_G) & RANK_8, // blackKnights;
    (FILE_C | FILE_F) & RANK_8, // blackBishops;
    RANK_7, // blackPawns;
};

int PIECE_VALUES[] = {
    0, // EMPTY
    100, // PAWN
    300, // KNIGHT
    300, // BISHOP
    500, // ROOK
    900, // QUEEN
    42000 // KING
};

int PAWN_BONUS[] = {0, 0,  0,  0,   0,   0,  0,  0, 0, 0, 0,  -40, -40, 0,  0, 0,
                    1, 2,  3,  -10, -10, 3,  2,  1, 2, 4, 6,  8,   8,   6,  4, 2,
                    3, 6,  9,  12,  12,  9,  6,  3, 4, 8, 12, 16,  16,  12, 8, 4,
                    5, 10, 15, 20,  20,  15, 10, 5, 0, 0, 0,  0,   0,   0,  0, 0};

int KNIGHT_BONUS[] = {-10, -30, -10, -10, -10, -10, -30, -10, -10, 0,   0,   0,   0,
                      0,   0,   -10, -10, 0,   5,   5,   5,   5,   0,   -10, -10, 0,
                      5,   10,  10,  5,   0,   -10, -10, 0,   5,   10,  10,  5,   0,
                      -10, -10, 0,   5,   5,   5,   5,   0,   -10, -10, 0,   0,   0,
                      0,   0,   0,   -10, -10, -10, -10, -10, -10, -10, -10, -10};

int BISHOP_BONUS[] = {-10, -10, -20, -10, -10, -20, -10, -10, -10, 0,   0,   0,   0,
                      0,   0,   -10, -10, 0,   5,   5,   5,   5,   0,   -10, -10, 0,
                      5,   10,  10,  5,   0,   -10, -10, 0,   5,   10,  10,  5,   0,
                      -10, -10, 0,   5,   5,   5,   5,   0,   -10, -10, 0,   0,   0,
                      0,   0,   0,   -10, -10, -10, -10, -10, -10, -10, -10, -10};

int KING_BONUS[] = {0,   20,  40,  -20, 0,   -20, 40,  20,  -20, -20, -20, -20, -20,
                    -20, -20, -20, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40,
                    -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40,
                    -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40,
                    -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40};

int KING_ENDGAME_BONUS[] = {0,  10, 20, 30, 30, 20, 10, 0,  10, 20, 30, 40, 40, 30, 20, 10,
                            20, 30, 40, 50, 50, 40, 30, 20, 30, 40, 50, 60, 60, 50, 40, 30,
                            30, 40, 50, 60, 60, 50, 40, 30, 20, 30, 40, 50, 50, 40, 30, 20,
                            10, 20, 30, 40, 40, 30, 20, 10, 0,  10, 20, 30, 30, 20, 10, 0};

int FLIP_VERTICAL[] = {56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55,
                       40, 41, 42, 43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39,
                       24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23,
                       8,  9,  10, 11, 12, 13, 14, 15, 0,  1,  2,  3,  4,  5,  6,  7};

void getInitialGame(Game* game) {
    game->position.board = INITIAL_BOARD;
    game->position.toMove = WHITE;
    game->position.epSquare = -1;
    game->position.castlingRights = CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE |
                                    CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK;
    game->position.halfmoveClock = 0;
    game->position.fullmoveNumber = 1;

    game->moveListLen = 0;
    memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int));
    memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char));
    memcpy(game->positionHistory[0], INITIAL_FEN, sizeof(INITIAL_FEN));
}

void getFenGame(Game* game, char fen[]) {
    int fenLen = loadFen(&(game->position), fen);

    game->moveListLen = 0;
    memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int));
    memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char));
    memcpy(game->positionHistory[0], fen, fenLen);
}

void insertPiece(Board* board, Bitboard position, char pieceCode) {
    switch(pieceCode) {
    case 'P':
        board->whitePawns |= position;
        break;
    case 'N':
        board->whiteKnights |= position;
        break;
    case 'B':
        board->whiteBishops |= position;
        break;
    case 'R':
        board->whiteRooks |= position;
        break;
    case 'Q':
        board->whiteQueens |= position;
        break;
    case 'K':
        board->whiteKing |= position;
        break;

    case 'p':
        board->blackPawns |= position;
        break;
    case 'n':
        board->blackKnights |= position;
        break;
    case 'b':
        board->blackBishops |= position;
        break;
    case 'r':
        board->blackRooks |= position;
        break;
    case 'q':
        board->blackQueens |= position;
        break;
    case 'k':
        board->blackKing |= position;
        break;
    }
}

int loadFen(Position* position, char fen[]) {
    // ===== BOARD =====
    position->board = EMPTY_BOARD;

    int rank = 7;
    int boardPos = rank * 8;
    char* charPos = fen;

    char pieceCode = *(charPos);

    while(pieceCode != ' ') {
        if(pieceCode == '/') {
            rank--;
            boardPos = rank * 8;
        } else if(isdigit(pieceCode)) {
            int emptySquares = atoi(charPos);
            boardPos += emptySquares;
        } else {
            insertPiece(&(position->board), index2bb(boardPos++), pieceCode);
        }

        pieceCode = *(++charPos);
    }

    // ===== TO MOVE =====
    char* nextFenField = strchr(fen, ' ') + 1;

    if(*nextFenField == 'b') {
        position->toMove = BLACK;
    } else {
        position->toMove = WHITE;
    }

    // ===== CASTLING RIGHTS =====
    nextFenField = strchr(nextFenField, ' ') + 1;

    position->castlingRights = 0;
    if(strchr(nextFenField, 'K')) position->castlingRights |= CASTLE_KINGSIDE_WHITE;
    if(strchr(nextFenField, 'Q')) position->castlingRights |= CASTLE_QUEENSIDE_WHITE;
    if(strchr(nextFenField, 'k')) position->castlingRights |= CASTLE_KINGSIDE_BLACK;
    if(strchr(nextFenField, 'q')) position->castlingRights |= CASTLE_QUEENSIDE_BLACK;

    // ===== EN PASSANT =====
    nextFenField = strchr(nextFenField, ' ') + 1;

    if(*nextFenField == '-') {
        position->epSquare = -1;
    } else {
        position->epSquare = str2index(nextFenField);
    }

    // ===== HALF MOVE CLOCK =====
    if(!strchr(nextFenField, ' ')) {
        position->halfmoveClock = 0;
        position->fullmoveNumber = 1;
        return 1 + nextFenField - fen;
    }
    nextFenField = strchr(nextFenField, ' ') + 1;

    position->halfmoveClock = atoi(nextFenField);

    // ===== FULL MOVE NUMBER =====
    if(!strchr(nextFenField, ' ')) {
        position->fullmoveNumber = 1;
        return 1 + nextFenField - fen;
    }
    nextFenField = strchr(nextFenField, ' ') + 1;

    position->fullmoveNumber = atoi(nextFenField);

    return 1 + nextFenField - fen;
}

int toFen(char* fen, Position* position) {
    int charCount = toMinFen(fen, position);
    fen[charCount - 1] = ' ';

    // ===== HALF MOVE CLOCK =====
    snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->halfmoveClock);
    if(position->halfmoveClock >= 10) {
        charCount++;
        if(position->halfmoveClock >= 100) {
            charCount++;
        }
    }
    fen[charCount++] = ' ';

    // ===== FULL MOVE NUMBER =====
    snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->fullmoveNumber);
    if(position->fullmoveNumber >= 10) {
        charCount++;
        if(position->fullmoveNumber >= 100) {
            charCount++;
        }
    }
    fen[charCount++] = '\0';

    return charCount;
}

int toMinFen(char* fen, Position* position) {
    int charCount = 0;

    // ===== BOARD =====
    int rank = 7;
    int file = 0;
    int* emptyCount = 0;

    Bitboard empties = getEmptySquares(&(position->board));
    Bitboard bb;

    while(rank >= 0) {
        bb = index2bb(8 * rank + file);

        if(bb & empties) {
            emptyCount++;
        } else {
            if(emptyCount != 0) {
                snprintf(&fen[charCount++], 2, "%d", emptyCount);
                emptyCount = 0;
            }
            fen[charCount++] = bb2char(bb, &(position->board));
        }

        file++;
        if(file > 7) {
            if(emptyCount != 0) {
                snprintf(&fen[charCount++], 2, "%d", emptyCount);
                emptyCount = 0;
            }
            file = 0;
            rank--;
            fen[charCount++] = '/';
        }
    }
    fen[charCount - 1] = ' ';

    // ===== TO MOVE =====
    if(position->toMove == BLACK) {
        fen[charCount++] = 'b';
    } else {
        fen[charCount++] = 'w';
    }
    fen[charCount++] = ' ';

    // ===== CASTLING RIGHTS =====
    if(position->castlingRights == 0) {
        fen[charCount++] = '-';
    } else {
        if(position->castlingRights & CASTLE_KINGSIDE_WHITE) {
            fen[charCount++] = 'K';
        }
        if(position->castlingRights & CASTLE_QUEENSIDE_WHITE) {
            fen[charCount++] = 'Q';
        }
        if(position->castlingRights & CASTLE_KINGSIDE_BLACK) {
            fen[charCount++] = 'k';
        }
        if(position->castlingRights & CASTLE_QUEENSIDE_BLACK) {
            fen[charCount++] = 'q';
        }
    }
    fen[charCount++] = ' ';

    // ===== EN PASSANT =====
    if(position->epSquare == -1) {
        fen[charCount++] = '-';
    } else {
        fen[charCount++] = getFile(position->epSquare);
        fen[charCount++] = getRank(position->epSquare);
    }
    fen[charCount++] = '\0';

    return charCount;
}

void getMovelistGame(Game* game, char moves[]) {
    getInitialGame(game);

    for(int i = 0; i < strlen(moves) - 3; i += 5) {
        makeMove(game, parseMove(&moves[i]));
        if(moves[i + 5] == ' ') i++; // FIXME Queening
    }
}

// ========= UTILITY =========

BOOL fromInitial(Game* game) {
    if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0)
        return TRUE;
    else
        return FALSE;
}

Bitboard index2bb(int index) {
    Bitboard bb = 1;
    return bb << index;
}

int str2index(char* str) {
    int i, file_num = 0, rank_num = 0;
    for(i = 0; i < 8; i++) {
        if(str[0] == FILES[i]) file_num = i;
        if(str[1] == RANKS[i]) rank_num = i;
    }
    return 8 * rank_num + file_num;
}

Bitboard str2bb(char* str) {
    return index2bb(str2index(str));
}

BOOL isSet(Bitboard bb, int index) {
    if(bb & index2bb(index))
        return TRUE;
    else
        return FALSE;
}

Bitboard lsb(Bitboard bb) {
    int i;
    for(i = 0; i < NUM_SQUARES; i++) {
        Bitboard bit = index2bb(i);
        if(bb & bit) return bit;
    }
    return 0;
}

Bitboard msb(Bitboard bb) {
    int i;
    for(i = 0; i < NUM_SQUARES; i++) {
        Bitboard bit = index2bb(63 - i);
        if(bb & bit) return bit;
    }
    return 0;
}

int bb2index(Bitboard bb) {
    int i;
    for(i = 0; i < NUM_SQUARES; i++) {
        Bitboard bit = index2bb(i);
        if(bb & bit) return i;
    }
    return -1;
}

char* movelist2str(Game* game) {
    char* movestr = NULL;

    if(game->moveListLen == 0) {
        movestr = (char*)malloc(sizeof(char));
        movestr[0] = 0;
        return movestr;
    }

    movestr = (char*)malloc(5 * game->moveListLen);

    int i;
    for(i = 0; i < game->moveListLen; i++) {
        int leaving = getFrom(game->moveList[i]);
        int arriving = getTo(game->moveList[i]);
        movestr[5 * i] = getFile(leaving);
        movestr[5 * i + 1] = getRank(leaving);
        movestr[5 * i + 2] = getFile(arriving);
        movestr[5 * i + 3] = getRank(arriving);
        movestr[5 * i + 4] = ' ';
    }

    movestr[5 * game->moveListLen - 1] = 0;

    return movestr;
}

Move getLastMove(Game* game) {
    if(game->moveListLen == 0)
        return 0;
    else
        return game->moveList[game->moveListLen - 1];
}

BOOL startsWith(const char* str, const char* pre) {
    size_t lenpre = strlen(pre), lenstr = strlen(str);

    if(lenpre > lenstr) return FALSE;

    return strncmp(pre, str, lenpre) == 0 ? TRUE : FALSE;
}

int countBookOccurrences(Game* game) {
    FILE* fp = fopen("book.txt", "r");

    if(fp == NULL) return 0;

    char* moveList = movelist2str(game);
    char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN);
    int charPos = 0, occurrences = 0;

    while(TRUE) {
        char ch = getc(fp);
        line[charPos++] = ch;

        if(ch == '\n' || ch == EOF) {
            line[charPos - 1] = '\0';

            if(startsWith(line, moveList) && strlen(line) > strlen(moveList) + 4) {
                occurrences++;
            }

            if(ch == EOF) break;

            charPos = 0;
        }
    }

    fclose(fp);
    free(line);
    free(moveList);

    return occurrences;
}

Move getBookMove(Game* game) {
    Move move = 0;
    int moveNum = rand() % countBookOccurrences(game);

    FILE* fp = fopen("book.txt", "r");

    if(fp == NULL) return 0;

    char* moveList = movelist2str(game);
    char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN);
    int charPos = 0, occurrences = 0;

    while(TRUE) {
        char ch = getc(fp);
        line[charPos++] = ch;

        if(ch == '\n') {
            line[charPos] = '\0';

            if(startsWith(line, moveList)) {
                if(occurrences == moveNum) {
                    int ind = game->moveListLen * 5;
                    move = parseMove(&line[ind]);
                    break;
                }
                occurrences++;
            }

            charPos = 0;
        }
    }

    fclose(fp);
    free(line);
    free(moveList);

    return move;
}

char getFile(int position) {
    int file = position % 8;
    return FILES[file];
}

char getRank(int position) {
    int rank = (int)(position / 8);
    return RANKS[rank];
}

Move generateMove(int leavingSquare, int arrivingSquare) {
    int leaving = (leavingSquare << 8);
    int arriving = arrivingSquare;
    return (Move)(leaving + arriving);
}

int getFrom(Move move) {
    return (move >> 8) & 0xFF;
}

int getTo(Move move) {
    return move & 0xFF;
}

int bb2piece(Bitboard position, Board* board) {
    if(position & board->whitePawns) return WHITE | PAWN;
    if(position & board->whiteKnights) return WHITE | KNIGHT;
    if(position & board->whiteBishops) return WHITE | BISHOP;
    if(position & board->whiteRooks) return WHITE | ROOK;
    if(position & board->whiteQueens) return WHITE | QUEEN;
    if(position & board->whiteKing) return WHITE | KING;
    if(position & board->blackPawns) return BLACK | PAWN;
    if(position & board->blackKnights) return BLACK | KNIGHT;
    if(position & board->blackBishops) return BLACK | BISHOP;
    if(position & board->blackRooks) return BLACK | ROOK;
    if(position & board->blackQueens) return BLACK | QUEEN;
    if(position & board->blackKing) return BLACK | KING;

    return EMPTY;
}

char bb2char(Bitboard position, Board* board) {
    if(position & board->whitePawns) return 'P';
    if(position & board->whiteKnights) return 'N';
    if(position & board->whiteBishops) return 'B';
    if(position & board->whiteRooks) return 'R';
    if(position & board->whiteQueens) return 'Q';
    if(position & board->whiteKing) return 'K';
    if(position & board->blackPawns) return 'p';
    if(position & board->blackKnights) return 'n';
    if(position & board->blackBishops) return 'b';
    if(position & board->blackRooks) return 'r';
    if(position & board->blackQueens) return 'q';
    if(position & board->blackKing) return 'k';

    return '?';
}

char* bb2str(Bitboard position, Board* board) {
    if((position & board->whitePawns) | (position & board->blackPawns)) return "Pawn";
    if((position & board->whiteKnights) | (position & board->blackKnights)) return "Knight";
    if((position & board->whiteBishops) | (position & board->blackBishops)) return "Bishop";
    if((position & board->whiteRooks) | (position & board->blackRooks)) return "Rook";
    if((position & board->whiteQueens) | (position & board->blackQueens)) return "Queen";
    if((position & board->whiteKing) | (position & board->blackKing)) return "King";

    return "?";
}

void printBitboard(Bitboard bitboard) {
    int rank, file;

    printf("\n");
    for(rank = 0; rank < 8; rank++) {
        printf("%d", 8 - rank);
        for(file = 0; file < 8; file++) {
            if(bitboard >> (file + (7 - rank) * 8) & 1) {
                printf(" #");
            } else {
                printf(" .");
            }
        }
        printf("\n");
    }
    printf("  a b c d e f g h\n");
    fflush(stdout);
}

char getPieceChar(Bitboard position, Board* board) {
    if(position & board->whiteKing) return 'K';
    if(position & board->whiteQueens) return 'Q';
    if(position & board->whiteRooks) return 'R';
    if(position & board->whiteKnights) return 'N';
    if(position & board->whiteBishops) return 'B';
    if(position & board->whitePawns) return 'P';
    if(position & board->blackKing) return 'k';
    if(position & board->blackQueens) return 'q';
    if(position & board->blackRooks) return 'r';
    if(position & board->blackKnights) return 'n';
    if(position & board->blackBishops) return 'b';
    if(position & board->blackPawns) return 'p';
    return '.';
}

void printBoard(Board* board) {
    int rank, file;

    printf("\n");
    for(rank = 0; rank < 8; rank++) {
        printf("%d", 8 - rank);
        for(file = 0; file < 8; file++) {
            printf(" %c", getPieceChar((FILES_BB[file] & RANKS_BB[7 - rank]), board));
        }
        printf("\n");
    }
    printf("  a b c d e f g h\n");

    fflush(stdout);
}

void printGame(Game* game) {
    // printf("Game -> %p (%lu)", game, sizeof(*game));
    // printBoard(&(game->position.board));
    // printf("board -> %p (%lu)\n", &(game->position.board), sizeof(game->position.board));
    // printf("toMove = %d -> %p (%lu)\n", game->position.toMove, &game->position.toMove, sizeof(game->position.toMove));
    // printf("ep = %d -> %p (%lu)\n", game->position.epSquare, &game->position.epSquare, sizeof(game->position.epSquare));
    // printf("castle rights = %d -> %p (%lu)\n", game->position.castlingRights, &game->position.castlingRights, sizeof(game->position.castlingRights));
    // printf("half clock = %d -> %p (%lu)\n", game->position.halfmoveClock, &game->position.halfmoveClock, sizeof(game->position.halfmoveClock));
    // printf("full num = %d -> %p (%lu)\n", game->position.fullmoveNumber, &game->position.fullmoveNumber, sizeof(game->position.fullmoveNumber));

    // printf("moveListLen = %d -> %p (%lu)\n", game->moveListLen, &game->moveListLen, sizeof(game->moveListLen));
    // printf("moveList -> %p (%lu)\n", game->moveList, sizeof(game->moveList));
    // printf("positionHistory -> %p (%lu)\n", game->positionHistory, sizeof(game->positionHistory));
    // fflush(stdout);
}

Bitboard not(Bitboard bb) {
    return ~bb & ALL_SQUARES;
}

char opponent(char color) {
    switch(color) {
    case WHITE:
        return BLACK;
    case BLACK:
        return WHITE;
    }
    return -1;
}

int countBits(Bitboard bb) {
    int bitCount = 0;
    Bitboard position = 1;

    for(int i = 0; i < NUM_SQUARES; i++) {
        if(position & bb) {
            bitCount++;
        }
        position = position << 1;
    }

    return bitCount;
}

void sortNodes(Node* sortedNodes, Node* nodes, int len, char color) {
    Node nodeBuffer[len];

    int i, j;
    BOOL sorted;
    for(i = 0; i < len; i++) {
        sorted = FALSE;

        for(j = 0; j < i; j++) {
            if((color == WHITE && nodes[i].score > sortedNodes[j].score) ||
               (color == BLACK && nodes[i].score < sortedNodes[j].score)) {
                sorted = TRUE;
                memcpy(nodeBuffer, &sortedNodes[j], (i - j) * sizeof(Node));
                memcpy(&sortedNodes[j + 1], nodeBuffer, (i - j) * sizeof(Node));
                sortedNodes[j] = nodes[i];
                break;
            }
        }

        if(sorted == FALSE) {
            sortedNodes[i] = nodes[i];
        }
    }
}

void printMove(Move move) {
    printf(
        "%c%c to %c%c",
        getFile(getFrom(move)),
        getRank(getFrom(move)),
        getFile(getTo(move)),
        getRank(getTo(move)));
}

void printFullMove(Move move, Board* board) {
    Bitboard leavingBB = index2bb(getFrom(move));
    printf("%s from ", bb2str(leavingBB, board));
    printMove(move);
}

void printLegalMoves(Position* position) {
    int i;
    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = legalMoves(moves, position, position->toMove);
    for(i = 0; i < moveCount; i++) {
        printf("%2d. ", i + 1);
        printFullMove(moves[i], &(position->board));
        //        printMove(moves[i]);
        printf("\n");
    }
    fflush(stdout);
}

void printNode(Node node) {
    printMove(node.move);
    printf(": %d", node.score);
}

void getTimestamp(char* timestamp) {
    time_t timer;
    struct tm* tm_info;

    time(&timer);
    tm_info = localtime(&timer);

    strftime(timestamp, 20, "%Y-%m-%d_%H.%M.%S", tm_info);
}

void dumpContent(Game* game) {
    char* movelist = movelist2str(game);

    char filename[50];
    sprintf(filename, "chess_game_");
    getTimestamp(&filename[strlen(filename)]);
    sprintf(&filename[strlen(filename)], ".txt");

    FILE* file = fopen(filename, "w+");

    fprintf(file, "movelist = %s\nposition history:\n", movelist);

    int i;
    for(i = 0; i < game->moveListLen + 1; i++) fprintf(file, "%s\n", game->positionHistory[i]);

    free(movelist);
    fclose(file);

    printf("Dumped game content to: %s\n", filename);
    fflush(stdout);
}

void dumpPGN(Game* game, char color, BOOL hasAI) {
    char filename[50];
    sprintf(filename, "chess_game_");
    getTimestamp(&filename[strlen(filename)]);
    sprintf(&filename[strlen(filename)], ".pgn");

    FILE* file = fopen(filename, "w+");

    char date[12];
    time_t timer;
    struct tm* tm_info;
    time(&timer);
    tm_info = localtime(&timer);
    strftime(date, 11, "%Y.%m.%d", tm_info);

    fprintf(file, "[Event \"Casual Game\"]\n");
    fprintf(file, "[Site \"?\"]\n");
    fprintf(file, "[Date \"%s\"]\n", date);
    fprintf(file, "[Round \"-\"]\n");

    if(hasAI) {
        if(color == WHITE) {
            fprintf(file, "[White \"%s\"]\n", HUMAN_NAME);
            fprintf(file, "[Black \"%s\"]\n", ENGINE_NAME);
        } else {
            fprintf(file, "[White \"%s\"]\n", ENGINE_NAME);
            fprintf(file, "[Black \"%s\"]\n", HUMAN_NAME);
        }
    } else {
        fprintf(file, "[White \"Unknown Human Player\"]\n");
        fprintf(file, "[Black \"Unknown Human Player\"]\n");
    }

    if(hasGameEnded(&game->position)) {
        if(endNodeEvaluation(&game->position) == winScore(WHITE)) {
            fprintf(file, "[Result \"1-0\"]\n");
        } else if(endNodeEvaluation(&game->position) == winScore(BLACK)) {
            fprintf(file, "[Result \"0-1\"]\n");
        } else if(endNodeEvaluation(&game->position) == 0) {
            fprintf(file, "[Result \"1/2-1/2\"]\n");
        }
    } else {
        fprintf(file, "[Result \"*\"]\n");
    }

    if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0) {
        fprintf(file, "[Variant \"Standard\"]\n");
    } else {
        fprintf(file, "[Variant \"From Position\"]\n");
        fprintf(file, "[FEN \"%s\"]\n", game->positionHistory[0]);
    }

    fprintf(file, "[PlyCount \"%d\"]\n\n", game->moveListLen);

    int i;
    char ply[8];
    for(i = 0; i < game->moveListLen; i++) {
        if(i % 2 == 0) fprintf(file, "%d. ", 1 + (i / 2));
        move2str(ply, game, i);
        fprintf(file, "%s ", ply);
    }

    fclose(file);

    printf("Dumped game pgn to: %s\n", filename);
    fflush(stdout);
}

void move2str(char* str, Game* game, int moveNumber) {
    Position posBefore, posAfter;
    loadFen(&posBefore, game->positionHistory[moveNumber]);
    loadFen(&posAfter, game->positionHistory[moveNumber + 1]);
    Move move = game->moveList[moveNumber];
    int leavingSquare = getFrom(move);
    int arrivingSquare = getTo(move);

    Bitboard leavingBB = index2bb(leavingSquare);
    Bitboard arrivingBB = index2bb(arrivingSquare);

    int length = 0;
    if((leavingBB & (posBefore.board.whiteKing | posBefore.board.blackKing)) &&
       abs(leavingSquare - arrivingSquare) == 2) { // if castling
        if(arrivingBB & FILE_G) {
            sprintf(str, "O-O");
            length += 3;
        } else if(arrivingBB & FILE_C) {
            sprintf(str, "O-O-O");
            length += 5;
        }
    } else { // if not castling
        if(leavingBB & (posBefore.board.whitePawns | posBefore.board.blackPawns)) {
            if(arrivingBB & getOccupiedSquares(&(posBefore.board))) {
                str[length++] = getFile(leavingSquare);
            }
        } else {
            str[length++] = bb2char(leavingBB, &(posBefore.board));
        }

        if(isAmbiguous(&posBefore, move)) {
            if(countBits(getTwinPieces(leavingBB, &(posBefore.board)) & fileFilter(leavingBB)) ==
               1) {
                str[length++] = getFile(leavingSquare);
            } else {
                str[length++] = getRank(leavingSquare);
            }
        }

        if(bb2piece(arrivingBB, &(posBefore.board)) != EMPTY) {
            str[length++] = 'x';
        }

        str[length++] = getFile(arrivingSquare);
        str[length++] = getRank(arrivingSquare);
    }

    if(isCheckmate(&posAfter)) {
        str[length++] = '#';
    } else if(isCheck(&(posAfter.board), posAfter.toMove)) {
        str[length++] = '+';
    }

    str[length++] = 0;
}

BOOL isAmbiguous(Position* position, Move move) {
    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = legalMoves(moves, position, position->toMove);
    Bitboard targetBB = index2bb(getTo(move));
    Bitboard attackerBB = index2bb(getFrom(move));
    int attackerPiece = bb2piece(attackerBB, &(position->board));

    int attackerCount = 0;
    for(int i = 0; i < moveCount; i++) {
        Bitboard tgtBB = index2bb(getTo(moves[i]));
        Bitboard atkBB = index2bb(getFrom(moves[i]));

        if(attackerPiece == bb2piece(atkBB, &(position->board)) && (targetBB & tgtBB)) {
            attackerCount++;
        }
    }

    return attackerCount > 1;
}

unsigned long hashPosition(Position* position) {
    char fen[MAX_FEN_LEN];
    toMinFen(fen, position);

    unsigned long hash = 5381;
    int c, i = 0;

    while((c = fen[i++])) {
        hash = ((hash << 5) + hash) + c;
    }

    return hash;
}

void writeToHashFile(Position* position, int evaluation, int depth) {
    FILE* fp = fopen("hashfile", "a");

    if(fp == NULL) return;

    char fen[MAX_FEN_LEN];
    toMinFen(fen, position);

    fprintf(fp, "%08lx %d %d %s\n", hashPosition(position), depth, evaluation, fen);
    fclose(fp);
}

// ====== BOARD FILTERS ======

Bitboard getColoredPieces(Board* board, char color) {
    if(color == WHITE) {
        return board->whiteKing | board->whiteQueens | board->whiteRooks | board->whiteKnights |
               board->whiteBishops | board->whitePawns;
    } else {
        return board->blackKing | board->blackQueens | board->blackRooks | board->blackKnights |
               board->blackBishops | board->blackPawns;
    }
}

Bitboard getEmptySquares(Board* board) {
    return not(getOccupiedSquares(board));
}

Bitboard getOccupiedSquares(Board* board) {
    return getColoredPieces(board, WHITE) | getColoredPieces(board, BLACK);
}

Bitboard getTwinPieces(Bitboard position, Board* board) {
    if(position & board->whiteKing) return board->whiteKing;
    if(position & board->whiteQueens) return board->whiteQueens;
    if(position & board->whiteRooks) return board->whiteRooks;
    if(position & board->whiteKnights) return board->whiteKnights;
    if(position & board->whiteBishops) return board->whiteBishops;
    if(position & board->whitePawns) return board->whitePawns;

    if(position & board->blackKing) return board->blackKing;
    if(position & board->blackQueens) return board->blackQueens;
    if(position & board->blackRooks) return board->blackRooks;
    if(position & board->blackKnights) return board->blackKnights;
    if(position & board->blackBishops) return board->blackBishops;
    if(position & board->blackPawns) return board->blackPawns;

    return position;
}

Bitboard fileFilter(Bitboard positions) {
    Bitboard filter = 0;
    int i;

    for(i = 0; i < 8; i++)
        if(positions & FILES_BB[i]) filter |= FILES_BB[i];
    return filter;
}

Bitboard rankFilter(Bitboard positions) {
    Bitboard filter = 0;
    int i;

    for(i = 0; i < 8; i++)
        if(positions & RANKS_BB[i]) filter |= RANKS_BB[i];
    return filter;
}

// ======= DIRECTIONS ========

Bitboard east(Bitboard bb) {
    return (bb << 1) & 0xfefefefefefefefe; // not(FILE_A)
}

Bitboard west(Bitboard bb) {
    return (bb >> 1) & 0x7f7f7f7f7f7f7f7f; // not(FILE_H)
}

Bitboard north(Bitboard bb) {
    return (bb << 8) & 0xffffffffffffff00; // not(RANK_1)
}

Bitboard south(Bitboard bb) {
    return (bb >> 8) & 0x00ffffffffffffff; // not(RANK_8)
}

Bitboard NE(Bitboard bb) {
    return (bb << 9) & 0xfefefefefefefe00; // not(RANK_1 | FILE_A)
}

Bitboard NW(Bitboard bb) {
    return (bb << 7) & 0x7f7f7f7f7f7f7f00; // not(RANK_1 | FILE_H)
}

Bitboard SE(Bitboard bb) {
    return (bb >> 7) & 0x00fefefefefefefe; // not(RANK_8 | FILE_A)
}

Bitboard SW(Bitboard bb) {
    return (bb >> 9) & 0x007f7f7f7f7f7f7f; // not(RANK_8 | FILE_H)
}

Bitboard WNW(Bitboard moving_piece) {
    return (moving_piece << 6) & 0x3f3f3f3f3f3f3f00; // not(RANK_1 | FILE_G | FILE_H)
}

Bitboard ENE(Bitboard moving_piece) {
    return (moving_piece << 10) & 0xfcfcfcfcfcfcfc00; // not(RANK_1 | FILE_A | FILE_B)
}

Bitboard NNW(Bitboard moving_piece) {
    return (moving_piece << 15) & 0x7f7f7f7f7f7f0000; // not(RANK_1 | RANK_2 | FILE_H)
}

Bitboard NNE(Bitboard moving_piece) {
    return (moving_piece << 17) & 0xfefefefefefe0000; // not(RANK_1 | RANK_2 | FILE_A)
}

Bitboard ESE(Bitboard moving_piece) {
    return (moving_piece >> 6) & 0x00fcfcfcfcfcfcfc; // not(RANK_8| FILE_A | FILE_B)
}

Bitboard WSW(Bitboard moving_piece) {
    return (moving_piece >> 10) & 0x003f3f3f3f3f3f3f; // not(RANK_8 | FILE_G | FILE_H)
}

Bitboard SSE(Bitboard moving_piece) {
    return (moving_piece >> 15) & 0x0000fefefefefefe; // not(RANK_7 | RANK_8 | FILE_A)
}

Bitboard SSW(Bitboard moving_piece) {
    return (moving_piece >> 17) & 0x00007f7f7f7f7f7f; // not(RANK_7 | RANK_8 | FILE_H)
}

// ========== PAWN ===========

Bitboard getPawns(Board* board) {
    return board->whitePawns | board->blackPawns;
}

Bitboard pawnSimplePushes(Bitboard moving_piece, Board* board, char color) {
    switch(color) {
    case WHITE:
        return north(moving_piece) & getEmptySquares(board);
    case BLACK:
        return south(moving_piece) & getEmptySquares(board);
    }
    return 0;
}

Bitboard pawnDoublePushes(Bitboard moving_piece, Board* board, char color) {
    switch(color) {
    case WHITE:
        return north(pawnSimplePushes(moving_piece, board, color)) &
               (getEmptySquares(board) & RANK_4);
    case BLACK:
        return south(pawnSimplePushes(moving_piece, board, color)) &
               (getEmptySquares(board) & RANK_5);
    }
    return 0;
}

Bitboard pawnPushes(Bitboard moving_piece, Board* board, char color) {
    return pawnSimplePushes(moving_piece, board, color) |
           pawnDoublePushes(moving_piece, board, color);
}

Bitboard pawnEastAttacks(Bitboard moving_piece, Board* board, char color) {
    switch(color) {
    case WHITE:
        return NE(moving_piece);
    case BLACK:
        return SE(moving_piece);
    }
    return 0;
}

Bitboard pawnWestAttacks(Bitboard moving_piece, Board* board, char color) {
    switch(color) {
    case WHITE:
        return NW(moving_piece);
    case BLACK:
        return SW(moving_piece);
    }
    return 0;
}

Bitboard pawnAttacks(Bitboard moving_piece, Board* board, char color) {
    return pawnEastAttacks(moving_piece, board, color) |
           pawnWestAttacks(moving_piece, board, color);
}

Bitboard pawnSimpleCaptures(Bitboard moving_piece, Board* board, char color) {
    return pawnAttacks(moving_piece, board, color) & getColoredPieces(board, opponent(color));
}

Bitboard pawnEpCaptures(Bitboard moving_piece, Position* position, char color) {
    if(position->epSquare == -1) return 0;

    Bitboard valid_ep_square = 0;

    switch(color) {
    case WHITE:
        valid_ep_square = index2bb(position->epSquare) & RANK_6;
        break;
    case BLACK:
        valid_ep_square = index2bb(position->epSquare) & RANK_3;
        break;
    }

    return pawnAttacks(moving_piece, &(position->board), color) & valid_ep_square;
}

Bitboard pawnCaptures(Bitboard moving_piece, Position* position, char color) {
    return pawnSimpleCaptures(moving_piece, &(position->board), color) |
           pawnEpCaptures(moving_piece, position, color);
}

Bitboard pawnMoves(Bitboard moving_piece, Position* position, char color) {
    return pawnPushes(moving_piece, &(position->board), color) |
           pawnCaptures(moving_piece, position, color);
}

BOOL isDoublePush(int leaving, int arriving) {
    if((index2bb(leaving) & RANK_2) && (index2bb(arriving) & RANK_4)) return TRUE;
    if((index2bb(leaving) & RANK_7) && (index2bb(arriving) & RANK_5)) return TRUE;
    return FALSE;
}

char getEpSquare(int leaving) {
    if(index2bb(leaving) & RANK_2) return leaving + 8;
    if(index2bb(leaving) & RANK_7) return leaving - 8;
    return -1;
}

BOOL isDoubledPawn(Bitboard position, Board* board, char color) {
    if(color == WHITE) {
        if(countBits(board->whitePawns & fileFilter(position)) > 1) return TRUE;
    } else {
        if(countBits(board->blackPawns & fileFilter(position)) > 1) return TRUE;
    }

    return FALSE;
}

BOOL isIsolatedPawn(Bitboard position, Board* board, char color) {
    Bitboard sideFiles = fileFilter(east(position) | west(position));

    if(color == WHITE) {
        if(countBits(board->whitePawns & sideFiles) == 0) return TRUE;
    } else {
        if(countBits(board->blackPawns & sideFiles) == 0) return TRUE;
    }

    return FALSE;
}

BOOL isBackwardsPawn(Bitboard position, Board* board, char color) {
    Bitboard squaresFilter = east(position) | west(position);

    if(color == BLACK) {
        squaresFilter |= northRay(squaresFilter);
        if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE;
    } else {
        squaresFilter |= southRay(squaresFilter);
        if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE;
    }

    return FALSE;
}

BOOL isPassedPawn(Bitboard position, Board* board, char color) {
    Bitboard squaresFilter = 0;

    if(color == BLACK) {
        squaresFilter |= southRay(east(position)) | southRay(west(position)) | southRay(position);
        if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE;
    } else {
        squaresFilter |= northRay(east(position)) | northRay(west(position)) | northRay(position);
        if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE;
    }

    return FALSE;
}

BOOL isOpenFile(Bitboard position, Board* board) {
    if(countBits(getPawns(board) & fileFilter(position)) == 0) return TRUE;
    return FALSE;
}

BOOL isSemiOpenFile(Bitboard position, Board* board) {
    if(countBits(getPawns(board) & fileFilter(position)) == 1) return TRUE;
    return FALSE;
}

// ========== KNIGHT =========

Bitboard getKnights(Board* board) {
    return board->whiteKnights | board->blackKnights;
}

Bitboard knightAttacks(Bitboard moving_piece) {
    return NNE(moving_piece) | ENE(moving_piece) | NNW(moving_piece) | WNW(moving_piece) |
           SSE(moving_piece) | ESE(moving_piece) | SSW(moving_piece) | WSW(moving_piece);
}

Bitboard knightMoves(Bitboard moving_piece, Board* board, char color) {
    return knightAttacks(moving_piece) & not(getColoredPieces(board, color));
}

Bitboard knightFill(Bitboard moving_piece, int jumps) {
    Bitboard fill = moving_piece;
    int i;
    for(i = 0; i < jumps; i++) {
        fill |= knightAttacks(fill);
    }
    return fill;
}

int knightDistance(Bitboard leaving_square, Bitboard arriving_square) {
    Bitboard fill = leaving_square;
    int dist = 0;

    while((fill & arriving_square) == 0) {
        dist++;
        fill |= knightAttacks(fill);
    }
    return dist;
}

// ========== KING ===========

Bitboard getKing(Board* board, char color) {
    if(color == WHITE) {
        return board->whiteKing;
    } else {
        return board->blackKing;
    }
}

Bitboard kingAttacks(Bitboard moving_piece) {
    Bitboard kingAtks = moving_piece | east(moving_piece) | west(moving_piece);
    kingAtks |= north(kingAtks) | south(kingAtks);
    return kingAtks & not(moving_piece);
}

Bitboard kingMoves(Bitboard moving_piece, Board* board, char color) {
    return kingAttacks(moving_piece) & not(getColoredPieces(board, color));
}

BOOL canCastleKingside(Position* position, char color) {
    Bitboard emptyPositions = getEmptySquares(&(position->board));

    switch(color) {
    case WHITE:
        if((position->castlingRights & CASTLE_KINGSIDE_WHITE) &&
           (FILE_E & RANK_1 & position->board.whiteKing) && (FILE_F & RANK_1 & emptyPositions) &&
           (FILE_G & RANK_1 & emptyPositions) && (FILE_H & RANK_1 & position->board.whiteRooks) &&
           (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_F & RANK_1), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_G & RANK_1), &(position->board), opponent(color))))
            return TRUE;
        else
            return FALSE;

    case BLACK:
        if((position->castlingRights & CASTLE_KINGSIDE_BLACK) &&
           (FILE_E & RANK_8 & position->board.blackKing) && (FILE_F & RANK_8 & emptyPositions) &&
           (FILE_G & RANK_8 & emptyPositions) && (FILE_H & RANK_8 & position->board.blackRooks) &&
           (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_F & RANK_8), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_G & RANK_8), &(position->board), opponent(color))))
            return TRUE;
        else
            return FALSE;
    }
    return FALSE;
}

BOOL canCastleQueenside(Position* position, char color) {
    Bitboard emptyPositions = getEmptySquares(&(position->board));

    switch(color) {
    case WHITE:
        if((position->castlingRights & CASTLE_QUEENSIDE_WHITE) &&
           (FILE_A & RANK_1 & position->board.whiteRooks) && (FILE_B & RANK_1 & emptyPositions) &&
           (FILE_C & RANK_1 & emptyPositions) && (FILE_D & RANK_1 & emptyPositions) &&
           (FILE_E & RANK_1 & position->board.whiteKing) &&
           (!isAttacked((FILE_C & RANK_1), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_D & RANK_1), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color))))
            return TRUE;
        else
            return FALSE;

    case BLACK:
        if((position->castlingRights & CASTLE_QUEENSIDE_BLACK) &&
           (FILE_A & RANK_8 & position->board.blackRooks) && (FILE_B & RANK_8 & emptyPositions) &&
           (FILE_C & RANK_8 & emptyPositions) && (FILE_D & RANK_8 & emptyPositions) &&
           (FILE_E & RANK_8 & position->board.blackKing) &&
           (!isAttacked((FILE_C & RANK_8), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_D & RANK_8), &(position->board), opponent(color))) &&
           (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color))))
            return TRUE;
        else
            return FALSE;
    }
    return FALSE;
}

char removeCastlingRights(char original_rights, char removed_rights) {
    return (char)(original_rights & ~(removed_rights));
}

// ========== BISHOP =========

Bitboard getBishops(Board* board) {
    return board->whiteBishops | board->blackBishops;
}

Bitboard NE_ray(Bitboard bb) {
    int i;
    Bitboard ray = NE(bb);

    for(i = 0; i < 6; i++) {
        ray |= NE(ray);
    }

    return ray & ALL_SQUARES;
}

Bitboard SE_ray(Bitboard bb) {
    int i;
    Bitboard ray = SE(bb);

    for(i = 0; i < 6; i++) {
        ray |= SE(ray);
    }

    return ray & ALL_SQUARES;
}

Bitboard NW_ray(Bitboard bb) {
    int i;
    Bitboard ray = NW(bb);

    for(i = 0; i < 6; i++) {
        ray |= NW(ray);
    }

    return ray & ALL_SQUARES;
}

Bitboard SW_ray(Bitboard bb) {
    int i;
    Bitboard ray = SW(bb);

    for(i = 0; i < 6; i++) {
        ray |= SW(ray);
    }

    return ray & ALL_SQUARES;
}

Bitboard NE_attack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = lsb(NE_ray(single_piece) & getOccupiedSquares(board));
    if(blocker) {
        return NE_ray(single_piece) ^ NE_ray(blocker);
    } else {
        return NE_ray(single_piece);
    }
}

Bitboard NW_attack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = lsb(NW_ray(single_piece) & getOccupiedSquares(board));
    if(blocker) {
        return NW_ray(single_piece) ^ NW_ray(blocker);
    } else {
        return NW_ray(single_piece);
    }
}

Bitboard SE_attack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = msb(SE_ray(single_piece) & getOccupiedSquares(board));
    if(blocker) {
        return SE_ray(single_piece) ^ SE_ray(blocker);
    } else {
        return SE_ray(single_piece);
    }
}

Bitboard SW_attack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = msb(SW_ray(single_piece) & getOccupiedSquares(board));
    if(blocker) {
        return SW_ray(single_piece) ^ SW_ray(blocker);
    } else {
        return SW_ray(single_piece);
    }
}

Bitboard diagonalAttacks(Bitboard single_piece, Board* board, char color) {
    return NE_attack(single_piece, board, color) | SW_attack(single_piece, board, color);
}

Bitboard antiDiagonalAttacks(Bitboard single_piece, Board* board, char color) {
    return NW_attack(single_piece, board, color) | SE_attack(single_piece, board, color);
}

Bitboard bishopAttacks(Bitboard moving_pieces, Board* board, char color) {
    return diagonalAttacks(moving_pieces, board, color) |
           antiDiagonalAttacks(moving_pieces, board, color);
}

Bitboard bishopMoves(Bitboard moving_piece, Board* board, char color) {
    return bishopAttacks(moving_piece, board, color) & not(getColoredPieces(board, color));
}

// ========== ROOK ===========

Bitboard getRooks(Board* board) {
    return board->whiteRooks | board->blackRooks;
}

Bitboard northRay(Bitboard moving_pieces) {
    Bitboard ray_atks = north(moving_pieces);

    int i;
    for(i = 0; i < 6; i++) {
        ray_atks |= north(ray_atks);
    }

    return ray_atks & ALL_SQUARES;
}

Bitboard southRay(Bitboard moving_pieces) {
    Bitboard ray_atks = south(moving_pieces);

    int i;
    for(i = 0; i < 6; i++) {
        ray_atks |= south(ray_atks);
    }

    return ray_atks & ALL_SQUARES;
}

Bitboard eastRay(Bitboard moving_pieces) {
    Bitboard ray_atks = east(moving_pieces);

    int i;
    for(i = 0; i < 6; i++) {
        ray_atks |= east(ray_atks);
    }

    return ray_atks & ALL_SQUARES;
}

Bitboard westRay(Bitboard moving_pieces) {
    Bitboard ray_atks = west(moving_pieces);

    int i;
    for(i = 0; i < 6; i++) {
        ray_atks |= west(ray_atks);
    }

    return ray_atks & ALL_SQUARES;
}

Bitboard northAttack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = lsb(northRay(single_piece) & getOccupiedSquares(board));

    if(blocker)
        return northRay(single_piece) ^ northRay(blocker);
    else
        return northRay(single_piece);
}

Bitboard southAttack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = msb(southRay(single_piece) & getOccupiedSquares(board));

    if(blocker)
        return southRay(single_piece) ^ southRay(blocker);
    else
        return southRay(single_piece);
}

Bitboard fileAttacks(Bitboard single_piece, Board* board, char color) {
    return northAttack(single_piece, board, color) | southAttack(single_piece, board, color);
}

Bitboard eastAttack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = lsb(eastRay(single_piece) & getOccupiedSquares(board));

    if(blocker)
        return eastRay(single_piece) ^ eastRay(blocker);
    else
        return eastRay(single_piece);
}

Bitboard westAttack(Bitboard single_piece, Board* board, char color) {
    Bitboard blocker = msb(westRay(single_piece) & getOccupiedSquares(board));

    if(blocker)
        return westRay(single_piece) ^ westRay(blocker);
    else
        return westRay(single_piece);
}

Bitboard rankAttacks(Bitboard single_piece, Board* board, char color) {
    return eastAttack(single_piece, board, color) | westAttack(single_piece, board, color);
}

Bitboard rookAttacks(Bitboard moving_piece, Board* board, char color) {
    return fileAttacks(moving_piece, board, color) | rankAttacks(moving_piece, board, color);
}

Bitboard rookMoves(Bitboard moving_piece, Board* board, char color) {
    return rookAttacks(moving_piece, board, color) & not(getColoredPieces(board, color));
}

// ========== QUEEN ==========

Bitboard getQueens(Board* board) {
    return board->whiteQueens | board->blackQueens;
}

Bitboard queenAttacks(Bitboard moving_piece, Board* board, char color) {
    return rookAttacks(moving_piece, board, color) | bishopAttacks(moving_piece, board, color);
}

Bitboard queenMoves(Bitboard moving_piece, Board* board, char color) {
    return rookMoves(moving_piece, board, color) | bishopMoves(moving_piece, board, color);
}

// ======== MAKE MOVE ========

void clearPositions(Board* board, Bitboard positions) {
    board->whiteKing = board->whiteKing & not(positions);
    board->whiteQueens = board->whiteQueens & not(positions);
    board->whiteRooks = board->whiteRooks & not(positions);
    board->whiteKnights = board->whiteKnights & not(positions);
    board->whiteBishops = board->whiteBishops & not(positions);
    board->whitePawns = board->whitePawns & not(positions);
    board->blackKing = board->blackKing & not(positions);
    board->blackQueens = board->blackQueens & not(positions);
    board->blackRooks = board->blackRooks & not(positions);
    board->blackKnights = board->blackKnights & not(positions);
    board->blackBishops = board->blackBishops & not(positions);
    board->blackPawns = board->blackPawns & not(positions);
}

void movePiece(Board* board, Move move) {
    Bitboard leaving = index2bb(getFrom(move));
    Bitboard arriving = index2bb(getTo(move));

    if(leaving & board->whiteKing) {
        clearPositions(board, arriving | leaving);
        board->whiteKing = arriving;
    } else if(leaving & board->whiteQueens) {
        clearPositions(board, arriving | leaving);
        board->whiteQueens = board->whiteQueens | arriving;
    } else if(leaving & board->whiteRooks) {
        clearPositions(board, arriving | leaving);
        board->whiteRooks = board->whiteRooks | arriving;
    } else if(leaving & board->whiteKnights) {
        clearPositions(board, arriving | leaving);
        board->whiteKnights = board->whiteKnights | arriving;
    } else if(leaving & board->whiteBishops) {
        clearPositions(board, arriving | leaving);
        board->whiteBishops = board->whiteBishops | arriving;
    } else if(leaving & board->whitePawns) {
        clearPositions(board, arriving | leaving);
        board->whitePawns = board->whitePawns | arriving;
    } else if(leaving & board->blackKing) {
        clearPositions(board, arriving | leaving);
        board->blackKing = arriving;
    } else if(leaving & board->blackQueens) {
        clearPositions(board, arriving | leaving);
        board->blackQueens = board->blackQueens | arriving;
    } else if(leaving & board->blackRooks) {
        clearPositions(board, arriving | leaving);
        board->blackRooks = board->blackRooks | arriving;
    } else if(leaving & board->blackKnights) {
        clearPositions(board, arriving | leaving);
        board->blackKnights = board->blackKnights | arriving;
    } else if(leaving & board->blackBishops) {
        clearPositions(board, arriving | leaving);
        board->blackBishops = board->blackBishops | arriving;
    } else if(leaving & board->blackPawns) {
        clearPositions(board, arriving | leaving);
        board->blackPawns = board->blackPawns | arriving;
    }
}

void updatePosition(Position* newPosition, Position* position, Move move) {
    memcpy(newPosition, position, sizeof(Position));
    int leavingSquare = getFrom(move);
    int arrivingSquare = getTo(move);
    Bitboard leavingBB = index2bb(leavingSquare);
    Bitboard arrivingBB = index2bb(arrivingSquare);

    // ===== MOVE PIECE =====
    movePiece(&(newPosition->board), move);

    // ===== TO MOVE =====
    newPosition->toMove = opponent(position->toMove);

    // ===== MOVE COUNTS =====
    newPosition->halfmoveClock += 1;
    if(position->toMove == BLACK) {
        newPosition->fullmoveNumber += 1;
    }

    if(arrivingBB & getOccupiedSquares(&(position->board))) {
        newPosition->halfmoveClock = 0;
    }

    // ===== PAWNS =====
    newPosition->epSquare = -1;
    if((leavingBB & position->board.whitePawns) | (leavingBB & position->board.blackPawns)) {
        newPosition->halfmoveClock = 0;

        if(arrivingSquare == position->epSquare) {
            if(index2bb(position->epSquare) & RANK_3) {
                clearPositions(&(newPosition->board), index2bb((int)(position->epSquare + 8)));
            }

            if(index2bb(position->epSquare) & RANK_6) {
                clearPositions(&(newPosition->board), index2bb((int)(position->epSquare - 8)));
            }
        }

        if(isDoublePush(leavingSquare, arrivingSquare)) {
            newPosition->epSquare = getEpSquare(leavingSquare);
        }

        if(arrivingBB & (RANK_1 | RANK_8)) {
            clearPositions(&(newPosition->board), arrivingBB);

            if(position->toMove == WHITE) {
                newPosition->board.whiteQueens = newPosition->board.whiteQueens | arrivingBB;
            } else {
                newPosition->board.blackQueens = newPosition->board.blackQueens | arrivingBB;
            }
        }
    }

    // ===== CASTLING =====
    if(leavingSquare == str2index("a1")) {
        newPosition->castlingRights =
            removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_WHITE);
    } else if(leavingSquare == str2index("h1")) {
        newPosition->castlingRights =
            removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_WHITE);
    } else if(leavingSquare == str2index("a8")) {
        newPosition->castlingRights =
            removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_BLACK);
    } else if(leavingSquare == str2index("h8")) {
        newPosition->castlingRights =
            removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_BLACK);
    }

    if(leavingBB & position->board.whiteKing) {
        newPosition->castlingRights = removeCastlingRights(
            newPosition->castlingRights, (CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE));
        if(leavingSquare == str2index("e1")) {
            if(arrivingSquare == str2index("g1"))
                movePiece(&(newPosition->board), generateMove(str2index("h1"), str2index("f1")));

            if(arrivingSquare == str2index("c1"))
                movePiece(&(newPosition->board), generateMove(str2index("a1"), str2index("d1")));
        }
    } else if(leavingBB & position->board.blackKing) {
        newPosition->castlingRights = removeCastlingRights(
            newPosition->castlingRights, CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK);
        if(leavingSquare == str2index("e8")) {
            if(arrivingSquare == str2index("g8"))
                movePiece(&(newPosition->board), generateMove(str2index("h8"), str2index("f8")));

            if(arrivingSquare == str2index("c8"))
                movePiece(&(newPosition->board), generateMove(str2index("a8"), str2index("d8")));
        }
    }
}

void makeMove(Game* game, Move move) {
    Position newPosition;
    updatePosition(&newPosition, &(game->position), move);
    memcpy(&(game->position), &newPosition, sizeof(Position));

    game->moveListLen += 1;

    // ===== MOVE LIST =====
    game->moveList[game->moveListLen - 1] = move;

    // ===== POSITION HISTORY =====
    toFen(game->positionHistory[game->moveListLen], &game->position);
}

void unmakeMove(Game* game) {
    Position newPosition;
    if(game->moveListLen >= 1) {
        loadFen(&newPosition, game->positionHistory[game->moveListLen - 1]);
        memcpy(&(game->position), &newPosition, sizeof(Position));

        game->moveList[game->moveListLen - 1] = 0;
        memset(game->positionHistory[game->moveListLen], 0, MAX_FEN_LEN * sizeof(char));

        game->moveListLen -= 1;
    } else { // return to initial game
        loadFen(&newPosition, game->positionHistory[0]);
        memcpy(&(game->position), &newPosition, sizeof(Position));

        game->moveListLen = 0;
        memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int));
        memset(&game->positionHistory[1], 0, (MAX_PLYS_PER_GAME - 1) * MAX_FEN_LEN * sizeof(char));
    }
}

// ======== MOVE GEN =========

Bitboard getMoves(Bitboard movingPiece, Position* position, char color) {
    if((movingPiece & position->board.whitePawns) | (movingPiece & position->board.blackPawns))
        return pawnMoves(movingPiece, position, color);
    if((movingPiece & position->board.whiteKnights) | (movingPiece & position->board.blackKnights))
        return knightMoves(movingPiece, &(position->board), color);
    if((movingPiece & position->board.whiteBishops) | (movingPiece & position->board.blackBishops))
        return bishopMoves(movingPiece, &(position->board), color);
    if((movingPiece & position->board.whiteRooks) | (movingPiece & position->board.blackRooks))
        return rookMoves(movingPiece, &(position->board), color);
    if((movingPiece & position->board.whiteQueens) | (movingPiece & position->board.blackQueens))
        return queenMoves(movingPiece, &(position->board), color);
    if((movingPiece & position->board.whiteKing) | (movingPiece & position->board.blackKing))
        return kingMoves(movingPiece, &(position->board), color);

    return 0;
}

int pseudoLegalMoves(Move* moves, Position* position, char color) {
    int leavingSquare, arrivingSquare, moveCount = 0;
    Bitboard leavingBB = 1;
    Bitboard attackers = getColoredPieces(&(position->board), color);

    for(leavingSquare = 0; leavingSquare < NUM_SQUARES; leavingSquare++) {
        if(leavingBB & attackers) {
            Bitboard targets = getMoves(leavingBB, position, color);

            for(arrivingSquare = 0; arrivingSquare < NUM_SQUARES; arrivingSquare++) {
                if(isSet(targets, arrivingSquare)) {
                    moves[moveCount++] = generateMove(leavingSquare, arrivingSquare);
                }
            }

            if((leavingBB & position->board.whiteKing) | (leavingBB & position->board.blackKing)) {
                if(canCastleKingside(position, color)) {
                    moves[moveCount++] = generateMove(leavingSquare, leavingSquare + 2);
                }
                if(canCastleQueenside(position, color)) {
                    moves[moveCount++] = generateMove(leavingSquare, leavingSquare - 2);
                }
            }
        }
        leavingBB <<= 1;
    }

    return moveCount;
}

Bitboard getAttacks(Bitboard movingPiece, Board* board, char color) {
    if((movingPiece & board->whitePawns) | (movingPiece & board->blackPawns))
        return pawnAttacks(movingPiece, board, color);
    if((movingPiece & board->whiteKnights) | (movingPiece & board->blackKnights))
        return knightAttacks(movingPiece);
    if((movingPiece & board->whiteBishops) | (movingPiece & board->blackBishops))
        return bishopAttacks(movingPiece, board, color);
    if((movingPiece & board->whiteRooks) | (movingPiece & board->blackRooks))
        return rookAttacks(movingPiece, board, color);
    if((movingPiece & board->whiteQueens) | (movingPiece & board->blackQueens))
        return queenAttacks(movingPiece, board, color);
    if((movingPiece & board->whiteKing) | (movingPiece & board->blackKing))
        return kingAttacks(movingPiece);

    return 0;
}

int countAttacks(Bitboard target, Board* board, char color) {
    int i, attackCount = 0;

    Bitboard attackers = getColoredPieces(board, color);
    Bitboard position = 1;

    for(i = 0; i < NUM_SQUARES; i++) {
        if(position & attackers) {
            if(getAttacks(position, board, color) & target) {
                attackCount += 1;
            }
        }
        position = position << 1;
    }

    return attackCount;
}

BOOL isAttacked(Bitboard target, Board* board, char color) {
    int i = 0;

    Bitboard attackers = getColoredPieces(board, color);
    Bitboard position = 1;

    for(i = 0; i < NUM_SQUARES; i++) {
        if(position & attackers) {
            if(getAttacks(position, board, color) & target) {
                return TRUE;
            }
        }
        position = position << 1;
    }

    return FALSE;
}

BOOL isCheck(Board* board, char color) {
    return isAttacked(getKing(board, color), board, opponent(color));
}

BOOL isLegalMove(Position* position, Move move) {
    Position newPosition;
    updatePosition(&newPosition, position, move);
    if(isCheck(&(newPosition.board), position->toMove)) return FALSE;
    return TRUE;
}

int legalMoves(Move* legalMoves, Position* position, char color) {
    int i, legalCount = 0;

    Move pseudoMoves[MAX_BRANCHING_FACTOR];
    int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color);

    for(i = 0; i < pseudoCount; i++) {
        if(isLegalMove(position, pseudoMoves[i])) {
            legalMoves[legalCount++] = pseudoMoves[i];
        }
    }

    return legalCount;
}

int legalMovesCount(Position* position, char color) {
    int i, legalCount = 0;

    Move pseudoMoves[MAX_BRANCHING_FACTOR];
    int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color);

    for(i = 0; i < pseudoCount; i++) {
        if(isLegalMove(position, pseudoMoves[i])) {
            legalCount++;
        }
    }

    return legalCount;
}

int staticOrderLegalMoves(Move* orderedLegalMoves, Position* position, char color) {
    Move moves[MAX_BRANCHING_FACTOR];
    int legalCount = legalMoves(moves, position, color);

    Position newPosition;
    Node nodes[legalCount], orderedNodes[legalCount];

    int i;
    for(i = 0; i < legalCount; i++) {
        updatePosition(&newPosition, position, moves[i]);
        nodes[i] = (Node){.move = moves[i], .score = staticEvaluation(&newPosition)};
    }

    sortNodes(orderedNodes, nodes, legalCount, color);

    for(i = 0; i < legalCount; i++) {
        orderedLegalMoves[i] = orderedNodes[i].move;
    }

    return legalCount;
}

int legalCaptures(Move* legalCaptures, Position* position, char color) {
    int i, captureCount = 0;

    Move moves[MAX_BRANCHING_FACTOR];
    int legalCount = legalMoves(moves, position, color);

    for(i = 0; i < legalCount; i++) {
        int arrivingSquare = getTo(moves[i]);
        if(index2bb(arrivingSquare) & getColoredPieces(&(position->board), opponent(color))) {
            legalCaptures[captureCount++] = moves[i];
        }
    }

    return captureCount;
}

// ====== GAME CONTROL =======

BOOL isCheckmate(Position* position) {
    if(isCheck(&(position->board), position->toMove) &&
       legalMovesCount(position, position->toMove) == 0)
        return TRUE;
    else
        return FALSE;
}

BOOL isStalemate(Position* position) {
    if(!isCheck(&(position->board), position->toMove) &&
       legalMovesCount(position, position->toMove) == 0)
        return TRUE;
    else
        return FALSE;
}

BOOL hasInsufficientMaterial(Board* board) {
    int pieceCount = countBits(getOccupiedSquares(board));

    if(pieceCount <= 3) {
        if(pieceCount == 2 || getKnights(board) != 0 || getBishops(board) != 0) return TRUE;
    }

    return FALSE;
}

BOOL isEndgame(Board* board) {
    if(countBits(getOccupiedSquares(board)) <= ENDGAME_PIECE_COUNT) return TRUE;
    return FALSE;
}

BOOL isOver75MovesRule(Position* position) {
    if(position->halfmoveClock >= 150)
        return TRUE;
    else
        return FALSE;
}

BOOL hasGameEnded(Position* position) {
    if(isCheckmate(position) || isStalemate(position) ||
       hasInsufficientMaterial(&(position->board)) || isOver75MovesRule(position))
        return TRUE;
    else
        return FALSE;
}

void printOutcome(Position* position) {
    if(isCheckmate(position) && position->toMove == BLACK) printf("WHITE wins!\n");
    if(isCheckmate(position) && position->toMove == WHITE) printf("BLACK wins!\n");
    if(isStalemate(position)) printf("Draw by stalemate!\n");
    if(hasInsufficientMaterial(&(position->board))) printf("Draw by insufficient material!\n");
    if(isOver75MovesRule(position)) printf("Draw by 75 move rule!\n");
    fflush(stdout);
}

// ========== EVAL ===========

int winScore(char color) {
    if(color == WHITE) return 10 * PIECE_VALUES[KING];
    if(color == BLACK) return -10 * PIECE_VALUES[KING];
    return 0;
}

int materialSum(Board* board, char color) {
    if(color == WHITE) {
        return PIECE_VALUES[PAWN] * countBits(board->whitePawns) +
               PIECE_VALUES[KNIGHT] * countBits(board->whiteKnights) +
               PIECE_VALUES[BISHOP] * countBits(board->whiteBishops) +
               PIECE_VALUES[ROOK] * countBits(board->whiteRooks) +
               PIECE_VALUES[QUEEN] * countBits(board->whiteQueens);
    } else {
        return PIECE_VALUES[PAWN] * countBits(board->blackPawns) +
               PIECE_VALUES[KNIGHT] * countBits(board->blackKnights) +
               PIECE_VALUES[BISHOP] * countBits(board->blackBishops) +
               PIECE_VALUES[ROOK] * countBits(board->blackRooks) +
               PIECE_VALUES[QUEEN] * countBits(board->blackQueens);
    }
}

int materialBalance(Board* board) {
    return materialSum(board, WHITE) - materialSum(board, BLACK);
}

int positionalBonus(Board* board, char color) {
    int bonus = 0;
    Bitboard positionBB;
    Bitboard coloredPieces = getColoredPieces(board, color);

    int i;
    for(i = 0; i < NUM_SQUARES; i++) {
        positionBB = index2bb(i);

        if(positionBB & coloredPieces) {
            if((positionBB & board->whitePawns) | (positionBB & board->blackPawns)) {
                if(color == WHITE) {
                    bonus += PAWN_BONUS[i];
                } else {
                    bonus += PAWN_BONUS[FLIP_VERTICAL[i]];
                }

                if(isDoubledPawn(positionBB, board, color)) {
                    bonus -= DOUBLED_PAWN_PENALTY / 2;
                }
                if(isPassedPawn(positionBB, board, color)) {
                    bonus += PASSED_PAWN_BONUS;
                }

                if(isIsolatedPawn(positionBB, board, color)) {
                    bonus -= ISOLATED_PAWN_PENALTY;
                } else if(isBackwardsPawn(positionBB, board, color)) {
                    bonus -= BACKWARDS_PAWN_PENALTY;
                }
            } else if((positionBB & board->whiteKnights) | (positionBB & board->blackKnights)) {
                if(color == WHITE) {
                    bonus += KNIGHT_BONUS[i];
                } else {
                    bonus += KNIGHT_BONUS[FLIP_VERTICAL[i]];
                }
            } else if((positionBB & board->whiteBishops) | (positionBB & board->blackBishops)) {
                if(color == WHITE) {
                    bonus += BISHOP_BONUS[i];
                } else {
                    bonus += BISHOP_BONUS[FLIP_VERTICAL[i]];
                }
            } else if((positionBB & board->whiteRooks) | (positionBB & board->blackRooks)) {
                if(isOpenFile(positionBB, board)) {
                    bonus += ROOK_OPEN_FILE_BONUS;
                } else if(isSemiOpenFile(positionBB, board)) {
                    bonus += ROOK_SEMI_OPEN_FILE_BONUS;
                }

                if(color == WHITE) {
                    if(positionBB & RANK_7) {
                        bonus += ROOK_ON_SEVENTH_BONUS;
                    }
                } else {
                    if(positionBB & RANK_2) {
                        bonus += ROOK_ON_SEVENTH_BONUS;
                    }
                }
            } else if((positionBB & board->whiteKing) | (positionBB & board->blackKing)) {
                if(isEndgame(board)) {
                    if(color == WHITE) {
                        bonus += KING_ENDGAME_BONUS[i];
                    } else {
                        bonus += KING_ENDGAME_BONUS[FLIP_VERTICAL[i]];
                    }
                } else {
                    if(color == WHITE) {
                        bonus += KING_BONUS[i];
                    } else {
                        bonus += KING_BONUS[FLIP_VERTICAL[i]];
                    }
                }
            }
        }
    }

    return bonus;
}

int positionalBalance(Board* board) {
    return positionalBonus(board, WHITE) - positionalBonus(board, BLACK);
}

int endNodeEvaluation(Position* position) {
    if(isCheckmate(position)) {
        return winScore(opponent(position->toMove));
    }
    if(isStalemate(position) || hasInsufficientMaterial(&(position->board)) ||
       isOver75MovesRule(position)) {
        return 0;
    }
    return 0;
}

int staticEvaluation(Position* position) {
    if(hasGameEnded(position))
        return endNodeEvaluation(position);
    else
        return materialBalance(&(position->board)) + positionalBalance(&(position->board));
}

int getPieceValue(Bitboard position, Board* board) {
    if((position & board->whitePawns) | (position & board->blackPawns)) return PIECE_VALUES[PAWN];
    if((position & board->whiteKnights) | (position & board->blackKnights))
        return PIECE_VALUES[KNIGHT];
    if((position & board->whiteBishops) | (position & board->blackBishops))
        return PIECE_VALUES[BISHOP];
    if((position & board->whiteRooks) | (position & board->blackRooks)) return PIECE_VALUES[ROOK];
    if((position & board->whiteQueens) | (position & board->blackQueens))
        return PIECE_VALUES[QUEEN];
    if((position & board->whiteKing) | (position & board->blackKing)) return PIECE_VALUES[KING];
    return 0;
}

int getCaptureSequence(Move* captures, Position* position, int targetSquare) {
    Move allCaptures[MAX_BRANCHING_FACTOR], targetCaptures[MAX_ATTACKING_PIECES];
    int captureCount = legalCaptures(allCaptures, position, position->toMove);
    int i, j, targetCount = 0;

    for(i = 0; i < captureCount; i++) {
        if(getTo(allCaptures[i]) == targetSquare) {
            targetCaptures[targetCount++] = allCaptures[i];
        }
    }

    Move captureBuffer[targetCount];

    BOOL sorted;
    for(i = 0; i < targetCount; i++) {
        sorted = FALSE;
        int pieceValue = getPieceValue(index2bb(getFrom(targetCaptures[i])), &(position->board));

        for(j = 0; j < i; j++) {
            int sortedPieceValue =
                getPieceValue(index2bb(getFrom(captures[j])), &(position->board));

            if(pieceValue < sortedPieceValue) {
                sorted = TRUE;
                memcpy(captureBuffer, &captures[j], (i - j) * sizeof(Move));
                memcpy(&captures[j + 1], captureBuffer, (i - j) * sizeof(Move));
                captures[j] = targetCaptures[i];
                break;
            }
        }

        if(sorted == FALSE) {
            captures[i] = targetCaptures[i];
        }
    }

    return targetCount;
}

int staticExchangeEvaluation(Position* position, int targetSquare) {
    Move captures[MAX_ATTACKING_PIECES];
    int attackCount = getCaptureSequence(captures, position, targetSquare);
    int value = 0;

    if(attackCount > 0) {
        Position newPosition;
        updatePosition(&newPosition, position, captures[0]);
        int pieceValue = getPieceValue(index2bb(targetSquare), &(position->board));
        value = pieceValue - staticExchangeEvaluation(&newPosition, targetSquare);
    }

    return value > 0 ? value : 0;
}

int quiescenceEvaluation(Position* position) {
    int staticScore = staticEvaluation(position);

    if(hasGameEnded(position)) return staticScore;

    Move captures[MAX_BRANCHING_FACTOR];
    int captureCount = legalCaptures(captures, position, position->toMove);

    if(captureCount == 0) {
        return staticScore;
    } else {
        Position newPosition;
        int i, bestScore = staticScore;

        for(i = 0; i < captureCount; i++) {
            if(staticExchangeEvaluation(position, getTo(captures[i])) <= 0) continue;

            updatePosition(&newPosition, position, captures[i]);
            int score = quiescenceEvaluation(&newPosition);

            if((position->toMove == WHITE && score > bestScore) ||
               (position->toMove == BLACK && score < bestScore)) {
                bestScore = score;
            }
        }

        return bestScore;
    }
}

// ========= SEARCH ==========

Node staticSearch(Position* position) {
    int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX;
    Move bestMove = 0;

    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = legalMoves(moves, position, position->toMove);

    Position newPosition;
    int i;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, moves[i]);
        int score = staticEvaluation(&newPosition);

        if(score == winScore(position->toMove)) {
            return (Node){.move = moves[i], .score = score};
        }

        if((position->toMove == WHITE && score > bestScore) ||
           (position->toMove == BLACK && score < bestScore)) {
            bestScore = score;
            bestMove = moves[i];
        }
    }

    return (Node){.move = bestMove, .score = bestScore};
}

Node quiescenceSearch(Position* position) {
    int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX;
    Move bestMove = 0;

    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = legalMoves(moves, position, position->toMove);

    Position newPosition;
    int i;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, moves[i]);
        int score = quiescenceEvaluation(&newPosition);

        if(score == winScore(position->toMove)) {
            return (Node){.move = moves[i], .score = score};
        }

        if((position->toMove == WHITE && score > bestScore) ||
           (position->toMove == BLACK && score < bestScore)) {
            bestScore = score;
            bestMove = moves[i];
        }
    }

    return (Node){.move = bestMove, .score = bestScore};
}

Node alphaBeta(Position* position, char depth, int alpha, int beta) {
    if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)};

    if(depth == 1) return staticSearch(position);

    // Mate in 1
    Node staticNode = staticSearch(position);
    if(staticNode.score == winScore(position->toMove)) return staticNode;

    Move bestMove = 0;

    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = staticOrderLegalMoves(moves, position, position->toMove);

    Position newPosition;
    int i;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, moves[i]);

        int score = alphaBeta(&newPosition, depth - 1, alpha, beta).score;

        if(score == winScore(position->toMove)) {
            return (Node){.move = moves[i], .score = score};
        }

        if(position->toMove == WHITE && score > alpha) {
            alpha = score;
            bestMove = moves[i];
        } else if(position->toMove == BLACK && score < beta) {
            beta = score;
            bestMove = moves[i];
        }

        if(alpha > beta) {
            break;
        }
    }

    return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta};
}

int alphaBetaNodes(Node* sortedNodes, Position* position, char depth) {
    Node nodes[MAX_BRANCHING_FACTOR];
    Move moves[MAX_BRANCHING_FACTOR];
    int moveCount = legalMoves(moves, position, position->toMove);

    Position newPosition;
    int i;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, moves[i]);

        nodes[i].move = moves[i];
        nodes[i].score = depth > 1 ?
                             alphaBeta(&newPosition, depth - 1, INT32_MIN, INT32_MAX).score :
                             staticEvaluation(&newPosition);
    }

    sortNodes(sortedNodes, nodes, moveCount, position->toMove);

    return moveCount;
}

Node iterativeDeepeningAlphaBeta(Position* position, char depth, int alpha, int beta, BOOL verbose) {
    if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)};

    if(depth == 1) return quiescenceSearch(position);
    //        return staticSearch(position);

    // Mate in 1
    Node staticNode = staticSearch(position);
    if(staticNode.score == winScore(position->toMove)) return staticNode;

    Move bestMove = 0;

    if(verbose) {
        printf("Ordering moves...\n");
        fflush(stdout);
    }

    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    Position newPosition;
    int i;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, nodes[i].move);

        if(verbose) {
            printf("(Move %2d/%d) ", i + 1, moveCount);
            printFullMove(nodes[i].move, &(position->board));
            printf(" = ");
            fflush(stdout);
        }

        int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score;

        if(verbose) {
            printf("%.2f\n", score / 100.00);
            fflush(stdout);
        }

        if(score == winScore(position->toMove)) {
            return (Node){.move = nodes[i].move, .score = score};
        }

        if(position->toMove == WHITE && score > alpha) {
            alpha = score;
            bestMove = nodes[i].move;
        } else if(position->toMove == BLACK && score < beta) {
            beta = score;
            bestMove = nodes[i].move;
        }

        if(alpha > beta) {
            break;
        }
    }

    return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta};
}

Node pIDAB(Position* position, char depth, int* p_alpha, int* p_beta) {
    if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)};

    if(depth == 1) return quiescenceSearch(position);

    // Mate in 1
    Node staticNode = staticSearch(position);
    if(staticNode.score == winScore(position->toMove)) return staticNode;

    Move bestMove = 0;

    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    Position newPosition;
    int i;
    int alpha = *p_alpha;
    int beta = *p_beta;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, nodes[i].move);

        int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score;

        if(score == winScore(position->toMove)) {
            return (Node){.move = nodes[i].move, .score = score};
        }

        if(position->toMove == WHITE && score > alpha) {
            alpha = score;
            bestMove = nodes[i].move;
        } else if(position->toMove == BLACK && score < beta) {
            beta = score;
            bestMove = nodes[i].move;
        }

        if(alpha > beta || alpha > *p_beta || *p_alpha > beta) {
            break;
        }
    }

    return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta};
}

Node pIDABhashed(Position* position, char depth, int* p_alpha, int* p_beta) {
    if(hasGameEnded(position)) {
        int score = endNodeEvaluation(position);
        writeToHashFile(position, score, 0);
        return (Node){.score = score};
    }

    if(depth <= 1) {
        Node quie = quiescenceSearch(position);
        writeToHashFile(position, quie.score, depth);
        return quie;
    }

    // Mate in 1
    Node staticNode = staticSearch(position);
    if(staticNode.score == winScore(position->toMove)) {
        writeToHashFile(position, staticNode.score, 1);
        return staticNode;
    }

    Move bestMove = 0;

    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    Position newPosition;
    int i;
    int alpha = *p_alpha;
    int beta = *p_beta;
    for(i = 0; i < moveCount; i++) {
        updatePosition(&newPosition, position, nodes[i].move);

        int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score;
        writeToHashFile(&newPosition, score, depth - 1);

        if(score == winScore(position->toMove)) {
            return (Node){.move = nodes[i].move, .score = score};
        }

        if(position->toMove == WHITE && score > alpha) {
            alpha = score;
            bestMove = nodes[i].move;
        } else if(position->toMove == BLACK && score < beta) {
            beta = score;
            bestMove = nodes[i].move;
        }

        if(alpha > beta || alpha > *p_beta || *p_alpha > beta) {
            break;
        }
    }

    writeToHashFile(position, position->toMove == WHITE ? alpha : beta, depth);
    return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta};
}

// Parallel processing currently only implemented for Windows
#ifdef _WIN32

DWORD WINAPI evaluatePositionThreadFunction(LPVOID lpParam) {
    ThreadInfo* tInfo = (ThreadInfo*)lpParam;
    Position* pos = &tInfo->pos;

    Node node = pIDAB(pos, tInfo->depth, tInfo->alpha, tInfo->beta);

    if(pos->toMove == BLACK && node.score > *tInfo->alpha) {
        *tInfo->alpha = node.score;
    } else if(pos->toMove == WHITE && node.score < *tInfo->beta) {
        *tInfo->beta = node.score;
    }

    if(tInfo->verbose) {
        printf("-");
        fflush(stdout);
    }

    return node.score;
}

DWORD WINAPI evaluatePositionThreadFunctionHashed(LPVOID lpParam) {
    ThreadInfo* tInfo = (ThreadInfo*)lpParam;
    Position* pos = &tInfo->pos;

    Node node = pIDABhashed(pos, tInfo->depth, tInfo->alpha, tInfo->beta);

    if(pos->toMove == BLACK && node.score > *tInfo->alpha) {
        *tInfo->alpha = node.score;
    } else if(pos->toMove == WHITE && node.score < *tInfo->beta) {
        *tInfo->beta = node.score;
    }

    if(tInfo->verbose) {
        printf("-");
        fflush(stdout);
    }

    return node.score;
}

Node idabThreaded(Position* position, int depth, BOOL verbose) {
    if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)};

    if(depth <= 1) return quiescenceSearch(position);

    int i;
    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    if(moveCount == 1) {
        return nodes[0];
    }

    if(verbose) {
        printf("Analyzing %d possible moves with base depth %d:\n[", moveCount, depth);
        for(i = 0; i < moveCount; i++) printf(" ");
        printf("]\r[");
        fflush(stdout);
    }

    HANDLE threadHandles[MAX_BRANCHING_FACTOR];
    ThreadInfo threadInfo[MAX_BRANCHING_FACTOR];
    int alpha = INT32_MIN;
    int beta = INT32_MAX;

    for(i = 0; i < moveCount; i++) {
        threadInfo[i].depth = depth - 1;
        updatePosition(&threadInfo[i].pos, position, nodes[i].move);
        threadInfo[i].alpha = &alpha;
        threadInfo[i].beta = &beta;
        threadInfo[i].verbose = verbose;

        threadHandles[i] =
            CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL);

        if(threadHandles[i] == NULL) {
            //            printf("Error launching process on move #%d!\n", i);
            printf("!");
            fflush(stdout);
        }
    }

    WaitForMultipleObjects((DWORD)moveCount, threadHandles, TRUE, INFINITE);
    if(verbose) {
        printf("]\n");
        fflush(stdout);
    }

    Move bestMove = 0;
    int bestMoveScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX;
    long unsigned int retVal;
    int score;
    for(i = 0; i < moveCount; i++) {
        GetExitCodeThread(threadHandles[i], &retVal);
        score = (int)retVal;

        if((position->toMove == WHITE && score > bestMoveScore) ||
           (position->toMove == BLACK && score < bestMoveScore)) {
            bestMove = nodes[i].move;
            bestMoveScore = score;
        }

        if(CloseHandle(threadHandles[i]) == 0) {
            //            printf("Error on closing thread #%d!\n", i);
            printf("x");
            fflush(stdout);
        }
    }

    return (Node){.move = bestMove, .score = bestMoveScore};
}

Node idabThreadedBestFirst(Position* position, int depth, BOOL verbose) {
    if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)};

    if(depth <= 1) return quiescenceSearch(position);

    int i;
    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    if(moveCount == 1) {
        return nodes[0];
    }

    Position firstPos;
    updatePosition(&firstPos, position, nodes[0].move);
    Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE);

    if(firstReply.score == winScore(position->toMove)) {
        if(verbose) {
            printf("Playing checkmate move: ");
            printFullMove(nodes[0].move, position->board);
            printf(".\n");
        }
        return (Node){.move = nodes[0].move, .score = firstReply.score};
    }

    if(verbose) {
        printf("Move ");
        printFullMove(nodes[0].move, position->board);
        printf(" had score of %+.2f.\n", firstReply.score / 100.0);
        printf(
            "Analyzing other %d possible moves with minimum depth of %d plies:\n[",
            moveCount - 1,
            depth);
        for(i = 0; i < moveCount - 1; i++) printf(" ");
        printf("]\r[");
        fflush(stdout);
    }

    HANDLE threadHandles[MAX_BRANCHING_FACTOR];
    ThreadInfo threadInfo[MAX_BRANCHING_FACTOR];
    int alpha = INT32_MIN;
    int beta = INT32_MAX;

    if(position->toMove == WHITE) {
        alpha = firstReply.score;
    } else {
        beta = firstReply.score;
    }

    for(i = 0; i < moveCount - 1; i++) {
        threadInfo[i].depth = depth - 1;
        updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move);
        threadInfo[i].alpha = &alpha;
        threadInfo[i].beta = &beta;
        threadInfo[i].verbose = verbose;

        threadHandles[i] =
            CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL);

        if(threadHandles[i] == NULL) {
            //            printf("Error launching process on move #%d!\n", i);
            printf("!");
            fflush(stdout);
        }
    }

    WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE);
    if(verbose) {
        printf("] Done!\n");
        fflush(stdout);
    }

    Move bestMove = nodes[0].move;
    int bestMoveScore = firstReply.score;
    long unsigned int retVal;
    int score;
    for(i = 0; i < moveCount - 1; i++) {
        GetExitCodeThread(threadHandles[i], &retVal);
        score = (int)retVal;

        if((position->toMove == WHITE && score > bestMoveScore) ||
           (position->toMove == BLACK && score < bestMoveScore)) {
            bestMove = nodes[i + 1].move;
            bestMoveScore = score;
        }

        if(CloseHandle(threadHandles[i]) == 0) {
            //            printf("Error on closing thread #%d!\n", i);
            printf("x");
            fflush(stdout);
        }
    }

    return (Node){.move = bestMove, .score = bestMoveScore};
}

Node idabThreadedBestFirstHashed(Position* position, int depth, BOOL verbose) {
    if(hasGameEnded(position)) {
        int score = endNodeEvaluation(position);
        writeToHashFile(position, score, 0);
        return (Node){.score = score};
    }

    if(depth <= 1) {
        Node quie = quiescenceSearch(position);
        writeToHashFile(position, quie.score, depth);
        return quie;
    }

    int i;
    Node nodes[MAX_BRANCHING_FACTOR];
    int moveCount = alphaBetaNodes(nodes, position, depth - 1);

    if(moveCount == 1) {
        return nodes[0];
    }

    Position firstPos;
    updatePosition(&firstPos, position, nodes[0].move);
    Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE);

    if(firstReply.score == winScore(position->toMove)) {
        if(verbose) {
            printf("Playing checkmate move: ");
            printFullMove(nodes[0].move, position->board);
            printf(".\n");
        }
        writeToHashFile(position, firstReply.score, depth);
        return (Node){.move = nodes[0].move, .score = firstReply.score};
    }

    if(verbose) {
        printf("Move ");
        printFullMove(nodes[0].move, position->board);
        printf(" had score of %+.2f.\n", firstReply.score / 100.0);
        printf(
            "Analyzing other %d possible moves with minimum depth of %d plies:\n[",
            moveCount - 1,
            depth);
        for(i = 0; i < moveCount - 1; i++) printf(" ");
        printf("]\r[");
        fflush(stdout);
    }

    HANDLE threadHandles[MAX_BRANCHING_FACTOR];
    ThreadInfo threadInfo[MAX_BRANCHING_FACTOR];
    int alpha = INT32_MIN;
    int beta = INT32_MAX;

    if(position->toMove == WHITE) {
        alpha = firstReply.score;
    } else {
        beta = firstReply.score;
    }

    for(i = 0; i < moveCount - 1; i++) {
        threadInfo[i].depth = depth - 1;
        updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move);
        threadInfo[i].alpha = &alpha;
        threadInfo[i].beta = &beta;
        threadInfo[i].verbose = verbose;

        threadHandles[i] = CreateThread(
            NULL, 0, evaluatePositionThreadFunctionHashed, (LPVOID)&threadInfo[i], 0, NULL);

        if(threadHandles[i] == NULL) {
            //            printf("Error launching process on move #%d!\n", i);
            printf("!");
            fflush(stdout);
        }
    }

    WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE);
    if(verbose) {
        printf("] Done!\n");
        fflush(stdout);
    }

    Move bestMove = nodes[0].move;
    int bestMoveScore = firstReply.score;
    long unsigned int retVal;
    int score;
    for(i = 0; i < moveCount - 1; i++) {
        GetExitCodeThread(threadHandles[i], &retVal);
        score = (int)retVal;

        writeToHashFile(&threadInfo[i].pos, score, depth - 1);

        if((position->toMove == WHITE && score > bestMoveScore) ||
           (position->toMove == BLACK && score < bestMoveScore)) {
            bestMove = nodes[i + 1].move;
            bestMoveScore = score;
        }

        if(CloseHandle(threadHandles[i]) == 0) {
            //            printf("Error on closing thread #%d!\n", i);
            printf("x");
            fflush(stdout);
        }
    }

    writeToHashFile(position, bestMoveScore, depth);
    return (Node){.move = bestMove, .score = bestMoveScore};
}

#endif /* _WIN32 */

Move getRandomMove(Position* position) {
    Move moves[MAX_BRANCHING_FACTOR];
    int totalMoves = legalMoves(moves, position, position->toMove);
    int chosenMove = rand() % totalMoves;
    return moves[chosenMove];
}

Move getAIMove(Game* game, int depth) {
    printf("--- AI ---\n");
    fflush(stdout);

    if(fromInitial(game) && countBookOccurrences(game) > 0) {
        printf("There are %d available book continuations.\n", countBookOccurrences(game));
        fflush(stdout);
        Move bookMove = getBookMove(game);
        printf("CHOSEN book move: ");
        printFullMove(bookMove, &(game->position.board));
        printf(".\n");
        fflush(stdout);
        return bookMove;
    }

    time_t startTime, endTime;
    startTime = time(NULL);

    //    Move move = getRandomMove(&game->position);
    //    Move move = simpleEvaluation(&game->position).move;
    //    Move move = minimax(&game->position, AI_DEPTH).move;
    //    Node node = alphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE);
    //    Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE);
    //    Node node = idabThreaded(&game->position, depth, TRUE);
    //    Node node = idabThreadedBestFirst(&game->position, depth, TRUE);
    //    Node node = idabThreadedBestFirstHashed(&game->position, depth, TRUE);

#ifdef _WIN32
    Node node = idabThreadedBestFirst(&game->position, depth, TRUE);
#else
    Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE);
#endif

    endTime = time(NULL);

    printf("CHOSEN move: ");
    printFullMove(node.move, &(game->position.board));
    printf(
        " in %d seconds [%+.2f, %+.2f]\n",
        (int)(endTime - startTime),
        staticEvaluation(&game->position) / 100.0,
        node.score / 100.0);
    fflush(stdout);

    return node.move;
}

Move parseMove(char* move) {
    int pos1 = str2index(&move[0]);
    int pos2 = str2index(&move[2]);
    return generateMove(pos1, pos2);
}

Move getPlayerMove() {
    char input[100];
    fgets(input, 100, stdin);
    return parseMove(input);
}

Move suggestMove(char fen[], int depth) {
    Game game;
    getFenGame(&game, fen);
    return getAIMove(&game, depth);
}

// ===== PLAY LOOP (TEXT) ====

void playTextWhite(int depth) {
    printf("Playing as WHITE!\n");
    fflush(stdout);

    Game game;
    getInitialGame(&game);

    while(TRUE) {
        printBoard(&(game.position.board));
        if(hasGameEnded(&game.position)) break;

        makeMove(&game, getPlayerMove());

        printBoard(&(game.position.board));
        if(hasGameEnded(&game.position)) break;

        makeMove(&game, getAIMove(&game, depth));
    }
    printOutcome(&game.position);
}

void playTextBlack(int depth) {
    printf("Playing as BLACK!\n");
    fflush(stdout);

    Game game;
    getInitialGame(&game);

    while(TRUE) {
        printBoard(&(game.position.board));
        if(hasGameEnded(&game.position)) break;

        makeMove(&game, getAIMove(&game, depth));

        printBoard(&(game.position.board));
        if(hasGameEnded(&game.position)) break;

        makeMove(&game, getPlayerMove());
    }
    printOutcome(&game.position);
}

void playTextAs(char color, int depth) {
    if(color == WHITE) playTextWhite(depth);
    if(color == BLACK) playTextBlack(depth);
}

void playTextRandomColor(int depth) {
    char colors[] = {WHITE, BLACK};
    char color = colors[rand() % 2];
    playTextAs(color, depth);
}

// ===========================

/*
int main(int argc, char *argv[]) {
    srand(time(NULL));

    int opt;
    int FEN_MODE = 0, MOVES_MODE = 1, mode = FEN_MODE;
    int depth = DEFAULT_AI_DEPTH;

    while ((opt = getopt(argc, argv, "fmd:v")) != -1) {
        switch (opt) {
        case 'f': mode = FEN_MODE; break;
        case 'm': mode = MOVES_MODE; break;
        case 'd': depth = atoi(optarg); break;
        case 'v': printf(ENGINE_VERSION); exit(0);
        default:
            fprintf(stderr, "Usage: %s [-d depth] [-f fen] [-m move_list]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    Game game;
    if (argc > optind) {
        if (mode == FEN_MODE) {
            getFenGame(&game, argv[optind]);
        } else if (mode == MOVES_MODE) {
            getMovelistGame(&game, argv[optind]);
        }
    } else {
        getInitialGame(&game);
    }

    Move move;
    if ( mode == MOVES_MODE && countBookOccurrences(&game) > 0 ) {
        move = getBookMove(&game);
    } else {
        Node node = iterativeDeepeningAlphaBeta(&(game.position), (char) depth, INT32_MIN, INT32_MAX, FALSE);
        move = node.move;
    }

    printf("%c%c%c%c", getFile(getFrom(move)), getRank(getFrom(move)), getFile(getTo(move)), getRank(getTo(move)));

    return EXIT_SUCCESS;
}
// */
