#include <assert.h>
#include <ctype.h>
#include <string.h>
#include "ramjet.h"
#include "bitboard.h"
#include "quadboard.h"

// set the whole quadboard to zero
void qb_clear_all(QUADBOARD* qb)
{
    qb->bb[0] = BB_NULL;
    qb->bb[1] = BB_NULL;
    qb->bb[2] = BB_NULL;
    qb->bb[3] = BB_NULL;
}

// set the specified squares to zero
void qb_clear_many(QUADBOARD* qb, BITBOARD bb)
{
    bb = ~bb;
    qb->bb[0] &= bb;
    qb->bb[1] &= bb;
    qb->bb[2] &= bb;
    qb->bb[3] &= bb;
}

// get the type of the piece on a particular square
int qb_get_sq_pt(const QUADBOARD* qb, SQUARE sq)
{
    assert(!IS_SQ_BAD(sq));

    // ignore the colour bit
    return 1 * ((qb->bb[0] >> sq) & 1)
         + 2 * ((qb->bb[1] >> sq) & 1)
         + 4 * ((qb->bb[2] >> sq) & 1);
}

// get the full code of the piece on a particular square
int qb_get_sq_pc(const QUADBOARD* qb, SQUARE sq)
{
    assert(!IS_SQ_BAD(sq));
    PIECETYPE pt = qb_get_sq_pt(qb, sq);
    PLAYER pl = (qb->bb[3] >> sq) & 1;
    return PT_2_PC(pt, pl);
}

// set the piece on a particular square
void qb_set_sq_pc(QUADBOARD* qb, SQUARE sq, int pc)
{
    assert(!IS_SQ_BAD(sq));
    assert(pc >= 1 && pc != 7 && pc <= 14);
    assert(qb_get_sq_pc(qb, sq) == PT_NULL);

    qb->bb[0] |= (BB_SQ(sq) & BB_ALL((pc >> 0) & 1));
    qb->bb[1] |= (BB_SQ(sq) & BB_ALL((pc >> 1) & 1));
    qb->bb[2] |= (BB_SQ(sq) & BB_ALL((pc >> 2) & 1));
    qb->bb[3] |= (BB_SQ(sq) & BB_ALL((pc >> 3) & 1));
}

// extract the total occupancy mask
BITBOARD qb_get_bb_tot(const QUADBOARD* qb)
{
    return qb->bb[0] | qb->bb[1] | qb->bb[2];
}

// extract the player's occupancy mask
BITBOARD qb_get_bb_pl(const QUADBOARD* qb, PLAYER pl)
{
    assert(!IS_PL_BAD(pl));
    return qb_get_bb_tot(qb) & BB_MASK_PL(qb, pl);
}

// extract all the pieces of a particular type (both colours)
BITBOARD qb_get_bb_pt(const QUADBOARD* qb, PIECETYPE pt)
{
    switch(pt) {
    case PT_NULL:
        return BB_NULL;
    case PAWN:
        return qb->bb[0] & (~qb->bb[1]) & (~qb->bb[2]);
    case KNIGHT:
        return (~qb->bb[0]) & qb->bb[1] & (~qb->bb[2]);
    case BISHOP:
        return qb->bb[0] & qb->bb[1];
    case ROOK:
        return (~qb->bb[0]) & (~qb->bb[1]) & qb->bb[2];
    case QUEEN:
        return qb->bb[0] & qb->bb[2];
    case KING:
        return qb->bb[1] & qb->bb[2];
    }
    return BB_NULL;
}

// return the king's square
int qb_get_k_sq(const QUADBOARD* qb, PLAYER pl)
{
    assert(!IS_PL_BAD(pl));
    return bb_lsb_sq(qb_get_bb_pt(qb, KING) & BB_MASK_PL(qb, pl));
}

// read the quadboard from a FEN string
void qb_scan_fen(const char* fen, QUADBOARD* qb)
{
    int i, sq, pc;

    qb_clear_all(qb);

    for(sq = 0, i = 0; fen[i] != '\0'; ++i) {

        // add the man and move on
        if((pc = char_2_pc[(int) fen[i]]) > 0) {
            qb_set_sq_pc(qb, sq, pc);
            ++sq;

        // skip multiple empty squares
        } else if(isdigit(fen[i]))
            sq += (fen[i] - '0');
    }
}

