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

// read a move from the UCI string
MOVE mv_scan_uci(const char* uci, const QUADBOARD* qb, STATE st, int c960)
{
    assert(!is_qb_bad(qb));
    assert(!is_st_bad(qb, st));

    int from = STR_2_SQ(&uci[0]);
    int to = STR_2_SQ(&uci[2]);
    int diff = from - to;
    PIECETYPE pt_from = qb_get_sq_pt(qb, from);
    PIECETYPE pt_promo = PC_2_PT(char_2_pc[(int) uci[4]]);

    // protection
    if(IS_SQ_BAD(from) || IS_SQ_BAD(to) || pt_from == PT_NULL)
        return MV_NULL;

    // standard move (include captures)
    MOVE mv = MV_NEW(from, to);

    // en-passant or promotion
    if(pt_from == PAWN) {
        if(to == ST_GET_EP(st))
            mv = MV_SET_SPECIAL(mv);
        else if(pt_promo)
            mv = MV_SET_PROMO(mv, pt_promo);

    // castling
    } else if(pt_from == KING) {
        if(c960 && qb_get_sq_pc(qb, to) == PT_2_PC(ROOK, ST_GET_PL(st)))
            mv = MV_SET_SPECIAL(mv);
        else if(diff == E1 - C1)
            mv = MV_SET_SPECIAL(MV_NEW(from, from - E1 + A1)); // queenside
        else if(diff == E1 - G1)
            mv = MV_SET_SPECIAL(MV_NEW(from, from - E1 + H1)); // kingside
    }

    return mv;
}

// convert the move to a UCI string
void mv_print_uci(char* uci, const QUADBOARD* qb, STATE st, int c960, MOVE mv)
{
    assert(!is_qb_bad(qb));
    assert(!is_st_bad(qb, st));
    assert(!is_mv_bad(qb, st, mv));

    SQUARE from = MV_GET_FROM(mv);
    SQUARE to = MV_GET_TO(mv);

    // standard move
    sprintf(uci, "%s%s", sq_2_str[from], sq_2_str[to]);

    // promotion (append)
    if(IS_MV_PROMO(mv)) {
        uci[4] = PT_2_CHAR(MV_GET_PROMO(mv));
        uci[5] = '\0';
    }

    // regular castle (overwrite)
    if(IS_MV_SPECIAL(mv) && qb_get_sq_pt(qb, from) == KING && !c960) {
        to = SQ_K_CASTLE((from < to), ST_GET_PL(st));
        sprintf(uci, "%s%s", sq_2_str[from], sq_2_str[to]);
    }
}

// serialise a map of target squares in a list of moves (single source)
int mv_unroll_bb_from(MOVE* mv, BITBOARD bb, int from)
{
    int n = 0;
    while(bb)
        mv[n++] = MV_NEW(from, bb_lsb_next(&bb));
    return n;
}

// serialise a map of target squares in a list of moves (same from-to difference)
int mv_unroll_bb_diff(MOVE* mv, BITBOARD bb, int diff)
{
    int n = 0;
    while(bb) {
        int to = bb_lsb_next(&bb);
        mv[n++] = MV_NEW(to - diff, to);
    }
    return n;
}

// serialise a map of promotion squares in a list of moves (same from-to difference)
int mv_unroll_bb_promo(MOVE* mv, BITBOARD bb, int diff)
{
    int n = 0;
    while(bb) {
        int to = bb_lsb_next(&bb);
        MOVE plain = MV_NEW(to - diff, to);
        mv[n + 0] = MV_SET_PROMO(plain, QUEEN);
        mv[n + 1] = MV_SET_PROMO(plain, ROOK);
        mv[n + 2] = MV_SET_PROMO(plain, BISHOP);
        mv[n + 3] = MV_SET_PROMO(plain, KNIGHT);
        n += 4;
    }
    return n;
}

// check the legality of the move
int is_mv_legal(const QUADBOARD* curr, const QUADBOARD* next, MOVE mv)
{
    SQUARE from = MV_GET_FROM(mv);
    SQUARE to = MV_GET_TO(mv);
    int pc = qb_get_sq_pc(curr, from);
    PLAYER pl = PC_2_PL(pc);
    PLAYER foe = FOE(pl);

    // king left under check
    if(is_k_in_check(next, pl))
        return 0;

    // castling was illegal
    if(IS_MV_SPECIAL(mv) && PC_2_PT(pc) == KING) {
        BITBOARD mask = BB_CASTLE_CHECK(from, SQ_K_CASTLE(from < to, pl));
        while(mask)
            if(bb_atk_sq(curr, bb_lsb_next(&mask), foe))
                return 0;
    }

    return 1;
}

