
package Cluedo.g1;

/** The base class of clue problem solvers who do not make use of
 * any external logic libraries.
 */
class ClueBroomSolver extends ClueProblem {
    CountingLogic logic;

    private int[] _holds;    // my own cards
    private int _shown;      // number of shown cards
    int _doom_player; // the player that we want to handle last

    public ClueBroomSolver( int n, int p, int c, int[] holds ) {
        super( n, p, c );

        // System.out.println( "N=" + n + " P=" + p + " C=" + c + " K=" + k() );

        int[] hc = new int[p+1];
        for ( int i=0; i<p; i++ )
            hc[i] = c;
        hc[p] = k();

        int[] vc = new int[n];
        for ( int i=0; i<n; i++ )
            vc[i] = 1;

        logic = new CountingLogic( p+1, n, hc, vc );
        _holds = randomReorder( holds );
        _shown = 0;
        _doom_player = _rand.nextInt( p-1 ) + 1;
    }


    /** return the binomial coefficients (number of combinations for 
     * (c out of n)
     */
    static double binomial( int n, int c ) {
        if ( c > n - c )
            c = n - c;

        double ret = 1;
        for ( int i=0; i<c; i++ )
        {
            ret *= n - i;
            ret /= i + 1;
        }

        return ret;
    }


    /** tell the solver the idx-th player owns at least n1 of cards,
     * and at most n2 of them.
     *
     * At this moment, we only deal with deterministic cases
     */
    void owns( int idx, int[] cards, int n1, int n2 ) {
/*
        System.out.print( "ClueProblem: Player " + idx + " has " 
                          + n1 + "-" + n2 + " of cards: " );
        printcards( cards );
*/
        if ( !logic.add( idx, cards, n1, n2 ) ) {
            System.out.println( "Error: invalid clue!" );
        }

        // logic.printstates();
    }

    /** observe an interrogator calls interrogatee for a bunch of cards,
     * and the interrogatee returns a response
     */
    void observe( int interrogator, int interrogatee, int[] cards, boolean response ) {
    }

    int _unknown_players = 0;

    /** find a best player to ask a question
     */
    int who() {
        int p = p();
        int n = n();
        int unknown = 0;

        int[] count = new int[p];
        for ( int i=1; i<p; i++ )
        {
            boolean flag = false;
            for ( int j=0; j<n; j++ )
                if ( 0 == logic.state( i, j ) )
                {
                    count[i]++;
                    flag = true;
                }

            if ( flag )
                unknown++;
        }

        _unknown_players = unknown;

        int ret = 0;
        int c = 0;
        for ( int i=1; i<p; i++ )
            if ( i != _doom_player && count[i] > 0 && count[i] >= c )
            {
                ret = i;
                c = count[i];
            }

        if ( 0 == ret )
            ret = _doom_player;
        return ret;
    }

    /** ask the idx-th player a question
     */
    int[] quest( int idx ) {
        int[] ret = new int[n()];
        int j=0;

        if ( _unknown_players > 1 )
        {
            for ( int i=0; i<n(); i++ )
                if ( logic.state( idx, i ) == 0 
                     || ( logic.state( idx, i ) < 0 
                          && _rand.nextInt(4) != 0 ) )
                    ret[j++] = i;
        }
        else
        {
            int count = 0;
            for ( int i=0; i<n(); i++ )
                if ( logic.state( idx, i ) == 0 )
                {
                    if ( count++ < k() )
                        ret[j++] = i;
                }
                else if( logic.state( idx, i ) < 0 
                         && _rand.nextInt(4) != 0 ) {
                    ret[j++] = i;
                }
        }

        return resizeArray( ret, j );
    }

    /** reply a question from the idx-th player
     */
    int reply( int idx, int[] cards ) {
        // for each card that I am holding
        for ( int i=0; i<c(); i++ )
        {
            // search for the same card in the request list
            for ( int j=0; j<cards.length; j++ )
                if ( cards[j] == _holds[i] )
                {
                    if ( i > _shown )
                    {
                        // if this card has not been previously shown,
                        // swap this card to the front of the holding
                        // list
                        _holds[i] = _holds[_shown];
                        _holds[_shown] = cards[j];
                        _shown++;
                    }
                    // if found, show this card
                    return cards[j];
                }
        }
        
        return -1;
    }

    /** get the rate of knowledge to the cards on the stake
     */
    double rate() {
        int[] cnt = logic.countrow( p() );

        // System.out.println( "pcount=" + cnt[0] + " ncount=" + cnt[1] );

        int total = n() - cnt[0] - cnt[1];
        int choose = k() - cnt[0];
        double combinations = binomial( total, choose );
        // System.out.println( "(" + total + " choose " + choose + ") = " + combinations );
        return 1.0 / combinations;
    }

    public double remaining_combinations() {
        int[] cnt = logic.countrow( p() );
        int total = n() - cnt[0] - cnt[1];
        int choose = k() - cnt[0];
        return( binomial( total, choose ) );
    }

    /** return a best guess of cards on the stake
     */
    int[] guess() {
        int[] ret = new int[k()];
        int j=0;

        for ( int i=0; i<n(); i++ )
            if ( logic.state( p(), i ) > 0 )
            {
                ret[j++] = i;
                if ( j >= ret.length )
                    return ret;
            }

        for ( int i=0; i<n(); i++ )
            if ( logic.state( p(), i ) == 0 )
            {
                ret[j++] = i;
                if ( j >= ret.length )
                    return ret;
            }

        // this cannot happen.
        for ( int i=0; i<n(); i++ )
            if ( logic.state( p(), i ) < 0 )
            {
                ret[j++] = i;
                if ( j >= ret.length )
                    return ret;
            }

        return ret;

        // throw new RuntimeException( "Program internal error" );
    }
}
