// move_do.cpp

// includes

#include "attack.h"
#include "board.h"
#include "colour.h"
#include "hash.h"
#include "move.h"
#include "move_do.h"
#include "pawn.h" // TODO: bit.h
#include "piece.h"
#include "pst.h"
#include "random.h"
#include "util.h"
#include "value.h"

// variables

static int CastleMask[SquareNb];

// prototypes

static void square_clear( board_t *board, int square, int piece, bool update );
static void square_set( board_t *board, int square, int piece, int pos, bool update );
static void square_move( board_t *board, int from, int to, int piece, bool update );

// functions

// move_do_init()

void move_do_init()
    {

    int sq;

    for ( sq = 0; sq < SquareNb; sq++ )
        CastleMask[sq] = 0xF;

    CastleMask[RookSquare[White][CastleQueen]] &= ~FlagsWhiteQueenCastle;
    CastleMask[KingSquare[White]] &= ~FlagsWhiteQueenCastle;

    CastleMask[RookSquare[White][CastleKing]] &= ~FlagsWhiteKingCastle;
    CastleMask[KingSquare[White]] &= ~FlagsWhiteKingCastle;

    CastleMask[RookSquare[Black][CastleQueen]] &= ~FlagsBlackQueenCastle;
    CastleMask[KingSquare[Black]] &= ~FlagsBlackQueenCastle;

    CastleMask[RookSquare[Black][CastleKing]] &= ~FlagsBlackKingCastle;
    CastleMask[KingSquare[Black]] &= ~FlagsBlackKingCastle;
    }

// move_do()