// convert the quadboard to a FEN string
void qb_print_fen(char* fen, const QUADBOARD* qb)
{
    int i, z, rk, sq, pc;

    // scan each rank counting empty squares and printing pieces
    for(i = 0, rk = 0; rk < 8; ++rk) {
        sq = rk << 3;
        z = 0;
        do {
            pc = qb_get_sq_pc(qb, sq);
            if(pc == PT_NULL)
                ++z;
            else {
                if(z > 0) {
                    fen[i++] = '0' + z;
                    z = 0;
                }
                fen[i++] = pc_2_char[pc];
            }
            ++sq;
        } while ((sq & 7));

        // end the rank rightly
        if(z > 0)
            fen[i++] = '0' + z;
        fen[i++] = '/';
    }

    // overwrite the last '/' with the string terminator
    fen[i - 1] = '\0';
}

// compute a hash digest of the quadboard
HASHKEY qb_hash(const QUADBOARD* qb)
{
    // random numbers from www.random.org
    static const HASHKEY chaos[8] = {HK_CONST(0x7cc3326f0834106e), HK_CONST(0x39e050ad702b1464),
                                     HK_CONST(0x8980701071b96b98), HK_CONST(0xe052d943853049b5),
                                     HK_CONST(0xa727464968cfa2b7), HK_CONST(0xc91823b0be41a498),
                                     HK_CONST(0x710603919283e031), HK_CONST(0xba425e7306c84750)};
    HASHKEY key = HK_NULL;
    static const BITBOARD low_32 = BB_CONST(0x00000000FFFFFFFF);
    int i;

    // since multiplication over-represents lower bits
    // we split each bitboard in two halves
    for(i = 0; i < 4; ++i) {
        key ^= (qb->bb[i] & low_32) * chaos[i];
        key ^= (qb->bb[i] >> 32) * chaos[i + 4];
    }

    return key;
}

// find the location of the attackers of a particular square
BITBOARD bb_atk_sq(const QUADBOARD* qb, SQUARE sq, PLAYER foe)
{
    assert(!IS_SQ_BAD(sq));
    assert(!IS_PL_BAD(foe));

    BITBOARD occ = qb_get_bb_tot(qb);
    BITBOARD all_q = qb_get_bb_pt(qb, QUEEN);

    return ((bb_p_atk(sq, FOE(foe)) & qb_get_bb_pt(qb, PAWN)) | // pawns
            (bb_n_leap[sq] & qb_get_bb_pt(qb, KNIGHT)) | // knights
            (bb_b_slide(occ, sq) & (qb_get_bb_pt(qb, BISHOP) | all_q)) | // bishops and queens
            (bb_r_slide(occ, sq) & (qb_get_bb_pt(qb, ROOK) | all_q)) | // rooks and queens
            (bb_k_leap[sq] & qb_get_bb_pt(qb, KING))) & BB_MASK_PL(qb, foe); // king
}

// king under check
int is_k_in_check(const QUADBOARD* qb, PLAYER pl)
{
    return (bb_atk_sq(qb, qb_get_k_sq(qb, pl), FOE(pl)))? 1 : 0;
}

// sanity check on the quadboard
int is_qb_bad(const QUADBOARD* qb)
{
    static const BITBOARD rank_18 = BB_CONST(0xFF000000000000FF);

    // no illegal piece codes
    BITBOARD type_seven = qb->bb[0] & qb->bb[1] & qb->bb[2];
    BITBOARD empty_black = ~qb_get_bb_tot(qb) & qb->bb[3];
    if(type_seven || empty_black)
        return 1;

    // one and only one king per side
    BITBOARD all_k = qb_get_bb_pt(qb, KING);
    if(!is_bb_single(all_k & BB_MASK_PL(qb, WHITE)) ||
       !is_bb_single(all_k & BB_MASK_PL(qb, BLACK)))
        return 2;

    // no pawns on the first and last ranks
    BITBOARD all_p = qb_get_bb_pt(qb, PAWN);
    if((all_p & rank_18))
        return 3;

    return 0;
}

