#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "ramjet.h"
#include "quadboard.h"
#include "state.h"
#include "move.h"
#include "position.h"

// make the position empty
void pos_clear(POSITION* pos)
{
    qb_clear_all(&pos->qb);
    pos->st = ST_NULL;
    pos->lmv = MV_NULL;
    pos->key = HK_NULL;
}

// compute a hash digest of the position
HASHKEY pos_hash(const POSITION* pos)
{
    return qb_hash(&pos->qb) ^ st_hash(pos->st);
}

// read the position from a FEN string
void pos_scan_fen(char* fen, POSITION* pos)
{
    qb_scan_fen(grab_token(&fen), &pos->qb);
    pos->st = st_scan_fen(fen, &pos->qb);
    pos->lmv = MV_NULL;
    pos->key = pos_hash(pos);
}

// convert the position into a FEN string
void pos_print_fen(char* fen, const POSITION* pos)
{
    qb_print_fen(fen, &pos->qb);
    st_print_fen(append_space(fen), &pos->qb, pos->st);
}

// execute the move on the given position
void pos_make_mv(POSITION* pos, MOVE mv)
{
    assert(!is_mv_bad(&pos->qb, pos->st, mv));

    BITBOARD occ = qb_get_bb_tot(&pos->qb);
    SQUARE from = MV_GET_FROM(mv);
    SQUARE to = MV_GET_TO(mv);
    int pc = qb_get_sq_pc(&pos->qb, from);
    PIECETYPE pt = PC_2_PT(pc);
    PLAYER pl = ST_GET_PL(pos->st);

    // state initialisation
    pos->st = ST_TOGGLE_PL(pos->st);
    pos->st = ST_CLEAR_EP(pos->st);
    pos->st = ST_INC_50(pos->st);

    // standard move, capture or promotion
    if(!IS_MV_SPECIAL(mv)) {

        // clear the moving piece and the potential victim
        qb_clear_many(&pos->qb, BB_SQ(from) | BB_SQ(to));

        // set the (promoted) piece on the new square
        if(IS_MV_PROMO(mv))
            pc = PT_2_PC(MV_GET_PROMO(mv), pl);
        qb_set_sq_pc(&pos->qb, to, pc);

        // reset the 50-move counter after a capture or pawn move
        if((occ & BB_SQ(to)) || pt == PAWN)
            pos->st = ST_CLEAR_50(pos->st);

        // set the ep target square on a pawn double push
        if(pt == PAWN && IS_MV_FRONT_2(mv, pl))
            pos->st = st_set_true_ep(pos->st, &pos->qb, SQ_FRONT(from, pl));

        // lose castling rights if king move, rook move (from) or rook captured (to)
        if(pt == KING)
            pos->st = ST_CLEAR_CASTLE_PL(pos->st, pl);
        else
            pos->st = ST_CLEAR_CASTLE_SQ(pos->st, from);
        pos->st = ST_CLEAR_CASTLE_SQ(pos->st, to);

    // en-passant capture
    } else if(pt == PAWN) {
        qb_clear_many(&pos->qb, BB_SQ(from) | BB_SQ(SQ_FRONT(to, FOE(pl))));
        qb_set_sq_pc(&pos->qb, to, pc);
        pos->st = ST_CLEAR_50(pos->st);

    // castle
    } else {
        qb_clear_many(&pos->qb, BB_SQ(from) | BB_SQ(to));
        qb_set_sq_pc(&pos->qb, SQ_K_CASTLE(from < to, pl), pc);
        qb_set_sq_pc(&pos->qb, SQ_R_CASTLE(from < to, pl), PT_2_PC(ROOK, pl));
        pos->st = ST_CLEAR_CASTLE_PL(pos->st, pl);
    }

    // end it rightly
    pos->lmv = mv;
    pos->key = pos_hash(pos);
}

// execute a null move on the given position
void pos_make_null_mv(POSITION* pos)
{
    pos->st = ST_TOGGLE_PL(pos->st);
    pos->st = ST_CLEAR_EP(pos->st);
    pos->st = ST_CLEAR_50(pos->st);
    pos->lmv = MV_NULL;
    pos->key = pos_hash(pos);
}