void move_do( board_t *board, int move, undo_t *undo )
    {

    int me, opp;
    int from, to;
    int piece, pos, capture;
    int old_flags, new_flags;
    int delta;
    int sq;
    int pawn, rook;

    ASSERT(board_is_ok(board));
    ASSERT(move_is_ok(move));
    ASSERT(undo != NULL);

    ASSERT(board_is_legal(board));

    // initialise undo

    undo->capture = false;

    undo->turn = board->turn;
    undo->flags = board->flags;
    undo->ep_square = board->ep_square;
    undo->ply_nb = board->ply_nb;

    undo->cap_sq = board->cap_sq;
    undo->moving_piece = board->moving_piece;

    undo->opening = board->opening;
    undo->endgame = board->endgame;

    undo->key = board->key;
    undo->pawn_key = board->pawn_key;
    undo->material_key = board->material_key;

    // init

    me = board->turn;
    opp = COLOUR_OPP(me);

    from = MOVE_FROM(move);
    to = MOVE_TO(move);

    piece = board->square[from];
    board->moving_piece = piece;
    ASSERT(COLOUR_IS(piece, me));

    // update key stack

    ASSERT(board->sp < StackSize);
    board->stack[board->sp++] = board->key;

    // update turn

    board->turn = opp;
    board->key ^= RANDOM_64(RandomTurn);

    // update castling rights

    old_flags = board->flags;
    new_flags = old_flags & CastleMask[from] & CastleMask[to];

    board->flags = new_flags;
    board->key ^= Castle64[new_flags ^ old_flags];

    // update en-passant square

    if( (sq = board->ep_square) != SquareNone )
        {
        board->key ^= RANDOM_64(RandomEnPassant + SQUARE_FILE(sq) - FileA);
        board->ep_square = SquareNone;
        }

    if( PIECE_IS_PAWN(piece) )
        {

        delta = to - from;

        if( delta == +32 || delta == -32 )
            {
            pawn = PAWN_MAKE(opp);

            if( board->square[to - 1] == pawn || board->square[to + 1] == pawn )
                {
                board->ep_square = (from + to) / 2;
                board->key ^= RANDOM_64(RandomEnPassant + SQUARE_FILE(to) - FileA);
                }
            }
        }

    // update move number (captures are handled later)

    board->ply_nb++;

    if( PIECE_IS_PAWN(piece) )
        board->ply_nb = 0; // conversion

    // update last square

    board->cap_sq = SquareNone;

    if( MOVE_IS_CASTLE(move) )
        {
        rook = Rook64 | COLOUR_FLAG(me);
        int rook_from, rook_to;

        if( to == G1 || to == G8 )
            {
            rook_from = RookSquare[me][CastleKing];
            ASSERT(board->square[to] == Empty || to == RookSquare[me][CastleKing] && board->square[to] == rook);
            rook_to = to - 1;
            }
        else
            {
            ASSERT(to == C1 || to == C8);
            rook_from = RookSquare[me][CastleQueen];
            ASSERT(board->square[to] == Empty || to == RookSquare[me][CastleQueen] && board->square[to] == rook);
            rook_to = to + 1;
            }

        if( to != from )
            {
            int from_64 = SQUARE_TO_64(from);
            int to_64 = SQUARE_TO_64(to);
            int piece_12 = PIECE_TO_12(piece);
            int piece_index = RandomPiece + (piece_12 ^ 1) * 64;
            board->square[from] = Empty;
            board->pos[from] = -1;
            board->piece[me][0] = to;
            board->opening += PST(piece_12, to_64, Opening) - PST(piece_12, from_64, Opening);
            board->endgame += PST(piece_12, to_64, Endgame) - PST(piece_12, from_64, Endgame);
            board->key ^= (RANDOM_64(piece_index + to_64) ^ RANDOM_64(piece_index + from_64));
            }

        if( rook_from != rook_to )
            {
            int rook_12 = PIECE_TO_12(rook);
            int to_64 = SQUARE_TO_64(rook_to);
            int from_64 = SQUARE_TO_64(rook_from);
            int piece_index = RandomPiece + (rook_12 ^ 1) * 64;
            board->square[rook_from] = Empty;
            board->pos[rook_to] = board->pos[rook_from];
            board->piece[me][board->pos[rook_to]] = rook_to;
            board->pos[rook_from] = -1;
            board->opening += PST(rook_12, to_64, Opening) - PST(rook_12, from_64, Opening);
            board->endgame += PST(rook_12, to_64, Endgame) - PST(rook_12, from_64, Endgame);
            board->key ^= (RANDOM_64(piece_index + to_64) ^ RANDOM_64(piece_index + from_64));
            }

        board->square[to] = piece;
        board->square[rook_to] = rook;
        board->pos[to] = 0;
        undo->capture_square = rook_from;
        }
    else
        {

        // remove the captured piece

        sq = to;

        if( MOVE_IS_EN_PASSANT(move) )
            sq = SQUARE_EP_DUAL(sq);

        if( (capture = board->square[sq]) != Empty )
            {

            ASSERT(COLOUR_IS(capture, opp));
            ASSERT(!PIECE_IS_KING(capture));

            undo->capture = true;
            undo->capture_square = sq;
            undo->capture_piece = capture;
            undo->capture_pos = board->pos[sq];

            square_clear(board, sq, capture, true);

            board->ply_nb = 0; // conversion
            board->cap_sq = to;
            }

        // move the piece

        if( MOVE_IS_PROMOTE(move) )
            {

            // promote

            undo->pawn_pos = board->pos[from];

            square_clear(board, from, piece, true);

            piece = move_promote(move);

            // insert the promote piece in MV order

            for ( pos = board->piece_size[me]; pos > 0 && piece > board->square[board->piece[me][pos - 1]];
                pos-- )
            ;

            square_set(board, to, piece, pos, true);

            board->cap_sq = to;
            }
        else
            {

            // normal move

            square_move(board, from, to, piece, true);
            }
        }

    // debug

    ASSERT(board_is_ok(board));
    }

// move_undo()

