// vim: set ts=4 sw=4 et tw=80:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "./chessboard.h"

#define DEBUG_BUILD
#ifdef DEBUG_BUILD
#define DEBUG(...) fprintf(stderr, __VA_ARGS__);
#else
#define DEBUG(...) (void) 0;
#endif

static inline enum pieces cb_move(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);
static inline void cb_move_restore(struct chessboard* cb, enum pieces piece,
        int ex_from_row, int ex_from_col, int ex_to_row, int ex_to_col);
static inline int sign(int n);
static inline enum player player(struct chessboard* cb, int row, int col);
static inline bool is_empty(struct chessboard* cb, int row, int col);
static bool check_empty_paths(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);

static bool can_move_pawn(struct chessboard* cb, enum player p,
        int from_row, int from_col, int to_row, int to_col);
static bool can_move_rook(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);
static bool can_move_knight(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);
static bool can_move_bishop(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);
static bool can_move_queen(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);
static bool can_move_king(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col);

void init_chessboard(struct chessboard * cb) {
    int i;
    for (i = 0; i < 8; i++) {
        cb->position[1][i] = WHITE_PAWN;
        cb->position[6][i] = BLACK_PAWN;
    }

    cb->position[0][0] = WHITE_ROOK;
    cb->position[0][1] = WHITE_KNIGHT;
    cb->position[0][2] = WHITE_BISHOP;
    cb->position[0][3] = WHITE_QUEEN;
    cb->position[0][4] = WHITE_KING;
    cb->position[0][5] = WHITE_BISHOP;
    cb->position[0][6] = WHITE_KNIGHT;
    cb->position[0][7] = WHITE_ROOK;

    cb->position[7][0] = BLACK_ROOK;
    cb->position[7][1] = BLACK_KNIGHT;
    cb->position[7][2] = BLACK_BISHOP;
    cb->position[7][3] = BLACK_QUEEN;
    cb->position[7][4] = BLACK_KING;
    cb->position[7][5] = BLACK_BISHOP;
    cb->position[7][6] = BLACK_KNIGHT;
    cb->position[7][7] = BLACK_ROOK;

    int j;
    for (i = 0; i < 8; i++) {
        for (j = 2; j < 6; j++) {
            cb->position[j][i] = EMPTY;
        }
    }

}

void print_chessboard(struct chessboard * cb) {
    printf("     a   b   c   d   e   f   g   h\n"
            "   ┌───┬───┬───┬───┬───┬───┬───┬───┐\n");

    int i;
    for (i = 7; i >= 0; i--) {

        printf("%d  │", i + 1);

        int j;
        for (j = 0; j < 8; j++) {
            printf(" %s │", utf_8_pieces[cb->position[i][j]]);
        }
        putchar('\n');

        if (i != 0) {
            printf("   ├───┼───┼───┼───┼───┼───┼───┼───┤\n");
        }
    }

    printf("   └───┴───┴───┴───┴───┴───┴───┴───┘\n"
            "     a   b   c   d   e   f   g   h\n");

}

inline enum pieces cb_move(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    enum pieces tmp = cb->position[to_row][to_col];
    cb->position[to_row][to_col] = cb->position[from_row][from_col];
    cb->position[from_row][from_col] = EMPTY;
    return tmp;
}

inline void cb_move_restore(struct chessboard* cb, enum pieces piece,
        int ex_from_row, int ex_from_col, int ex_to_row, int ex_to_col) {
    cb->position[ex_from_row][ex_from_col] =
        cb->position[ex_to_row][ex_to_col];
    cb->position[ex_to_row][ex_to_col] = piece;
}

inline bool is_empty(struct chessboard* cb, int row, int col) {
    return cb->position[row][col] == EMPTY;
}

inline enum player player(struct chessboard* cb, int row, int col) {
    return cb->position[row][col] < 7 ? WHITE : BLACK;
}

inline int sign(int n) {
    return n == 0 ? 0 : (n > 0 ? 1 : -1);
}

bool can_move_pawn(struct chessboard* cb, enum player p,
        int from_row, int from_col, int to_row, int to_col) {
    DEBUG("can_move_pawn: from: row %d col %d to: row %d col %d\n",
            from_row, from_col, to_row, to_col);

    // two forward if in the starting position, no capture is valid
    if (((p == WHITE && from_row == 1 && to_row == 3 &&
                    is_empty(cb, 2, from_col)) ||
                (p == BLACK && from_row == 6 && to_row == 4 &&
                 is_empty(cb, 5, from_col))) &&
            from_col == to_col &&
            is_empty(cb, to_row, to_col)) {
        DEBUG("can_move_pawn: two forward, valid\n");
        return true;
        // one forward, no capture is valid
    } else if (((p == WHITE && to_row - from_row == 1) ||
                (p == BLACK && from_row - to_row == 1)) &&
            from_col == to_col &&
            is_empty(cb, to_row, to_col)) {
        DEBUG("can_move_pawn: one forward no capture, valid\n");
        return true;
        // one forward, one left or right, with capture, is valid
    } else if (to_row == from_row + (p == WHITE ? 1 : -1) &&
            abs(from_col - to_col) == 1 &&
            !is_empty(cb, to_row, to_col)) {
        DEBUG("can_move_pawn: one forward diagonal capture, valid\n");
        return true;
    } else {
        DEBUG("can_move_pawn: move pawn not valid\n");
        return false;
    }
}

