
// Library class containing static utility methods
// Sorting algorithms are from Weiss's web site 

package Cluedo.g2;
 
public class Lib
{

    // Tests the methods in this file
    public static void main(String[] args)
    {
        int[] A = {1, 2, 3, 4};
        int[] B = {1, 2, 3};
        System.out.println(equalLists(A, B));
    }

    public static boolean equalLists( int[] A, int[] B )
    {
        if ( A.length != B.length )
            return false;

        int[] AA = (int[])(A.clone());
        int[] BB = (int[])(B.clone());

        quicksort(AA);
        quicksort(BB);

        for ( int i = 0; i < AA.length; i++ )
        {
            if ( AA[i] != BB[i] )
                return false;
        }

        return true;
    }

    public static int[] difference( int[] A, int[] B )
    {
        int size = A.length;
        int[] difference = new int[size];
        int count = 0;

        for ( int i = 0; i < A.length; i++ )
        {
            if ( !inlist( A[i], B ) )
            {
                difference[count++] = A[i];
            }
        }

        int[] retlist = new int[count];
        System.arraycopy(difference, 0, retlist, 0, count);

        return retlist;
    }

    public static int[] union( int[] A, int[] B )
    {
        int size = A.length + B.length;
        int[] union = new int[size];
        int count = 0;

        for ( int i = 0; i < A.length; i++ )
        {
            if ( !inlist( A[i], B ) )
            {
                union[count++] = A[i];
            }
        }

        for ( int i = 0; i < B.length; i++ )
        {
            union[count++] = B[i];
        }

        int[] retlist = new int[count];
        System.arraycopy(union, 0, retlist, 0, count);

        return retlist;
    }
    
    public static int[] combine( int[] A, int[] B )
    {
        int size = A.length + B.length;
        int[] combined = new int[size];

        for ( int i = 0; i < A.length; i++ )
        {
            combined[i] = A[i];
        }
        for ( int i = 0; i < B.length; i++ )
        {
            combined[A.length+i] = B[i];
        }

        return combined;
    }
    
    // Returns an array of ints containing the overlap of A and B
    public static int[] overlap( int[] A, int[] B )
    {
        int size = (A.length < B.length) ? A.length : B.length;
        int[] overlap = new int[size];
        int count = 0;

        for ( int i = 0; i < A.length; i++ )
        {
            if ( inlist( A[i], B ) )
            {
                overlap[count++] = A[i];
            }
        }

        int[] retlist = new int[count];
        System.arraycopy(overlap, 0, retlist, 0, count);

        return retlist;
    }

    // Returns an array of all indices+1 (aka cards) of list
    // that is true
    public static int[] getTrue(boolean[] list) 
    {
        int count = 0;
        int size = list.length;
        int[] hcards = new int[size];
        for ( int i = 0, j = 0; i < size; i++ )
        {
            if ( list[i] == true )
            {
                count++;
                hcards[j++] = i+1;
            }
        }
        int[] hlist = new int[count];
        System.arraycopy(hcards, 0, hlist, 0, count);

        return hlist;
    }

    public static int[] getFalse(boolean[] list) 
    {
        int count = 0;
        int size = list.length;
        int[] hcards = new int[size];
        for ( int i = 0, j = 0; i < size; i++ )
        {
            if ( list[i] == false )
            {
                count++;
                hcards[j++] = i+1;
            }
        }
        int[] hlist = new int[count];
        System.arraycopy(hcards, 0, hlist, 0, count);

        return hlist;
    }

    // Returns true if the value num appears in the array list
    public static boolean inlist( int num, int[] list )
    {
        boolean found = false;

        for ( int i = 0; i < list.length; i++ )
        {
            if ( num == list[i] )
            {
                found = true;
                break;
            }
        }

        return found;
    }

