#include "bitkpk.h"
#include "bitmagic.h"
#include <stdio.h>
#include <memory.h>

// BitKPK

// idea: will only store a bit mask with drawish positions
// if it's not a draw, the side with the pawn wins (big score)

// new: generated hdr => integrate into code
#include "kpk.h"

enum kpk_Index_Size
{
    kpkIndexSize	=	64 * 64 * 48 * 2
};

BitKPK::BitKPK() : temp(0), bitmap(0)
{
    bitmap = new u8[ kpkSize ];
    if ( bitmap )
    {
        memcpy( bitmap, kpk, kpkSize );
    }
}

BitKPK::~BitKPK()
{
    delete[] bitmap;
}

void BitKPK::generate()
{
    // 64*64*48*2
    // 48: pawn_pos
    // 64: king_pos
    // 64: king_pos
    // 2: turn
    temp = new i8[ kpkIndexSize ];
    memset( temp, -2, kpkIndexSize );		// -2 = unknown, -3 = invalid

    invalid = wins = draws = 0;
    remaining = kpkIndexSize;

    // first mark invalid positions
    i32 i;
    for (i=0; i< kpkIndexSize; i++ )
    {
        u8 turn, wk, bk, wp;
        posFromIndex( i, turn, wk, bk, wp );
        if ( wk == bk || wk == wp || bk == wp || (kingMov[ wk ] & ONESHL( bk )) != 0 )
        {
            temp[i] = -3;		// invalid!
            invalid++;
            remaining--;
            continue;
        }
        // now check for immediate win/draw
        if ( turn == white && (wp >> 3) == 1 )
        {
            // pawn on promoting rank
            u8 promosq = wp - 8;
            u64 attm = kingMov[ wk ];
            u64 defm = kingMov[ bk ];		// defensive moves mask
            defm &= ~attm;
            u64 chkm = ONESHL( bk );
            if ( !(ONESHL( promosq ) & kingMov[ bk ]) || (ONESHL( promosq ) & kingMov[ wk ]) )
            {
                // ok, enemy king doesn't guard promoting square OR it's guarded by our king so he won't be able to recapture
                if ( wk != promosq && bk != promosq )
                {
                    // promotion square clear
                    // verify that promoting to queen/rook won't result in a stalemate...
                    u64 qam = queenAttm( promosq, ONESHL( wk ) | ONESHL( bk ) );
                    u64 ram = rookAttm( promosq, ONESHL( wk ) | ONESHL( bk ) );
                    // if it's a checking move OR it leaves some valid moves to enemy king, it's a win
                    if ( (qam & chkm) || (defm & ~qam) || (defm & ~ram) )
                    {
                        // it's a win!
                        temp[ i ] = 1;
                        wins++;
                        remaining--;
                        continue;
                    }
                }
            }
        }
        if ( turn == black )
        {
            // checking for stalemate:
            // if we can't move it's a draw
            u64 pattm = bpAttm[ wp ];		// FIXME: bpAttm sucks as gives attackmask for white pawns... design error
            u64 defm = kingMov[ bk ];
            defm &= ~(kingMov[ wk ] | pattm);
            // defm is now legal moves for black
            // if it can capture a pawn it's a draw
            if ( (defm & ONESHL( wp )) != 0 )
            {
                if ( !(kingMov[ wk ] & ONESHL( wp )) )
                {
                    // can capture => force draw!
                    defm = 0;
                }
            }
            if ( !defm )
            {
                temp[ i ] = 0;
                draws++;
                remaining--;
                continue;
            }
        }

        ASSERT( wp >= 8 && wp < 64-8 );
    }

    while ( remaining > 0 )
    {
        i32 oremaining = remaining;
        iterate();
        if ( oremaining == remaining )
            break;
    }

    if ( remaining > 0 )
    {
        // we have a bug here => we didn't recognise a draw/win...
        // ok, not necessarily, the rest probably means draw by repetition for neither side can force a win/draw by stalemate
        for ( i=0; i<kpkIndexSize; i++ )
        {
            if ( temp[ i ] != -2 )
                continue;
            temp[ i ] = 0;
            remaining--;
            /*			BitBoard b;
            			boardFromIndex( b, i );

            			b.dump();*/
        }
    }

    delete bitmap;
    bitmap = new u8[ (kpkIndexSize + 7) / 8 ];
    memset( bitmap, 0, (kpkIndexSize + 7) / 8 );

    // now pack draw_result into bitmap
    for (i=0; i<kpkIndexSize; i++)
    {
        if ( temp[i] == 0 || temp[i] == -3 )
        {
            bitmap[ (i>>3) ] |= 1 << (i & 7);
        }
    }

    delete temp;
}