// copy the position, execute the move, return legality
int pos_copy_make_mv(const POSITION* curr, POSITION* next, MOVE mv)
{
    *next = *curr;
    pos_make_mv(next, mv);
    return is_mv_legal(&curr->qb, &next->qb, mv);
}

// copy the position, execute a null move, return legality
void pos_copy_make_null_mv(const POSITION* curr, POSITION* next)
{
    *next = *curr;
    pos_make_null_mv(next);
}

// generate the list of pseudo-legal moves
int pos_gen_mv(const POSITION* pos, MOVE* mv)
{
    assert(!is_pos_bad(pos));

    static const BITBOARD rank_18 = BB_CONST(0xFF000000000000FF);
    static const BITBOARD rank_1 = BB_CONST(0xFF00000000000000);

    PLAYER pl = ST_GET_PL(pos->st);

    BITBOARD occ_tot = qb_get_bb_tot(&pos->qb);
    BITBOARD occ_pl = occ_tot & BB_MASK_PL(&pos->qb, pl);
    BITBOARD occ_foe = occ_tot & ~occ_pl;
    BITBOARD source, target;
    BITBOARD all_q = qb_get_bb_pt(&pos->qb, QUEEN);

    SQUARE from, to;
    int ep, n = 0;

    // pawn pushes
    source = qb_get_bb_pt(&pos->qb, PAWN) & occ_pl;
    target = bb_front(source, pl) & ~occ_tot;
    n += mv_unroll_bb_diff(&mv[n], target & ~rank_18, SQ_FRONT(0, pl));
    n += mv_unroll_bb_promo(&mv[n], target & rank_18, SQ_FRONT(0, pl));
    target = bb_front_45(target, pl) & ~occ_tot;
    n += mv_unroll_bb_diff(&mv[n], target & ~rank_18, 2 * SQ_FRONT(0, pl));

    // pawn captures
    target = bb_dia_w(source, pl) & occ_foe;
    n += mv_unroll_bb_diff(&mv[n], target & ~rank_18, SQ_DIA_W(0, pl));
    n += mv_unroll_bb_promo(&mv[n], target & rank_18, SQ_DIA_W(0, pl));
    target = bb_dia_e(source, pl) & occ_foe;
    n += mv_unroll_bb_diff(&mv[n], target & ~rank_18, SQ_DIA_E(0, pl));
    n += mv_unroll_bb_promo(&mv[n], target & rank_18, SQ_DIA_E(0, pl));

    // en-passant capture
    ep = ST_GET_EP(pos->st);
    source &= bb_ep_atk[ep];
    while(source)
        mv[n++] = MV_SET_SPECIAL(MV_NEW(bb_lsb_next(&source), ep));

    // knights
    source = qb_get_bb_pt(&pos->qb, KNIGHT) & occ_pl;
    while(source) {
        from = bb_lsb_next(&source);
        n += mv_unroll_bb_from(&mv[n], bb_n_leap[from] & ~occ_pl, from);
    }

    // bishops and queens
    source = (all_q | qb_get_bb_pt(&pos->qb, BISHOP)) & occ_pl;
    while(source) {
        from = bb_lsb_next(&source);
        n += mv_unroll_bb_from(&mv[n], bb_b_slide(occ_tot, from) & ~occ_pl, from);
    }

    // rooks and queens
    source = (all_q | qb_get_bb_pt(&pos->qb, ROOK)) & occ_pl;
    while(source) {
        from = bb_lsb_next(&source);
        n += mv_unroll_bb_from(&mv[n], bb_r_slide(occ_tot, from) & ~occ_pl, from);
    }

    // king
    from = qb_get_k_sq(&pos->qb, pl);
    n += mv_unroll_bb_from(&mv[n], bb_k_leap[from] & ~occ_pl, from);

    // castling
    STATE castle = ST_GET_CASTLE_PL(pos->st, pl);
    while(castle) {
        to = st_castle_sq_next(&castle);
        if((bb_castle_occ[SQ_FILE(from)][SQ_FILE(to)] & (rank_1 ^ BB_ALL(pl)) & occ_tot) == BB_NULL)
            mv[n++] = MV_SET_SPECIAL(MV_NEW(from, to));
    }

    return n;
}