void move_undo( board_t *board, int move, const undo_t *undo )
    {

    int me;
    int from, to;
    int piece, pos;
    int rook;

    ASSERT(board != NULL);
    ASSERT(move_is_ok(move));
    ASSERT(undo != NULL);

    // init

    me = undo->turn;

    from = MOVE_FROM(move);
    to = MOVE_TO(move);

    piece = board->square[to];
    ASSERT(COLOUR_IS(piece, me));

    // castle

    if( MOVE_IS_CASTLE(move) )
        {

        rook = Rook64 | COLOUR_FLAG(me);
        int rook_to = to + (to == G1 || to == G8 ? -1 : 1);
        int rook_from = undo->capture_square;

        if( to != from )
            {
            board->square[to] = Empty;
            board->pos[to] = -1;
            board->piece[me][0] = from;
            }

        if( rook_to != rook_from )
            {
            board->square[rook_to] = Empty;
            board->pos[rook_from] = board->pos[rook_to];
            board->piece[me][board->pos[rook_from]] = rook_from;
            board->pos[rook_to] = -1;
            }

        board->square[from] = piece;
        board->pos[from] = 0;
        board->square[rook_from] = rook;
        }
    else if( MOVE_IS_PROMOTE(move) )
        {

        // promote

        ASSERT(piece == move_promote(move));
        square_clear(board, to, piece, false);

        piece = PAWN_MAKE(me);
        pos = undo->pawn_pos;

        square_set(board, from, piece, pos, false);
        }
    else
        {

        // normal move

        square_move(board, to, from, piece, false);
        }

    // put the captured piece back

    if( undo->capture )
        {
        square_set(board, undo->capture_square, undo->capture_piece, undo->capture_pos, false);
        }

    // update board info

    board->turn = undo->turn;
    board->flags = undo->flags;
    board->ep_square = undo->ep_square;
    board->ply_nb = undo->ply_nb;

    board->cap_sq = undo->cap_sq;
    board->moving_piece = undo->moving_piece;

    board->opening = undo->opening;
    board->endgame = undo->endgame;

    board->key = undo->key;
    board->pawn_key = undo->pawn_key;
    board->material_key = undo->material_key;

    // update key stack

    ASSERT(board->sp > 0);
    board->sp--;

    // debug

    ASSERT(board_is_ok(board));
    ASSERT(board_is_legal(board));
    }

// move_do_null()

void move_do_null( board_t *board, undo_t *undo )
    {

    int sq;

    ASSERT(board != NULL);
    ASSERT(undo != NULL);

    ASSERT(board_is_legal(board));
    ASSERT(!board_is_check(board));

    // initialise undo

    undo->turn = board->turn;
    undo->ep_square = board->ep_square;
    undo->ply_nb = board->ply_nb;
    undo->cap_sq = board->cap_sq;
    undo->moving_piece = board->moving_piece;
    undo->key = board->key;

    // update key stack

    ASSERT(board->sp < StackSize);
    board->stack[board->sp++] = board->key;

    // update turn

    board->turn = COLOUR_OPP(board->turn);
    board->key ^= RANDOM_64(RandomTurn);

    // update en-passant square

    sq = board->ep_square;

    if( sq != SquareNone )
        {
        board->key ^= RANDOM_64(RandomEnPassant + SQUARE_FILE(sq) - FileA);
        board->ep_square = SquareNone;
        }

    // update move number

    board->ply_nb = 0;

    // update last square

    board->cap_sq = SquareNone;
    board->moving_piece = PieceNone256;

    // debug

    ASSERT(board_is_ok(board));
    }

// move_undo_null()

void move_undo_null( board_t *board, const undo_t *undo )
    {

    ASSERT(board != NULL);
    ASSERT(undo != NULL);

    ASSERT(board_is_legal(board));
    ASSERT(!board_is_check(board));

    // update board info

    board->turn = undo->turn;
    board->ep_square = undo->ep_square;
    board->ply_nb = undo->ply_nb;
    board->cap_sq = undo->cap_sq;
    board->moving_piece = undo->moving_piece;
    board->key = undo->key;

    // update key stack

    ASSERT(board->sp > 0);
    board->sp--;

    // debug

    ASSERT(board_is_ok(board));
    }

// square_clear()