i32 BitKPK::probe(const BitBoard &b) const
{
    if ( bitmap )
    {
        const BitFlags &bf = b.flags();
        if ( b.bits[ wpawn ] )
        {
            // easy -- white_pawn variant
            u8 turn = bf.turn;
            u8 wk = bf.kingPos[ white ];
            u8 bk = bf.kingPos[ black ];
            u8 wp = getLSB( b.bits[ wpawn ] );

            i32 index = indexFromPos( turn, wk, bk, wp );
            // query bitmap
            bool isdraw = (bitmap[ index >> 3 ] & (1 << (index & 7) )) != 0;
            return isdraw ? 0 : (turn == white ? 1 : -1);
        }
        else
        {
            // must flip sides!
            // TODO: VERIFY!
            u8 turn = bf.turn ^ 1;
            u8 wk = bf.kingPos[ black ] ^ 0x38;
            u8 bk = bf.kingPos[ white ] ^ 0x38;
            u8 wp = getLSB( b.bits[ bpawn ] ) ^ 0x38;

            i32 index = indexFromPos( turn, wk, bk, wp );
            // query bitmap
            bool isdraw = (bitmap[ index >> 3 ] & (1 << (index & 7) )) != 0;
            return isdraw ? 0 : (turn == white ? 1 : -1);
        }
    }
    return -2;
}

i32 BitKPK::indexFromPos( u8 turn, u8 wk, u8 bk, u8 wp ) const
{
    return (wk & 63) | ((bk & 63) << 6) | ((turn & 1) << 12) | ((wp - 8) << 13);
}

void BitKPK::posFromIndex( i32 index, u8 &turn, u8 &wk, u8 &bk, u8 &wp ) const
{
    wk = index & 63;
    bk = (index >> 6) & 63;
    turn = (index >> 12) & 1;
    wp = ((index >> 13) + 8) & 63;
}