bool check_empty_paths(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    int ii = sign(to_row - from_row), jj = sign(to_col - from_col);
    int i = from_row + ii, j = from_col + jj;

    DEBUG("check_empty_paths: from: row %d col %d, to: row %d col %d, ii: %d,"
            " jj: %d\n", from_row, from_col, to_row, to_col, ii, jj);

    for (; !(i == to_row && j == to_col); i += ii, j += jj) {
        if (!is_empty(cb, i, j)) {
            DEBUG("check_empty_paths: position row %d col %d"
                    " is occupied\n", i, j);
            return false;
        }
    }

    return true;
}

bool can_move_rook(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    if (to_row != from_row && to_col != from_col) {
        return false;
    }

    return check_empty_paths(cb, from_row, from_col, to_row, to_col);
}

bool can_move_bishop(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    if (abs(from_row - to_row) != abs(from_col - to_col)) {
        return false;
    }

    return check_empty_paths(cb, from_row, from_col, to_row, to_col);
}

bool can_move_queen(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    return can_move_bishop(cb, from_row, from_col, to_row, to_col) ||
        can_move_rook(cb, from_row, from_col, to_row, to_col);
}

bool can_move_knight(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    return (to_row == from_row + 2 && to_col == from_col + 1) ||
        (to_row == from_row + 2 && to_col == from_col - 1) ||
        (to_row == from_row - 2 && to_col == from_col + 1) ||
        (to_row == from_row - 2 && to_col == from_col - 1) ||
        (to_row == from_row + 1 && to_col == from_col + 2) ||
        (to_row == from_row - 1 && to_col == from_col + 2) ||
        (to_row == from_row + 1 && to_col == from_col - 2) ||
        (to_row == from_row - 1 && to_col == from_col + 2);
}

bool can_move_king(struct chessboard* cb,
        int from_row, int from_col, int to_row, int to_col) {
    if (abs(to_row - from_row) > 1) {
        DEBUG("can_move_king: row movement too big (from %d to %d)\n", from_row,
                to_row);
        return false;
    }
    if (abs(to_col - from_col) > 1) {
        DEBUG("can_move_king: column movement too big (from %d to %d)\n",
                from_col, to_col);
        return false;
    }
    return true;
}

bool can_move(struct chessboard* cb, enum player p,
        int from_row, int from_col, int to_row, int to_col) {

    // try to understand which piece we are moving
    switch (cb->position[from_row][from_col]) {
        case WHITE_PAWN:
        case BLACK_PAWN:
            DEBUG("can_move: moving a pawn\n");
            return can_move_pawn(cb, p, from_row, from_col, to_row, to_col);
        case WHITE_ROOK:
        case BLACK_ROOK:
            DEBUG("can_move: moving a rook\n");
            return can_move_rook(cb, from_row, from_col, to_row, to_col);
        case WHITE_BISHOP:
        case BLACK_BISHOP:
            DEBUG("can_move: moving a bishop\n");
            return can_move_bishop(cb, from_row, from_col, to_row, to_col);
        case WHITE_QUEEN:
        case BLACK_QUEEN:
            DEBUG("can_move: moving a queen\n");
            return can_move_queen(cb, from_row, from_col, to_row, to_col);
        case WHITE_KNIGHT:
        case BLACK_KNIGHT:
            DEBUG("can_move: moving a knight\n");
            return can_move_knight(cb, from_row, from_col, to_row, to_col);
        case WHITE_KING:
        case BLACK_KING:
            DEBUG("can_move: moving a king\n");
            return can_move_king(cb, from_row, from_col, to_row, to_col);
        default:
            DEBUG("can_move: piece not implemented\n");
            return false;
    }
}

void find_other_king(struct chessboard *cb, enum player p,
        int* k_row, int* k_col) {
    for (*k_row = 0; *k_row < 8; (*k_row)++) {
        for (*k_col = 0; *k_col < 8; (*k_col)++) {
            if ((p == BLACK && cb->position[*k_row][*k_col] == WHITE_KING) ||
                    (p == WHITE && cb->position[*k_row][*k_col] == BLACK_KING))
            {
                DEBUG("find_other_king: %s king in position row %d col %d\n",
                        p == WHITE ? "black" : "white", *k_row, *k_col);
                return;
            }
        }
    }
    DEBUG("find_king: king not found");
}

bool player_checks(struct chessboard *cb, enum player p) {
    int k_row, k_col;
    find_other_king(cb, p, &k_row, &k_col);
    int i, j;
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            if (!is_empty(cb, i, j) && player(cb, i, j) == p) {
                DEBUG("player_checks: checking row %d col %d to attack king\n",
                        i, j);
                if (can_move(cb, p, i, j, k_row, k_col)) {
                    DEBUG("player_checks: row %d col %d can attack %s king\n",
                            i, j, p == WHITE ? "black" : "white");
                    return true;
                }
            }
        }
    }
    return false;
}