static void square_clear( board_t *board, int square, int piece, bool update )
    {

    int pos, piece_12, colour;
    int sq;
    int i, size;
    int sq_64;
    uint64 hash_xor;

    ASSERT(board != NULL);
    ASSERT(SQUARE_IS_OK(square));
    ASSERT(piece_is_ok(piece));
    ASSERT(update == true || update == false);

    // init

    pos = board->pos[square];
    ASSERT(pos >= 0);

    piece_12 = PIECE_TO_12(piece);
    colour = PIECE_COLOUR(piece);

    // square

    ASSERT(board->square[square] == piece);
    board->square[square] = Empty;

    // piece list

    if( !PIECE_IS_PAWN(piece) )
        {

        // init

        size = board->piece_size[colour];
        ASSERT(size >= 1);

        // stable swap

        ASSERT(pos >= 0 && pos < size);

        ASSERT(board->pos[square] == pos);
        board->pos[square] = -1;

        for ( i = pos; i < size - 1; i++ )
            {

            sq = board->piece[colour][i + 1];

            board->piece[colour][i] = sq;

            ASSERT(board->pos[sq] == i + 1);
            board->pos[sq] = i;
            }

        // size

        size--;

        board->piece[colour][size] = SquareNone;
        board->piece_size[colour] = size;
        }
    else
        {

        // init

        size = board->pawn_size[colour];
        ASSERT(size >= 1);

        // stable swap

        ASSERT(pos >= 0 && pos < size);

        ASSERT(board->pos[square] == pos);
        board->pos[square] = -1;

        for ( i = pos; i < size - 1; i++ )
            {

            sq = board->pawn[colour][i + 1];

            board->pawn[colour][i] = sq;

            ASSERT(board->pos[sq] == i + 1);
            board->pos[sq] = i;
            }

        // size

        size--;

        board->pawn[colour][size] = SquareNone;
        board->pawn_size[colour] = size;

        // pawn "bitboard"

        board->pawn_file[colour][SQUARE_FILE(square)] ^= BIT(PAWN_RANK(square, colour));
        }

    // material

    ASSERT(board->piece_nb > 0);
    board->piece_nb--;

    ASSERT(board->number[piece_12] > 0);
    board->number[piece_12]--;

    board->piece_material[colour] -= VALUE_PIECE(piece);

    // update

    if( update )
        {

        // init

        sq_64 = SQUARE_TO_64(square);

        // PST

        board->opening -= PST(piece_12, sq_64, Opening);
        board->endgame -= PST(piece_12, sq_64, Endgame);

        // hash key

        hash_xor = RANDOM_64(RandomPiece + (piece_12 ^ 1) * 64 + sq_64);

        board->key ^= hash_xor;

        if( PIECE_IS_PAWN(piece) )
            board->pawn_key ^= hash_xor;

        // material key

        board->material_key ^= RANDOM_64(piece_12 * 16 + board->number[piece_12]);
        }
    }

// square_set()