// count the leaves at the specified depth of the game tree
NODECOUNT pos_leaf_count(const POSITION* pos, int depth)
{
    MOVE mv[256];
    POSITION next;
    NODECOUNT nc = 0;
    int i, n;

    if(depth <= 0)
        return 1;

    n = pos_gen_mv(pos, mv);
    for(i = 0; i < n; ++i)
        if(pos_copy_make_mv(pos, &next, mv[i]))
            nc += pos_leaf_count(&next, depth - 1);

    return nc;
}

// sanity check on the position
int is_pos_bad(const POSITION* pos)
{
    if(is_qb_bad(&pos->qb)) // quadboard
        return 1;
    if(is_st_bad(&pos->qb, pos->st)) // state
        return 2;
    if(pos_hash(pos) != pos->key) // hashkey
        return 3;

    // the last move field is not checked
    return 0;
}

// test the functions above
int pos_test()
{
    MOVE mv[256]; char fen[128]; POSITION pos; int c960 = 0;

    // input/output in FEN format
    strcpy(fen, "r3kb2/1p1q1p1p/p1n3r1/3Nb3/2N1p3/2P5/PPp2PPP/R2QKB1R w KQq - 2 15");
    pos_scan_fen(fen, &pos);
    pos_print_fen(fen, &pos);
    if(strcmp(fen, "r3kb2/1p1q1p1p/p1n3r1/3Nb3/2N1p3/2P5/PPp2PPP/R2QKB1R w QKq - 2"))
        return 1;

    // make move
    strcpy(fen, "r3kb2/1p1q1p1p/p1n3r1/3Nb3/2N1p3/2P5/PPp2PPP/R2QKB1R w KQq - 2 15");
    pos_scan_fen(fen, &pos);
    pos_make_mv(&pos, mv_scan_uci("f2f4", &pos.qb, pos.st, c960));
    pos_make_mv(&pos, mv_scan_uci("e4f3", &pos.qb, pos.st, c960));
    pos_make_mv(&pos, mv_scan_uci("d1f3", &pos.qb, pos.st, c960));
    pos_make_mv(&pos, mv_scan_uci("c2c1q", &pos.qb, pos.st, c960));
    pos_make_mv(&pos, mv_scan_uci("a1c1", &pos.qb, pos.st, c960));
    pos_make_mv(&pos, mv_scan_uci("e8c8", &pos.qb, pos.st, c960));
    pos_print_fen(fen, &pos);
    if(strcmp(fen, "2kr1b2/1p1q1p1p/p1n3r1/3Nb3/2N5/2P2Q2/PP4PP/2R1KB1R w K - 1"))
        return 2;

    //
    //pos_copy_make_mv

    // generate moves;
    strcpy(fen, "r2Bk2r/pb3n2/3q4/3Pp2p/Pp2P2P/1K3P2/5pp1/6N1 b kq a3 0 42");
    pos_scan_fen(fen, &pos);
    if(pos_gen_mv(&pos, mv) != 45)
        return 3;

    return 0;
}

// test the tree-generating routines
int pos_perft(const char* filename)
{
    char fen[128], buf1[16], buf2[16];
    FILE* fp;
    POSITION pos;
    NODECOUNT nc1, nc2;
    int i, depth;

    if((fp = fopen(filename, "r")) == NULL)
        return -1;

    printf(">>> perft on %s:\n", filename);

    // the file format is in triplets "fen\ndepth\nnodes\n"
    for(i = 1; fgets(fen, 128, fp) != NULL; ++i) {
        fgets(buf1, 16, fp);
        fgets(buf2, 16, fp);
        sscanf(buf1, "%d", &depth);
        sscanf(buf2, "%"NC_SCAN, &nc1);

        printf("%s depth %d -> ", fen, depth);

        // compute the perft
        pos_scan_fen(fen, &pos);
        if(!is_pos_bad(&pos) && depth >= 0)
            nc2 = pos_leaf_count(&pos, depth);
        else {
            printf("(error)\n");
            return i;
        }

        // compare the result
        if(nc1 != nc2) {
            printf("%"NC_PRINT" (failed)\n", nc2);
            fclose(fp);
            return i;
        } else
            printf("%"NC_PRINT"\n", nc2);
    }

    fclose(fp);
    return 0;
}