    // Return index of min element
    public static int getMin( int[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i] < a[index] )
                index = i;
        }

        return index;
    }

    // Return index of min element
    public static int getMin( double[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i] < a[index] )
                index = i;
        }

        return index;
    }

    // Return index of min element
    public static int getMin( Comparable[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i].compareTo(a[index]) < 0 )
                index = i;
        }

        return index;
    }

    // Return index of max element
    public static int getMax( int[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i] > a[index] )
                index = i;
        }

        return index;
    }

    // Return index of max element
    public static int getMax( double[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i] > a[index] )
                index = i;
        }

        return index;
    }

    // Return index of max element
    public static int getMax( Comparable[] a )
    {
        int index = 0;

        for ( int i = 1; i < a.length; i++ )
        {
            if ( a[i].compareTo(a[index]) > 0 )
                index = i;
        }

        return index;
    }

    /**
     * Quicksort algorithm.
     */
    public static void quicksort( Comparable [ ] a )
    {
        quicksort( a, 0, a.length - 1 );
    }

    public static void quicksort( int [ ] a )
    {
        quicksort( a, 0, a.length - 1 );
    }

    public static void quicksort( double [ ] a )
    {
        quicksort( a, 0, a.length - 1 );
    }

    // When to use insertion sort cutoff
    private static final int CUTOFF = 5;

    /**
     * Method to swap to elements in an array.
     */
    public static final void swapReferences( Object [ ] a, int index1, int index2 )
    {
        Object tmp = a[ index1 ];
        a[ index1 ] = a[ index2 ];
        a[ index2 ] = tmp;
    }

    public static final void swapPrimitives( int[] a, int index1, int index2 )
    {
        int tmp = a[index1];
        a[index1] = a[index2];
        a[index2] = tmp;
    }

    public static final void swapPrimitives( double[] a, int index1, int index2 )
    {
        double tmp = a[index1];
        a[index1] = a[index2];
        a[index2] = tmp;
    }

    /**
     * Return median of left, center, and right.
     * Order these and hide the pivot.
     */
    private static Comparable median3( Comparable [ ] a, int left, int right )
    {
        int center = ( left + right ) / 2;
        if( a[ center ].compareTo( a[ left ] ) < 0 )
            swapReferences( a, left, center );
        if( a[ right ].compareTo( a[ left ] ) < 0 )
            swapReferences( a, left, right );
        if( a[ right ].compareTo( a[ center ] ) < 0 )
            swapReferences( a, center, right );

            // Place pivot at position right - 1
        swapReferences( a, center, right - 1 );
        return a[ right - 1 ];
    }

    private static int median3( int [ ] a, int left, int right )
    {
        int center = ( left + right ) / 2;
        if( a[ center ] < a[ left ] )
            swapPrimitives( a, left, center );
        if( a[ right ] < a[ left ] )
            swapPrimitives( a, left, right );
        if( a[ right ] < a[ center ] )
            swapPrimitives( a, center, right );

            // Place pivot at position right - 1
        swapPrimitives( a, center, right - 1 );
        return a[ right - 1 ];
    }

    private static double median3( double [ ] a, int left, int right )
    {
        int center = ( left + right ) / 2;
        if( a[ center ] < a[ left ] )
            swapPrimitives( a, left, center );
        if( a[ right ] < a[ left ] )
            swapPrimitives( a, left, right );
        if( a[ right ] < a[ center ] )
            swapPrimitives( a, center, right );

            // Place pivot at position right - 1
        swapPrimitives( a, center, right - 1 );
        return a[ right - 1 ];
    }

    /**
     * Internal quicksort method that makes recursive calls.
     * Uses median-of-three partitioning and a cutoff of 10.
     */
    private static void quicksort( Comparable [ ] a, int left, int right )
    {
        if( left + CUTOFF <= right )
        {
            Comparable pivot = median3( a, left, right );

                // Begin partitioning
            int i = left, j = right - 1;
            for( ; ; )
            {
                while( a[ ++i ].compareTo( pivot ) < 0 ) { }
                while( a[ --j ].compareTo( pivot ) > 0 ) { }
                if( i < j )
                    swapReferences( a, i, j );
                else
                    break;
            }

            swapReferences( a, i, right - 1 );   // Restore pivot

            quicksort( a, left, i - 1 );    // Sort small elements
            quicksort( a, i + 1, right );   // Sort large elements
        }
        else  // Do an insertion sort on the subarray
            insertionSort( a, left, right );
    }

    private static void quicksort( int [ ] a, int left, int right )
    {
        if( left + CUTOFF <= right )
        {
            int pivot = median3( a, left, right );

                // Begin partitioning
            int i = left, j = right - 1;
            for( ; ; )
            {
                while( a[ ++i ] < pivot ) { }
                while( a[ --j ] > pivot ) { }
                if( i < j )
                    swapPrimitives( a, i, j );
                else
                    break;
            }

            swapPrimitives( a, i, right - 1 );   // Restore pivot

            quicksort( a, left, i - 1 );    // Sort small elements
            quicksort( a, i + 1, right );   // Sort large elements
        }
        else  // Do an insertion sort on the subarray
            insertionSort( a, left, right );
    }

    private static void quicksort( double [ ] a, int left, int right )
    {
        if( left + CUTOFF <= right )
        {
            double pivot = median3( a, left, right );

                // Begin partitioning
            int i = left, j = right - 1;
            for( ; ; )
            {
                while( a[ ++i ] < pivot ) { }
                while( a[ --j ] > pivot ) { }
                if( i < j )
                    swapPrimitives( a, i, j );
                else
                    break;
            }

            swapPrimitives( a, i, right - 1 );   // Restore pivot

            quicksort( a, left, i - 1 );    // Sort small elements
            quicksort( a, i + 1, right );   // Sort large elements
        }
        else  // Do an insertion sort on the subarray
            insertionSort( a, left, right );
    }

    /**
     * Internal insertion sort routine for subarrays
     * that is used by quicksort.
     */
    private static void insertionSort( Comparable [ ] a, int left, int right )
    {
        for( int p = left + 1; p <= right; p++ )
        {
            Comparable tmp = a[ p ];
            int j;

            for( j = p; j > left && tmp.compareTo( a[ j - 1 ] ) < 0; j-- )
                a[ j ] = a[ j - 1 ];
            a[ j ] = tmp;
        }
    }

    // Internal insertion sort for integers
    private static void insertionSort( int [ ] a, int left, int right )
    {
        for( int p = left + 1; p <= right; p++ )
        {
            int tmp = a[ p ];
            int j;

            for( j = p; j > left && tmp < a[ j - 1 ]; j-- )
                a[ j ] = a[ j - 1 ];
            a[ j ] = tmp;
        }
    }

    // Internal insertion sort for doubles
    private static void insertionSort( double [ ] a, int left, int right )
    {
        for( int p = left + 1; p <= right; p++ )
        {
            double tmp = a[ p ];
            int j;

            for( j = p; j > left && tmp < a[ j - 1 ]; j-- )
                a[ j ] = a[ j - 1 ];
            a[ j ] = tmp;
        }
    }



}