bool player_checkmates(struct chessboard *cb, enum player p) {
    int i, j;
    // for every opponent's piece
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            if (is_empty(cb, i, j) || player(cb, i, j) == p) {
                continue;
            }

            int k, l;
            // for every possible position not equal to the current position of
            // that piece, which is empty or occupied by our pieces (implying
            // capture)
            for (k = 0; k < 8; k++) {
                for (l = 0; l < 8; l++) {
                    if ((k == i && l == j) ||
                        is_empty(cb, k, l) ||
                        player(cb, k, l) != p) {
                        continue;
                    }

                    // check if the move is valid
                    bool can = can_move(cb, p == BLACK ? WHITE : BLACK,
                            i, j, k, l);
                    // if not, carry on
                    if (!can) {
                        continue;
                    }

                    // if valid, perform the move and check for check.
                    enum pieces tmp = cb_move(cb, i, j, k, l);
                    bool check = player_checks(cb, p);
                    cb_move_restore(cb, tmp, i, j, k, l);

                    // if check is no longer there, then this is not a checkmate
                    if (!check) {
                        return false;
                    }
                }
            }
        }
    }

    // if no such move exists, then this is checkmate
    return true;
}

enum mstatus move(
        struct chessboard * cb, enum player p,
        const char * from, const char * to) {

    // if you are giving us garbage, we behave like Rome's municipal services
    if (strlen(from) != 2 || from[0] < 'a' || from[0] > 'h' || from[1] < '1'
            || from[1] > '8') {
        DEBUG("move: 'from' input '%s' is invalid\n", from);
        return INVALID;
    }
    if (to[0] < 'a' || to[0] > 'h' || to[1] < '1' || to[1] > '8'
            || strlen(to) != 2) {
        DEBUG("move: 'to' input '%s' is invalid\n", to);
        return INVALID;
    }

    int from_col = from[0] - 'a', to_col = to[0] - 'a';
    int from_row = from[1] - '1', to_row = to[1] - '1';

    // if you are moving thin air, EH VOLEVIH
    if (is_empty(cb, from_row, from_col)) {
        DEBUG("move: from is empty\n");
        return INVALID;
    }

    // if you are moving someone else's pieces, "thou shall not steal"
    if (player(cb, from_row, from_col) != p) {
        DEBUG("move: moving piece of opponent\n");
        return INVALID;
    }

    // if you are trying to suicide (capture your piecies), sorry but assisted
    // suicide is not legal in italy
    if (cb->position[to_row][to_col] != EMPTY &&
            cb->position[from_row][from_col] / 7 ==
            cb->position[to_row][to_col] / 7) {
        DEBUG("move: capturing piece from moving player\n");
        return INVALID;
    }

    // if you try to move on the same spot, don't do it you buffoon
    if (from_row == to_row && from_col == to_col) {
        DEBUG("move: moving to same position\n");
        return INVALID;
    }

    // check if we are currently in check state
    bool check_before = player_checks(cb, p == WHITE ? BLACK : WHITE);
    // determine if move is valid
    bool can = can_move(cb, p, from_row, from_col, to_row, to_col);

    if (!can) {
        return INVALID;
    } else {
        // try to move, and revert if invalid due to check in progress
        enum pieces tmp = cb_move(cb, from_row, from_col, to_row, to_col);
        if (player_checks(cb, p == WHITE ? BLACK : WHITE) && check_before) {
            DEBUG("move: move does not end check state, invalid");
            cb_move_restore(cb, tmp, from_row, from_col, to_row, to_col);
            return INVALID;
        } else if (player_checks(cb, p)) {
            if (player_checkmates(cb, p)) {
                return CHECK_MATE;
            } else {
                return CHECK;
            }
        } else {
            return VALID;
        }
    }
}

int main() {
    struct chessboard cb;
    init_chessboard(&cb);
    enum player p = WHITE;
    char from[5], to[5];
    while (true) {
        print_chessboard(&cb);
        printf("%s moves.\n", p == WHITE ? "White" : "Black");
        printf("From: ");
        fgets(from, 5, stdin);
        // remove trailing \n: https://stackoverflow.com/q/2693776
        strtok(from, "\n");

        if (strcmp(from, "exit") == 0) {
            printf("Exiting...\n");
            return 0;
        }

        printf("To: ");
        fgets(to, 5, stdin);
        strtok(to, "\n");
        enum mstatus s = move(&cb, p, from, to);
        switch (s) {
            case INVALID:
                printf("Invalid move.\n");
                break;
            case VALID:
                printf("No check/checkmate.\n");
                break;
            case CHECK:
                printf("Check.\n");
                break;
            case CHECK_MATE:
                printf("Checkmate, %s wins.\n", p == WHITE ? "white" : "black");
        }

        if (s != INVALID) {
            p = !p; // switch player: this works since WHITE = 0 and BLACK = 1
        }
    }
}