// test the functions above
int qb_test()
{
    QUADBOARD qb, qb2, qb3, qb4;

    // square getter and setter
    qb_clear_all(&qb);
    qb_set_sq_pc(&qb, D4, PT_2_PC(BISHOP, WHITE));
    qb_set_sq_pc(&qb, H7, PT_2_PC(PAWN, BLACK));
    qb_clear_many(&qb, BB_SQ(B3) | BB_SQ(D4));
    if(qb.bb[0] != BB_SQ(H7) || qb.bb[1] != BB_NULL ||
       qb.bb[2] != BB_NULL || qb.bb[3] != BB_SQ(H7) ||
       qb_get_sq_pc(&qb, H7) != PT_2_PC(PAWN, BLACK))
        return 1;

    // bitboard getters, king's square
    qb_clear_all(&qb);
    qb_set_sq_pc(&qb, B7, PT_2_PC(PAWN, WHITE));
    qb_set_sq_pc(&qb, C6, PT_2_PC(KNIGHT, BLACK));
    qb_set_sq_pc(&qb, D5, PT_2_PC(BISHOP, WHITE));
    qb_set_sq_pc(&qb, E4, PT_2_PC(ROOK, BLACK));
    qb_set_sq_pc(&qb, F3, PT_2_PC(QUEEN, WHITE));
    qb_set_sq_pc(&qb, G2, PT_2_PC(KING, BLACK));
    if(qb_get_bb_tot(&qb) != BB_CONST(0x0040201008040200) ||
       qb_get_bb_pl(&qb, WHITE) != BB_CONST(0x0000200008000200) ||
       qb_get_bb_pl(&qb, BLACK) != BB_CONST(0x0040001000040000) ||
       qb_get_bb_pt(&qb, PAWN) != BB_SQ(B7) ||
       qb_get_bb_pt(&qb, KNIGHT) != BB_SQ(C6) ||
       qb_get_bb_pt(&qb, BISHOP) != BB_SQ(D5) ||
       qb_get_bb_pt(&qb, ROOK) != BB_SQ(E4) ||
       qb_get_bb_pt(&qb, QUEEN) != BB_SQ(F3) ||
       qb_get_bb_pt(&qb, KING) != BB_SQ(G2) ||
       qb_get_k_sq(&qb, BLACK) != G2)
        return 2;

    // input-output in FEN format
    char fen[128] = "7R/8/1p2b3/1P3k2/P4P1K/n7/8/2q5";
    qb_scan_fen(fen, &qb);
    qb_print_fen(fen, &qb);
    if(qb_get_bb_pl(&qb, WHITE) != BB_CONST(0x000000A102000080) ||
       qb_get_bb_pl(&qb, BLACK) != BB_CONST(0x0400010020120000) ||
       qb_get_k_sq(&qb, WHITE) != H4 || qb_get_k_sq(&qb, BLACK) != F5 ||
       strcmp(fen, "7R/8/1p2b3/1P3k2/P4P1K/n7/8/2q5"))
        return 3;

    // attacked square
    qb_scan_fen("6R1/8/1p2b3/1P3k2/P4P1K/n7/8/2q5", &qb);
    if(bb_atk_sq(&qb, G5, WHITE) != (BB_SQ(G8) | BB_SQ(F4) | BB_SQ(H4)) ||
       bb_atk_sq(&qb, C4, BLACK) != (BB_SQ(E6) | BB_SQ(A3) | BB_SQ(C1)))
        return 4;

    // sanity check
    qb_scan_fen("7R/8/1p2b3/1P3k2/P4P1K/n7/8/2q5", &qb);
    qb2 = qb; qb2.bb[2] |= BB_SQ(E6);
    qb_scan_fen("7R/8/1p2b3/1P6/P4P1K/n7/8/2q5", &qb3);
    qb_scan_fen("p6R/8/1p2b3/1P3k2/P4P1K/n7/8/2q5", &qb4);
    if(is_qb_bad(&qb) || is_qb_bad(&qb2) != 1 || is_qb_bad(&qb3) != 2 || is_qb_bad(&qb4) != 3)
        return 5;

    return 0;
}