void BitKPK::iterate()
{
    i32 i;
    u8 turn, wk, bk, wp;
    for (i=0; i< kpkIndexSize; i++ )
    {
        if ( temp[i] != -2 )
            continue;					// position already known
        // now try to generate all legal moves for this position
        posFromIndex( i, turn, wk, bk, wp );
        if ( turn == white )
        {
            // white to move
            bool canWin = 0;
            u64 kingm = kingMov[ wk ] & ~kingMov[ bk ] & NONESHL( wp );
            u8 pos[64];
            i32 np = getPos( kingm, pos );
            i32 j;
            i32 ldraws = 0;
            // king moves first
            for (j=0; j<np; j++)
            {
                u8 nwk = pos[j];
                // compute new index
                i32 ni = indexFromPos( turn ^ 1, nwk, bk, wp );
                ASSERT( temp[ ni ] != 1 && temp[ ni ] != -3 );
                if ( temp[ ni ] != -2 )
                {
                    if ( temp[ ni ] == -1 )
                    {
                        // it's a win -- nothing can be better for white!
                        canWin = 1;
                        break;
                    }
                    else if ( temp[ ni ] == 0 )
                    {
                        ldraws++;
                    }
                }
            }
            // now pawn moves
            u8 wpn = wp - 8;
            if ( wpn >= 8 )
            {
                // check pawn push (1)
                if ( wk != wpn && bk != wpn )
                {
                    // compute new index
                    i32 ni = indexFromPos( turn ^ 1, wk, bk, wpn );
                    ASSERT( temp[ ni ] != 1 && temp[ ni ] != -3 );
                    if ( temp[ ni ] == -1 )
                    {
                        canWin = 1;
                    }
                    else if ( temp[ ni ] == 0 )
                    {
                        ldraws++;
                    }
                    np++;
                }
            }
            if ( (wp >> 3) == 6 )
            {
                u8 wpn = wp - 16;
                // check pawn push (2)
                if ( wk != wpn && bk != wpn && wk != wp + 8 && bk != wp + 8 )
                {
                    // compute new index
                    i32 ni = indexFromPos( turn ^ 1, wk, bk, wpn );
                    ASSERT( temp[ ni ] != 1 && temp[ ni ] != -3 );
                    if ( temp[ ni ] == -1 )
                    {
                        canWin = 1;
                    }
                    else if ( temp[ ni ] == 0 )
                    {
                        ldraws++;
                    }
                    np++;
                }
            }
            if ( canWin )
            {
                temp[ i ] = 1;
                wins++;
                remaining--;
                continue;
            }
            if ( ldraws == np )
            {
                // FIXME: what if pawn is on promo_rank?!
                // it's a draw...
                temp[ i ] = 0;
                draws++;
                remaining--;
                continue;
            }
        }
        else
        {
            // black to move
            u64 pattm = bpAttm[ wp ];		// FIXME: bpAttm sucks as gives attackmask for white pawns... design error
            u64 defm = kingMov[ bk ];
            defm &= ~(kingMov[ wk ] | pattm);
            ASSERT( defm );
            u8 pos[64];
            i32 np = getPos( defm, pos );
            i32 j;
            bool canDraw = !np;
            i32 losses = 0;
            for (j=0; j<np; j++)
            {
                u8 nbk = pos[j];
                // compute new index
                i32 ni = indexFromPos( turn ^ 1, wk, nbk, wp );
                if ( temp[ ni ] != -2 )
                {
                    // have_result!
                    if ( temp[ ni ] == 1 )
                    {
                        losses++;
                    }
                    else if ( temp[ ni ] == 0 )
                    {
                        // can draw -- nothing can be better for black!
                        canDraw = 1;
                        break;
                    }
                    ASSERT( temp[ ni ] != -1 && temp[ ni ] != -3 );
                }
            }
            if ( canDraw )
            {
                // drawn...
                temp[ i ] = 0;
                remaining--;
                draws++;
            }
            if ( np == losses )
            {
                // lost...
                temp[ i ] = -1;
                remaining--;
            }
        }
    }
}

// for debugging only
void BitKPK::boardFromIndex( BitBoard &b, i32 index ) const
{
    u8 turn, wk, bk, wp;
    posFromIndex( index, turn, wk, bk, wp );
    b.reset();
    memset( b.bits, 0, sizeof(b.bits) );		// FIXME: hack!
    b.bits[ wpawn ] = ONESHL( wp );
    b.flags().kingPos[ white ] = wk;
    b.flags().kingPos[ black ] = bk;
    b.flags().turn = turn;
    b.update();
    b.flags().check = b.attacked( turn == white ? wk : bk, turn );
}

// save bitmap
// returns 1 on success
bool BitKPK::saveBin( const char *fnm ) const
{
    if ( !bitmap )
    {
        return 0;
    }
    FILE *f = fopen( fnm, "wb" );
    if ( !f )
    {
        return 0;
    }
    if ( fwrite( bitmap, (kpkIndexSize + 7) / 8, 1, f ) != 1 )
    {
        fclose(f);
        return 0;
    }
    fclose(f);
    return 1;
}

// load bitmap
// returns 1 on success
bool BitKPK::loadBin( const char *fnm )
{
    if ( !bitmap )
    {
        return 0;
    }
    FILE *f = fopen( fnm, "rb" );
    if ( !f )
    {
        return 0;
    }
    if ( fread( bitmap, (kpkIndexSize + 7) / 8, 1, f ) != 1 )
    {
        fclose(f);
        return 0;
    }
    fclose(f);
    return 1;
}