interface Comparable
{
    int compareTo( Comparable rhs );
}


// == Data structures for keeping info

class CardNode
{
    // The value of this card
    
    // Which player has this card
    
}

// Keeps track of the info about all cards relative to
// one particular player, i.e. chances of that player
// having each card
class PlayerNode
{

    static class InfoNode implements Comparable
    {
        public int card;
        public double weight;   

        public int compareTo( Comparable _rhs )
        {
            InfoNode rhs = (InfoNode)_rhs;

            if ( this.weight > rhs.weight )
                return 1;
            else if ( this.weight < rhs.weight )
                return -1;
            else
                return 0;
        }
    }

    int player;

    // Cards are kept in unsorted order so that the index
    // into cardsInfo is 1 less than the card number. This
    // is done for speed of access. When we need the array
    // sorted, we do it at the time in a copy of the array.
    public InfoNode[] cardsInfo;

    // numCards is the total number of cards, not just how 
    // many are in this player's hand
    public PlayerNode( int player, int numCards )
    {
        cardsInfo = new InfoNode[numCards];

        for ( int i = 0; i < numCards; i++ )
        {
            cardsInfo[i] = new InfoNode();
            cardsInfo[i].card = i+1;
            cardsInfo[i].weight = 0;
        }
    }

    // Add weights to cards based on the passed in weight array
    // Assumes cardlist and weights are of the same size 
    public void addWeight( int[] cardlist, double[] weights )
    {
        for ( int i = 0; i < cardlist.length; i++ )
        {
            cardsInfo[ cardlist[i] - 1 ].weight += weights[i];       
        }
    }