// sanity check on the move
int is_mv_bad(const QUADBOARD* qb, STATE st, MOVE mv)
{
    assert(!is_qb_bad(qb));
    assert(!is_st_bad(qb, st));

    static const BITBOARD rank_1 = BB_CONST(0xFF00000000000000);

    SQUARE from = MV_GET_FROM(mv);
    SQUARE to = MV_GET_TO(mv);
    PIECETYPE pt = qb_get_sq_pt(qb, from);
    PLAYER pl = ST_GET_PL(st);
    BITBOARD mask, occ = qb_get_bb_tot(qb);
    BITBOARD occ_pl = occ & BB_MASK_PL(qb, pl);

    // wrong colour or empty square
    if((occ_pl & BB_SQ(from)) == BB_NULL)
        return 1;

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

        // generate the set of pseudo-legal target squares
        switch(pt) {
        case PAWN:
            mask = (bb_p_atk(from, pl) & ~occ_pl) | bb_p_push(occ, from, pl);
            break;
        case KNIGHT:
            mask = bb_n_leap[from] & ~occ_pl;
            break;
        case BISHOP:
            mask = bb_b_slide(occ, from) & ~occ_pl;
            break;
        case ROOK:
            mask = bb_r_slide(occ, from) & ~occ_pl;
            break;
        case QUEEN:
            mask = (bb_b_slide(occ, from) | bb_r_slide(occ, from)) & ~occ_pl;
            break;
        case KING:
            mask = bb_k_leap[from] & ~occ_pl;
            break;
        default:
            return 7; // impossible to reach
        }

        // wrong target square or self-capture
        if((mask & BB_SQ(to)) == BB_NULL)
            return 2;

        // non-pawn promotion
        if(IS_MV_PROMO(mv) && pt != PAWN)
            return 3;

    // wrong en-passant capture
    } else if(pt == PAWN) {
        int ep = ST_GET_EP(st);
        if(IS_SQ_BAD(ep) || (bb_ep_atk[ep] & BB_SQ(from)) == BB_NULL)
            return 4;

    // wrong castle
    } else if(pt == KING) {
        if(!IS_ST_CASTLE_SQ(st, to) ||
           (bb_castle_occ[SQ_FILE(from)][SQ_FILE(to)] & (rank_1 ^ BB_ALL(pl)) & occ))
            return 5;

    // wrong special move
    } else
        return 6;

    return 0;
}

// test the functions above
int mv_test()
{
    char uci1[8], uci2[8], uci3[8], fen[16];
    QUADBOARD qb, qb2, qb3, qb4, qb5; STATE st;
    int c960 = 1;

    // getters, setters and testers
    MOVE mv = MV_SET_SPECIAL(MV_SET_PROMO(MV_NEW(B2, F4), BISHOP));
    if(MV_GET_TO(mv) != F4 || MV_GET_FROM(mv) != B2 || MV_GET_PROMO(mv) != BISHOP ||
       !IS_MV_PROMO(mv) || !IS_MV_SPECIAL(mv) || !IS_MV_FRONT_2(MV_NEW(E7, E5), BLACK))
        return 1;

    // input-output in UCI format
    qb_scan_fen("rn2k1r1/p1p1pp1P/3p2p1/Pp3bn1/8/2N2B2/1PPPPP2/2BNK1RR", &qb);
    strcpy(fen, "w Gkq b6 4 11"); st = st_scan_fen(fen, &qb);
    mv_print_uci(uci1, &qb, st, c960, mv_scan_uci("e1g1", &qb, st, c960));
    mv_print_uci(uci2, &qb, st, c960, mv_scan_uci("a5b6", &qb, st, c960));
    mv_print_uci(uci3, &qb, st, c960, mv_scan_uci("h7g8r", &qb, st, c960));
    if(strcmp(uci1, "e1g1") || strcmp(uci2, "a5b6") || strcmp(uci3, "h7g8r"))
        return 2;

    // move legality
    qb_scan_fen("1r4k1/6pb/5p1p/1Pp1p3/8/4PP2/2N3PP/RK3R1r", &qb);
    strcpy(fen, "w KQ c6 4 11"); st = st_scan_fen(fen, &qb);
    qb_scan_fen("1r4k1/6pb/5p1p/1Pp1p3/1N6/4PP2/6PP/RK3R1r", &qb2); // c2b4
    qb_scan_fen("1r4k1/6pb/2P2p1p/4p3/8/4PP2/2N3PP/RK3R1r", &qb3); // b5c6
    qb_scan_fen("1r4k1/6pb/5p1p/1Pp1p3/8/4PP2/2N3PP/2KR1R1r", &qb4); // b1a1
    qb_scan_fen("1r4k1/6pb/5p1p/1Pp1p3/8/4PP2/2N3PP/R4RKr", &qb5); // b1f1
    if(is_mv_legal(&qb, &qb2, mv_scan_uci("c2b4", &qb, st, c960)) ||
       is_mv_legal(&qb, &qb3, mv_scan_uci("b5c6", &qb, st, c960)) ||
       !is_mv_legal(&qb, &qb4, mv_scan_uci("b1a1", &qb, st, c960)) ||
       is_mv_legal(&qb, &qb5, mv_scan_uci("b1f1", &qb, st, c960)))
        return 3;

    // sanity check
    qb_scan_fen("rn2k1r1/p1p1pp1P/3p2p1/Pp3bn1/8/2N2B2/1PPPPP2/2BNKQRR", &qb);
    strcpy(fen, "w Gkq b6 4 11"); st = st_scan_fen(fen, &qb);
    if(is_mv_bad(&qb, st, MV_NEW(F5, C2)) != 1 ||
       is_mv_bad(&qb, st, MV_NEW(G1, H1)) != 2 ||
       is_mv_bad(&qb, st, MV_SET_PROMO(MV_NEW(F3, A8), KNIGHT)) != 3 ||
       is_mv_bad(&qb, st, MV_SET_SPECIAL(MV_NEW(B2, B6))) != 4 ||
       is_mv_bad(&qb, st, MV_SET_SPECIAL(MV_NEW(E1, G1))) != 5 ||
       is_mv_bad(&qb, st, MV_SET_SPECIAL(MV_NEW(D1, E3))) != 6)
        return 4;

    return 0;
}