static void square_set( board_t *board, int square, int piece, int pos, bool update )
    {

    int piece_12, colour;
    int sq;
    int i, size;
    int sq_64;
    uint64 hash_xor;

    ASSERT(board != NULL);
    ASSERT(SQUARE_IS_OK(square));
    ASSERT(piece_is_ok(piece));
    ASSERT(pos >= 0);
    ASSERT(update == true || update == false);

    // init

    piece_12 = PIECE_TO_12(piece);
    colour = PIECE_COLOUR(piece);

    // square

    ASSERT(board->square[square] == Empty);
    board->square[square] = piece;

    // piece list

    if( !PIECE_IS_PAWN(piece) )
        {

        // init

        size = board->piece_size[colour];
        ASSERT(size >= 0);

        // size

        size++;

        board->piece[colour][size] = SquareNone;
        board->piece_size[colour] = size;

        // stable swap

        ASSERT(pos >= 0 && pos < size);

        for ( i = size - 1; i > pos; i-- )
            {

            sq = board->piece[colour][i - 1];

            board->piece[colour][i] = sq;

            ASSERT(board->pos[sq] == i - 1);
            board->pos[sq] = i;
            }

        board->piece[colour][pos] = square;

        ASSERT(board->pos[square] == -1);
        board->pos[square] = pos;
        }
    else
        {

        // init

        size = board->pawn_size[colour];
        ASSERT(size >= 0);

        // size

        size++;

        board->pawn[colour][size] = SquareNone;
        board->pawn_size[colour] = size;

        // stable swap

        ASSERT(pos >= 0 && pos < size);

        for ( i = size - 1; i > pos; i-- )
            {

            sq = board->pawn[colour][i - 1];

            board->pawn[colour][i] = sq;

            ASSERT(board->pos[sq] == i - 1);
            board->pos[sq] = i;
            }

        board->pawn[colour][pos] = square;

        ASSERT(board->pos[square] == -1);
        board->pos[square] = pos;

        // pawn "bitboard"

        board->pawn_file[colour][SQUARE_FILE(square)] ^= BIT(PAWN_RANK(square, colour));
        }

    // material

    ASSERT(board->piece_nb < 32);
    board->piece_nb++;
    ;

    ASSERT(board->number[piece_12] < 9);
    board->number[piece_12]++;

    board->piece_material[colour] += VALUE_PIECE(piece);

    // update

    if( update )
        {

        // init

        sq_64 = SQUARE_TO_64(square);

        // PST

        board->opening += PST(piece_12, sq_64, Opening);
        board->endgame += PST(piece_12, sq_64, Endgame);

        // hash key

        hash_xor = RANDOM_64(RandomPiece + (piece_12 ^ 1) * 64 + sq_64);

        board->key ^= hash_xor;

        if( PIECE_IS_PAWN(piece) )
            board->pawn_key ^= hash_xor;

        // material key

        board->material_key ^= RANDOM_64(piece_12 * 16 + (board->number[piece_12] - 1));
        }
    }

// square_move()

static void square_move( board_t *board, int from, int to, int piece, bool update )
    {

    int colour;
    int pos;
    int from_64, to_64;
    int piece_12;
    int piece_index;
    uint64 hash_xor;

    ASSERT(board != NULL);
    ASSERT(SQUARE_IS_OK(from));
    ASSERT(SQUARE_IS_OK(to));
    ASSERT(piece_is_ok(piece));
    ASSERT(update == true || update == false);

    // init

    colour = PIECE_COLOUR(piece);

    pos = board->pos[from];
    ASSERT(pos >= 0);

    // from

    ASSERT(board->square[from] == piece);
    board->square[from] = Empty;

    ASSERT(board->pos[from] == pos);
    board->pos[from] = -1; // not needed

    // to

    ASSERT(board->square[to] == Empty);
    board->square[to] = piece;

    ASSERT(board->pos[to] == -1);
    board->pos[to] = pos;

    // piece list

    if( !PIECE_IS_PAWN(piece) )
        {

        ASSERT(board->piece[colour][pos] == from);
        board->piece[colour][pos] = to;
        }
    else
        {

        ASSERT(board->pawn[colour][pos] == from);
        board->pawn[colour][pos] = to;

        // pawn "bitboard"

        board->pawn_file[colour][SQUARE_FILE(from)] ^= BIT(PAWN_RANK(from, colour));
        board->pawn_file[colour][SQUARE_FILE(to)] ^= BIT(PAWN_RANK(to, colour));
        }

    // update

    if( update )
        {

        // init

        from_64 = SQUARE_TO_64(from);
        to_64 = SQUARE_TO_64(to);
        piece_12 = PIECE_TO_12(piece);

        // PST

        board->opening += PST(piece_12, to_64, Opening) - PST(piece_12, from_64, Opening);
        board->endgame += PST(piece_12, to_64, Endgame) - PST(piece_12, from_64, Endgame);

        // hash key

        piece_index = RandomPiece + (piece_12 ^ 1) * 64;

        hash_xor = RANDOM_64(piece_index + to_64) ^ RANDOM_64(piece_index + from_64);

        board->key ^= hash_xor;

        if( PIECE_IS_PAWN(piece) )
            board->pawn_key ^= hash_xor;
        }
    }

// end of move_do.cpp