    public void incWeight( int card )
    {
        cardsInfo[card-1].weight++;
    }

    // True if this player definitely has the specified card
    public boolean hasCard( int card )
    {
        if ( cardsInfo[card-1].weight == Double.POSITIVE_INFINITY )
            return true;
        else
            return false;
    }

    // True if this player definitely does not have the card
    public boolean notHave( int card )
    {
        if ( cardsInfo[card-1].weight == Double.NEGATIVE_INFINITY )
            return true;
        else
            return false;
    }

    // Add 1 to the weights of every card in this list
    public void addWeight( int[] cardlist )
    {
        double weight = 1.0 / cardlist.length;

        for ( int i = 0; i < cardlist.length; i++ )
        {
            cardsInfo[ cardlist[i]-1 ].weight += weight;
        }
    }

    // Set the weights of these cards to -INF signifying this player
    // definitely doesn't have them
    public void removeCards( int[] cardlist )
    {
        for ( int i = 0; i < cardlist.length; i++ )
        {
            cardsInfo[ cardlist[i]-1 ].weight = Double.NEGATIVE_INFINITY;
        }
    }

    public void removeCard( int card )
    {
        cardsInfo[card-1].weight = Double.NEGATIVE_INFINITY;
    }
    
    // set the weight of given card to +INF to signify that this player
    // definitely has that card
    public void confirmCard( int card )
    {
        cardsInfo[card-1].weight = Double.POSITIVE_INFINITY;
    }

    // Returns a list of cards for this player that we have not confirmed
    // whether or not he has it (this list could include cards we know for
    // certain he doesn't have), the list is sorted by weight
    public InfoNode[] getUnconfirmedNodes()
    {
        int count = 0;
        InfoNode[] retlist = new InfoNode[cardsInfo.length];

        for ( int i = 0; i < cardsInfo.length; i++ )
        {
            if ( cardsInfo[i].weight != Double.POSITIVE_INFINITY )
            {
                retlist[count] = cardsInfo[i];
                count++;
            }
        }   

        InfoNode[] sorted = new InfoNode[count];
        System.arraycopy(retlist, 0, sorted, 0, count);
        
        Lib.quicksort(sorted);

        return sorted;
    }

    public InfoNode[] getOnlyUnconfirmedNodes()
    {
        int count = 0;
        InfoNode[] retlist = new InfoNode[cardsInfo.length];

        for ( int i = 0; i < cardsInfo.length; i++ )
        {
            if ( cardsInfo[i].weight != Double.POSITIVE_INFINITY && cardsInfo[i].weight != Double.NEGATIVE_INFINITY )
            {
                retlist[count] = cardsInfo[i];
                count++;
            }
        }   

        InfoNode[] sorted = new InfoNode[count];
        System.arraycopy(retlist, 0, sorted, 0, count);
        
        Lib.quicksort(sorted);

        return sorted;
    }

    public int[] getUnconfirmedList()
    {
        InfoNode[] nodes = getUnconfirmedNodes();

        int[] list = new int[nodes.length];

        for ( int i = 0; i < nodes.length; i++ )
        {
            list[i] = nodes[i].card;
        }

        return list;
    }

    // We reupdate the player's info assuming that he has the cards that
    // have the lowest weight and not the cards that have the highest weight
    public void assumeKnowledge(int k)
    {
        InfoNode[] unconfirmed = getOnlyUnconfirmedNodes();

        int confirmed = cardsInfo.length - unconfirmed.length;
        int left = k - confirmed;

        for ( int i = unconfirmed.length-1; i >= 0; i-- )
        {
            // These are cards with the highest weight, set them to not "have"
            if ( i > unconfirmed.length-left-1 )
            {
                cardsInfo[ unconfirmed[i].card - 1 ].weight = Double.NEGATIVE_INFINITY;
            }
            
            // These are cards with the lowest weight, set them to "have"
            else
            {
                cardsInfo[ unconfirmed[i].card - 1 ].weight = Double.POSITIVE_INFINITY;
            }
        }
        
    }

