
package Cluedo.g1;

/** The base class of clue problem solvers who do not make use of
 * any external logic libraries.
 */
class ClueDummySolver extends ClueProblem {
    int[] _nos;      // number of cards that players hold

    int[][] _os;     // the ownership matrix
    // If _os[i][j] is 1, -1, or 0, the player i definitely owns card
    // j, definitely not, or undetermined.  _os[n()] represents the stake.

    public ClueDummySolver( int n, int p, int c ) {
        super( n, p, c );

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

        _os = new int[p+1][n];

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

    /** resize an input array to size n
     */
    static int[] resizeArray( int[] array, int n ) {
        int[] ret = new int[n];
        System.arraycopy( array, 0, ret, 0, Math.min(n, array.length) );
        return ret;
    }

    /** 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;
    }

    /** count the number of positive and negative number in the array os
     */
    static int[] count( int[] os ) {
        int pcount = 0, ncount = 0;       // two counters

        // count numbers of positive and negative elements 
        for ( int j=0; j<os.length; j++ )
        {
            if ( os[j] > 0 )
                pcount++;
            else if ( os[j] < 0 )
                ncount++;
        }

        return new int[]{ pcount, ncount };
    }

    /** count whether there are c positive elements (if so, make all others
     * negative) or n-c negative elements (if so, make all others positive)
     * @return a boolean value indicating whether the array is modified.
     */
    static boolean countLine( int[] os, int c ) {
        int n = os.length;
        boolean ret = false;

        int[] cnt = count( os );

        if ( cnt[0] >= c )
        {   
            // is this array deterministic?
            if ( cnt[1] == n - c )
                return false;

            // if number of positve elements equals c, make all others
            // negative.
            for ( int j=0; j<n; j++ )
                if ( 0 == os[j] )
                    os[j] = -1;

            ret = true;
        }
        else if ( cnt[1] == n - c )
        {
            // if number of positve elements equals c, make all others
            // negative.
            for ( int j=0; j<n; j++ )
                if ( 0 == os[j] )
                    os[j] = 1;

            ret = true;
        }

        return ret;
    }

    /** check whether each player is now deterministic
     * @return return whether the ownership matrix is modified
     */
    static boolean hcount( int[][] os, int[] nos ) {
        boolean ret = false;
        for ( int i=0; i<os.length; i++ )
            ret = ret || countLine( os[i], nos[i] );
        return ret;
    }

    /** check whether each card is definitely owned by a player
     * @return return whether the ownership matrix is modified
     */
    static boolean vcount( int[][] os, int n ) {
        boolean ret = false;

        // for each card, check its ownership
        for ( int i=0; i<n; i++ )
        {
            // compute the ownership first
            int pcount = 0, ncount = 0;
            for ( int j=0; j<os.length; j++ )
            {
                if ( os[j][i] > 0 )
                    pcount++;
                else if ( os[j][i] < 0 )
                    ncount++;
            }

            // if the card is known owned by a player, update the
            // ownership with other players to be false
            if ( pcount > 0 && pcount + ncount < os.length )
            {
                ret = true;
                for ( int j=0; j<os.length; j++ )
                    if ( 0 == os[j][i] )
                        os[j][i] = -1;
            }
        }
        
        return ret;
    }

    /** find out and update all certain ownerships
     */
    void recount() {

        vcount( _os, n() );
        
        for ( ;; ) 
        {
            if ( !hcount( _os, _nos ) )
                break;
            
            if ( !vcount( _os, n() ) )
                break;
        }
    }

    void observe( int interrogator, int interrogatee, int[] cards, 
                  boolean response ) {

        owns( interrogatee, cards, response ? 0 : 1, 
              response ? cards.length : 0 );
    }

    /** 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 ( n1 != n2 )
            return;

        boolean changed = false;
        int[] os = _os[idx];

        int n = cards.length;
        int m = n1;

        for ( int i=0; i<cards.length; i++ )
        {
            int d = cards[i];
            if ( os[d] > 0 )
            {
                n--;
                m--;
            }
            else if ( os[d] < 0 )
                n--;
        }

        // assert n>=0 && n1>=0 && n2<=n;
        if ( n <= 0 )
            return;

        if ( m == n )
        {
            // if all cards are owned by the player
            for ( int i=0; i<cards.length; i++ )
            {
                int d = cards[i];
                if ( os[d] <= 0 )
                {
                    changed = true;
                    os[d] = 1;
                }
            }
        }
        else if ( m == 0 )
        {
            // if none of the cards is owned by the player
            for ( int i=0; i<cards.length; i++ )
            {
                int d = cards[i];
                if ( os[d] <= 0 )
                {
                    changed = true;
                    os[d] = -1;
                }
            }
        }

        if ( changed )
            recount();

        //printmatrix( _os );
    }

    /** find a best player to ask a question
     */
    int who() {
        for ( int i=1; i<p(); i++ )
        {
            for ( int j=0; j<n(); j++ )
                if ( 0 == _os[i][j] )
                    return i;
        }

        return 0;
    }

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

        for ( int i=0; i<n(); i++ )
            if ( _os[idx][i] <= 0 )
                ret[j++] = i;

        return resizeArray( ret, j );
    }

    /** reply a question from the idx-th player
     */
    int reply( int idx, int[] cards ) {
        for ( int i=0; i<cards.length; i++ )
        {
            int d = cards[i];
            if ( _os[0][d] > 0 )
                return d;
        }
        
        return -1;
    }

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

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

        return 1.0 / binomial( n() - cnt[0] - cnt[1], k() - cnt[0] );
    }

    /** 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 ( _os[p()][i] > 0 )
            {
                ret[j++] = i;
                if ( j >= ret.length )
                    return ret;
            }

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

        // this cannot happen.
        throw new RuntimeException( "Program internal error" );
    }

    static void printmatrix( int[][] mat ) {
        for ( int i=0; i<mat.length; i++ )
        {
            System.out.print( "[" + i + "]" );
            for ( int j=0; j<mat[i].length; j++ )
                System.out.print( " " + mat[i][j] );
            System.out.println();
        }
    }
}