    public InfoNode[] getSortedWeights()
    {
        return null;
    }

}

// Represents pieces of information revealed by players' answers
// to interrogation, e.g. A does not have 1, 2, 3 would have 
// members be a size 3 array of int that contains the values 1,
// 2, and 3 and the condition would be false; Conditionals can
// be combined to produce new Conditionals. Right now, only
// Conditionals for the same player can be combined
class Conditional
{
    private int player;
    private int[] members;
    private boolean condition;

    public Conditional( int _player, int[] _members, boolean _condition )
    {
        player = _player;
        members = _members;
        condition = _condition;
    }

    public boolean getCondition()
    {
        return condition;
    }

    public int getSize()
    {
        return members.length;
    }
    
    public int getPlayer()
    {
        return player;
    }

    public int[] getMembers()
    {
        return members;
    }

    // Combines the conditions of two Conditionals into a 
    // single resultant conditional that satisfies both
    // There are 3 combinations:
    //   Yes, Yes   - do nothing
    //   No, No     - merge into one big No condition
    //   Yes, No    - produce a new Yes condition
    public Conditional combineConditionals( Conditional C )
    {
        Conditional cond = null;

        if ( this.player == C.player )
        {
            if ( this.condition == false && C.condition == false )
            {
                cond = new Conditional( this.player, Lib.union(this.members, C.members), false );
            }
            else if ( this.condition == false && C.condition == true )
            {
                int[] result = Lib.difference(C.members, this.members);
                if ( result.length > 0 )
                    cond = new Conditional( this.player, result, true );
            }
            else if ( this.condition == true && C.condition == false )
            {
                int[] result = Lib.difference(this.members, C.members);
                if ( result.length > 0 )
                    cond = new Conditional( this.player, result, true );
            }
            else // if ( this.condition == true && C.condition == true )
            {
                if ( this.player != C.player && this.getSize() == 2 && C.getSize() == 2 )
                {
                    if ( Lib.equalLists( this.members, C.members ) )
                        cond = new Conditional( -1, this.members, false );
                            
                }
                // Do nothing in this case, combining them would only decrease the amount of info we have
            }

        }
        else
        {
            // For now, we do not combine conditionals of different players because it yields us no clear advantage
        }

        return cond;
    }

    public Conditional[] combineConditionalList( Conditional[] clist )
    {
        Conditional result = null;
        Conditional[] tmplist = new Conditional[clist.length];
        int count = 0;

        for ( int i = 0; i < clist.length; i++ )
        {
            if ( (result = combineConditionals(clist[i])) != null )
            {
                tmplist[count++] = result;
            }
        }

        Conditional[] retlist = new Conditional[count];
        System.arraycopy(tmplist, 0, retlist, 0, count);

        return retlist;
    }

    // Combine this conditional with a list of confirmed positives
    // Assumes the list belongs to the same player as this conditional 
    public Conditional combinePositiveList( boolean[] list )
    {
        if ( this.condition == true )
        {
            // If the number of knowns in the positive list is 
            
        }
        else
        {
        }

        return null;    
    }

    // Combine this conditional with a list of confirmed negatives
    // Assumes the list belongs to the same player as this conditional 
    public Conditional combineNegativeList( boolean[] list )
    {
        return null;    
    }

}

// Array of cards for each player
 
// Array of players for each card

// Array of hidden cards

// == Systems of info tracking
 
// Geometric weights
// Keep an array of card weights for every player. Cards with more
// weight for a player have a bigger chance of being held by that player
// than cards with less weight.

// Classes facilitating various representations of logic and probabilities

// Logic box

// Floating conditionals

// Probability matrix

// Conditional permutations

// Use separate classes for each aspect of the game to facilitate using
// different combinations
 
