package Cluedo.g1;

class CountingLogic {
    int _m, _n;

    int[] _hc;    // number of total 1's in a row
    int[] _vc;

    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.
    //Clue[] cls = new Clue[256];
    Clue[] cls = new Clue[1024];

    /** Create a logic structure for m people and n cards.  Person i
     * can hold hc[i] cards, and card j can be held by vc[j] people.
     */
    public CountingLogic( int m, int n, int[] hc, int[] vc ) {
        _m = m;
        _os = new int[m*n];
        _n = n;
        _hc = hc;
        _vc = vc;
    }

    protected CountingLogic( int m, int n, int[] hc, int[] vc, Clue[] clues) {
        _m = m;
        _os = new int[m*n];
        _n = n;
        _hc = hc;
        _vc = vc;
		cls = clues;
    }

    /** create a new object without sharing data.
     */
    public Object clone() {
        int[] hctmp = new int[_hc.length];
        System.arraycopy( _hc, 0, hctmp, 0, _hc.length );
        int[] vctmp = new int[_vc.length];
        System.arraycopy( _vc, 0, vctmp, 0, _vc.length );
		
        // clone the cls array, too
        Clue[] clues_tmp = new Clue[cls.length];
        System.arraycopy( cls, 0, clues_tmp, 0, cls.length );

        return new CountingLogic( _m, _n, hctmp, vctmp, clues_tmp );
    }

    /** get the information about the ownership between a person and a
     * card.  The card is held by that person if a positive value is
     * returned, or not held by that person if a negative value is
     * returned.  Returning zero means the ownership is unknown.
     */
    public final int state( int idx, int card ) {
        return _os[ idx*_n + card ];
    }

    /** merge the facts from other logics, if they are all identical.
     * @return    whether the matrix is changed
     */
    public boolean mergeFacts( CountingLogic[] logic ) {
        boolean ret = false;
    outer_loop:
        for ( int i=0; i<_os.length; i++ )
        {
            if ( _os[i] != 0 )
                continue;
            int x = logic[0]._os[i];
            if ( x == 0 )
                continue;
            for ( int j=1; j<logic.length; j++ )
                if ( x != logic[j]._os[i] )
                    continue outer_loop;
            _os[i] = x;
            ret = true;
        }

        return ret;
    }

    /** add an undeterministic clue into the clue repository
     */
    public Clue insert( Clue clue ) {
        int pos = -1;
        for ( int i=cls.length-1; i>=0; i-- )
            if ( null == cls[i] )
                pos = i;
            else if ( implied( clue, cls[i] ) )
                return null;
        if ( pos < 0 )
            return null;
        cls[pos] = clue;
        return clue;
    }

    /** check whether clue 2 implies clue 1
     */
    public boolean implied( Clue clue1, Clue clue2 ) {
        if ( clue1.idx != clue2.idx )
            return false;
        if ( null == difference( clue2.list, clue2.n, clue1.list, clue1.n ) 
             && clue1.lb <= clue2.lb && clue1.hb >= clue2.hb )
            return true;
        return false;
    }

    /** reduce a clue by checking up the ownership matrix.  (i.e.,
     * removing known facts from the clue
     */
    public Clue reduce( Clue clue ) throws InvalidClueException {
        try
        {
            Clue ret = reduce( clue.idx, clue.list, clue.n, clue.lb, clue.hb );
            // is this clue useless?
            if ( ret == null )
                return null;
            // is this clue irreducible?
            if ( ret.n == clue.n )
                return clue;
            return ret;
        }
        catch ( InvalidClueException e ) 
        {
            throw new InvalidClueException( clue );
        }
    }

    /** reduce a clue by another clue by checking the intersection of
     * two clues.
     */
/*
    public Clue reduce( Clue dst, Clue src ) throws InvalidClueException {
        // if two clues are about different persons, no reduction
        if ( dst.idx != src.idx )
            return dst;

        // get the intersection of two sets of cards
        int[] list = intersection( dst.list, dst.n, src.list, src.n ) ;
        // do nothing if there is no intersection
        if ( null == list || list.length == 0 )
            return dst;

        // the lower bound of number of holding cards in a subset is
        // the original lower bound minus the size of difference set.
        int lb = src.lb - ( src.list.length - list.length );
        // the upper bounds are the same.
        int hb = src.hb;

        // both bounds should be non-negative and no more than the total
        // number of cards in the subset.
        if ( lb < 0 )
            lb = 0;
        if ( hb > list.length )
            hb = list.length;

        // if everything is possible in the subset, there is no reduction.
        if ( lb == 0 && hb == list.length )
            return dst;

        // the number of cards in the difference set.
        int ndiff = dst.n - list.length;
        if ( ndiff > 0 )
        {
            // if there are some cards in dst but not in src, compute the
            // difference set.
            int[] diff = difference( dst.list, dst.n, list, list.length );
        
            // the lower bounds and upper bounds for the difference set.
            int difflb = dst.lb - hb;
            int diffhb = dst.hb - lb;
            if ( difflb < 0 )
                difflb = 0;
            if ( diffhb > diff.length )
                diffhb = diff.length;

            // if this bounds for the difference set make sence, add
            // this into the clue repository
            if ( difflb > 0 || diffhb < diff.length )
                add( dst.idx, diff, difflb, diffhb );
        }

        // is there any improvement, if we use the bounds from the subset?
        if ( lb <= dst.lb && hb >= dst.hb - ndiff )
            return dst;

        if ( lb < dst.lb )
            lb = dst.lb;
        if ( hb > dst.hb - ndiff )
            hb = dst.hb - ndiff;

        return reduce( dst.idx, dst.list, dst.n, lb, hb );
    }
*/

    /** intersection of two sorted list
     */ 
    int[] intersection( int[] dst, int ndst, int[] src, int nsrc ) {
        int[] ret = new int[ndst];
        int nret = 0;
        for ( int i=0, j=0;;)
        {
            int diff = dst[i] - src[j];
            if ( 0 == diff )
                ret[nret++] = dst[i];

            if ( diff <= 0 && ++i >= ndst )
                break;
            if ( diff >= 0 && ++j >= nsrc )
                break;
        }

        return ClueProblem.resizeArray( ret, nret );
    }

    /** difference of two sorted list
     */ 
    int[] difference( int[] dst, int ndst, int[] src, int nsrc ) {
        int[] ret = new int[ndst];
        int nret = 0;

        for ( int i=0, j=0;;)
        {
            int diff = dst[i] - src[j];
            if ( diff < 0 )
                ret[nret++] = dst[i];

            if ( diff <= 0 && ++i >= ndst )
                break;
            if ( diff >= 0 && ++j >= nsrc )
                break;
        }

        return ClueProblem.resizeArray( ret, nret );
    }

    final int[] difference( int[] dst, int[] src ) {
        if ( dst == null )
            return null;
        if ( src == null )
            return dst;
        return difference( dst, dst.length, src, src.length );
    }

    /**
     * reduce all known facts (positive or negative) from the list of
     * a clue. 
     * @param idx   the person index
     * @param list  the card list
     * @param n     the length of the list
     * @param lb    the lower bound of the number of holding cards
     * @param hb    the upper bound of the number of holding cards
     */
    public Clue reduce( int idx, int[] list, int n, int lb, int hb ) 
        throws InvalidClueException {

        return reduce( idx, list, n, lb, hb, true );
    }

    public Clue reduce( int idx, int[] list, int n, int lb, int hb,
                        boolean removeable ) throws InvalidClueException {
        
        // return a list of at most n cards
        int[] ret = new int[n];
        int j=0;           // the number of returned cards

        // for each card, check up the ownership matrix
        for ( int i=0; i<n; i++ )
        {
            int x = list[i];           // the card id
            int flag = _os[idx*_n+x];  // matrix entry

            if ( flag != 0 ) 
            {
                // if the ownership is known, remove the card from the list
                if ( flag > 0 )
                {
                    // decrement the lower bound of the upper bound
                    // of the clue, if the person own this card
                    lb--;
                    hb--;
                }
            }
            else 
            { // the ownership is unknown.
                ret[j++] = x;
            }
        }

        // the upper bound can not exceed the nubmer of cards
        if ( hb > j )
            hb = j;
        // the lower bound can not be negative
        if ( lb < 0 )
            lb = 0;

        // a conflict is found if the upper bound is less than the
        // lower bound.  Wwhat can I do for a conflict?
        if ( lb > hb )
            throw new InvalidClueException( new Clue( idx, ret, j, lb, hb ) );

        // if everything is possible, this clue is now useless
        if ( removeable & lb == 0 && hb == j )
            return null;

        return new Clue( idx, ret, j, lb, hb );
    }

    /** apply the clue to the ownership matrix
     */
    boolean apply( Clue clue ) throws InvalidClueException {
        int idx = clue.idx;
        boolean ret = false;

        if ( clue.hb == 0 )
        {
            // if no card in the list belongs to the person, update
            // the ownership matrix for all cards in the list.
            for ( int i=0; i<clue.n; i++ )
            {
                int j = idx*_n + clue.list[i];
                if ( 0 == _os[j] )
                {
                    ret = true;
                    _os[j] = -1;
                }
            }

            return ret;
        }

        if ( clue.lb == clue.n )
        {
            // if all cards in the list belong to the person, also
            // update the matrix.
            for ( int i=0; i<clue.n; i++ )
            {
                int j = idx*_n + clue.list[i];
                if ( 0 == _os[j] )
                {
                    ret = true;
                    _os[j] = 1;
                }
            }
            return ret;
        }

        // otherwise, count how many cards are known belong to
        // or not belong to the person.
        int[] cnt = count( _os, idx*_n, 1, _n );

        if ( cnt[0] + clue.hb > _hc[idx] )
            clue.hb = _hc[idx] - cnt[0];
        if ( cnt[1] + clue.n - clue.lb > _n - _hc[idx] )
            clue.lb = clue.n - ( _n - _hc[idx] - cnt[1] );
        
        // the total number of cards owned by a person must
        // be a fixed value
        if ( clue.hb < clue.lb )
            throw new InvalidClueException( clue );

        // if none of the cards other than those in the clue list are
        // unknown, do nothing
        if ( _n <= cnt[0] + cnt[1] + clue.n )
            return false;

        if ( cnt[0] + clue.lb >= _hc[idx] )
        {
            // if the total number of cards (known owned + clue minimum)
            // now reachs the total pre-defined value, all other cards
            // do not belong the that persion.  First set all zero-valued
            // entries to negative, the reset entries of cards in the
            // clue list.
            for ( int i=0; i<_n; i++ )
                if ( 0 == _os[ idx*_n + i ] )
                    _os[ idx*_n + i ] = -1;
            for ( int i=0; i<clue.n; i++ )
                _os[ idx*_n + clue.list[i] ] = 0;
            
            return true;
        }

        if ( cnt[1] + (clue.n-clue.hb) >= _n - _hc[idx] ) 
        {
            // similarly, checking for known non-owned cards against
            // the clue maximum.
            for ( int i=0; i<_n; i++ )
                if ( 0 == _os[ idx*_n + i ] )
                    _os[ idx*_n + i ] = 1;
            for ( int i=0; i<clue.n; i++ )
                _os[ idx*_n + clue.list[i] ] = 0;
            
            return true;
        }

        return false;
    }

    /** Add a clue.  The idx-th person hold some cards in the list.
     * The minimum possible number of cards held is lb, and the maximum
     * is hb.
     * @return  the flag whether there is no conflict
     */
    boolean add( int idx, int[] list, int lb, int hb ) {
        // sort the list
        java.util.Arrays.sort( list );
        int last = -1;
        int j = 0;
        for ( int i=0; i<list.length; i++ )
            if ( list[i] != last )
            {
                last = list[i];
                list[j++] = last;
            }

        try 
        {
            // first try to reduce the clue by known facts
            Clue clue = reduce( idx, list, j, lb, hb );
            // if the clue becomes useless, do nothing
            if ( null == clue )
                return true;

            // System.out.println( "Reduced clue: " + clue.toString() );
            // printstates();

            // whether the ownership matrix is changed
            boolean changed = apply( clue );
            
            // if now the clue is not useless, add it in the repository
            if ( !changed && clue != null )
            {
                // System.out.println( "Add clue to repository:" + clue.toString() );
                clue = insert( clue );
            }

            // printstates();
            
            revalidate( changed, clue != null );
/*
            // flip cells to find out more inferences.
            clue = leastClue();
            if ( null != clue )
                flipTest( clue.idx, clue.list[0] );
*/
        }
        catch ( InvalidClueException e ) 
        {
            // System.out.println( "Invalid clue -- " + e._clue.toString() );
            // printstates();
            // e.printStackTrace();
            // throw new RuntimeException();
            return false;
        }
        
        return true;
    }

    public void revalidate( boolean mat, boolean repos ) 
        throws InvalidClueException {

        for ( int i=0; i<100 && ( mat || repos ); i++ )
        {
            // if the ownership matrix is changed
            if ( mat )
            {
                mat = false;
                // propagate the changes within the matrix by the counting
                // constraints.
                repos = recount() || repos; 
                // eliminate all useless clues in the repository
                repos = eliminate() || repos;
            }

            if ( repos )
            {
                repos = false;
                mat = reapply() || mat;
            }
/*
            // if a new clue is added, eliminate clues within the repository
            if ( clue != null )
                crosseliminate();
*/
        }

        for ( int i=0; i<cls.length; i++ )
        {
            if ( cls[i] == null )
                continue;

            for ( int j=0; j<cls.length; j++ )
            {
                if ( i == j || cls[j] == null )
                    continue;
                if ( implied( cls[j], cls[i] ) )
                    cls[j] = null;
            }
        }
    }

    /** re-apply all clues in the repository to the matrix
     */
    public boolean reapply() throws InvalidClueException {
        boolean ret = false;
        for ( int i=0; i<cls.length; i++ )
            if ( cls[i] != null )
            {
                Clue clue = reduce( cls[i] );
                if ( clue != null )
                    ret = apply( clue ) || ret;
            }
        return ret;
    }

    /** returns the list of unknown cards for row, idx
     */
    public int[] unknown_cards( int idx ) {
        int unknown[] = new int[_n];
        int j = 0;
        
        for( int i = 0; i < _n; i++ ) {
            if( state( idx, i ) == 0 ) {
                unknown[j++] = i;
            }
        }
        
        return ClueProblem.resizeArray( unknown, j );
    }

    /** count the number of positive and negative number in a row or
     * a column
     */
    static int[] count( int[] os, int first, int stride, int n ) {
        int pcount = 0, ncount = 0;       // two counters

        // count numbers of positive and negative elements 
        for ( int i=0, j=first; i<n; i++, j+=stride )
        {
            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 first, int stride, int n, int c ) {
        boolean ret = false;

        int[] cnt = count( os, first, stride, n );

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

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

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

            ret = true;
        }

        return ret;
    }

    /** count the number of known owned and known non-owned cards of a
     * person.
     */
    public final int[] countrow( int idx ) {
        return count( _os, idx*_n, 1, _n );
    }

    /** check whether each player is now deterministic
     * @return return whether the ownership matrix is modified
     */
    boolean hcount() {
        boolean ret = false;
        for ( int i=0; i<_m; i++ )
            ret = countline( _os, i*_n, 1, _n, _hc[i] ) || ret;
        return ret;
    }

    /** check whether each card is definitely owned by a player
     * @return return whether the ownership matrix is modified
     */
    boolean vcount() {
        boolean ret = false;
        for ( int i=0; i<_n; i++ )
            ret = countline( _os, i, _n, _m, _vc[i] ) || ret;
        return ret;
    }

    /** find out and update all matrix elements
     */
    boolean recount() {
        boolean ret = vcount();
        
        for ( ;; ) 
        {
            if ( !hcount() )
                break;
            
            if ( !vcount() )
                break;
        }

        return ret;
    }

    /** try to reduce all clues in the repository, by the ownership
     * matrix
     */
    boolean eliminate() throws InvalidClueException {
        boolean ret = false;

        for ( int i=0; i<cls.length; i++ )
            if ( cls[i] != null )
            {
                Clue clue = reduce( cls[i] );
                if ( clue != cls[i] )
                {
                    cls[i] = clue;
                    ret = true;
                }
            }

        return ret;
    }

    /** try to reduce all clues in the repository, by each other
     */
/*
    void crosseliminate() throws InvalidClueException {

        for ( int i=0; i<cls.length; i++ )
        {
            if ( null == cls[i] )
                continue;

            for ( int j=0; j<cls.length; j++ )
                if ( i != j && cls[j] != null )
                {
                    cls[i] = reduce( cls[i], cls[j] );
                    if ( null == cls[i] )
                        break;
                }
        }
    }
*/

    /** find a clue in the repository that has minimum number of
     * cards
     */
    Clue leastClue() {
        Clue ret = null;
        int ncards = 1000000;
        for ( int i=0; i<cls.length; i++ )
            if ( cls[i] != null && cls[i].list.length < ncards )
            {
                ncards = cls[i].list.length;
                ret = cls[i];
            }
        
        if ( ret != null ) 
        {
            //System.out.println( "The least clue: " + ret.toString() );
        }
        return ret;
    }

    /** try to test an entry with true and false, the merge the results
     */
    void flipTest( int idx, int card ) throws InvalidClueException {
        CountingLogic[] logic = new CountingLogic[] {
            (CountingLogic) clone(), (CountingLogic) clone() };
        logic[0]._os[ idx*_n + card ] = 1;
        logic[1]._os[ idx*_n + card ] = -1;

        try
        {
            logic[0].revalidate( true, false );
        } 
        catch( InvalidClueException e )
        {
            //System.out.println( "Set [" + idx + "] " + card + " to negative" );
            _os[ idx*_n + card ] = -1;
        }

        try
        {
            logic[1].revalidate( true, false );
        } 
        catch( InvalidClueException e )
        {
            //System.out.println( "Set [" + idx + "] " + card + " to positive" );
            _os[ idx*_n + card ] = 1;
        }

        if ( mergeFacts( logic ) )
        {
            //System.out.println( "Found additional facts!!!" );
            revalidate( true, false );
        }
    }

    /** print the current state of the logic box
     */
    void printstates() {
        System.out.print( "   " );
        for ( int j=0; j<_n; j++ )
            System.out.print( " " + (j+1) % 10 );
        System.out.println( "    " );

        String[] marks = { " -", " ?", " +" };
        for ( int i=0; i<_m; i++ )
        {
            System.out.print( "[" + i + "]" );
            for ( int j=0; j<_n; j++ )
                System.out.print( marks[_os[i*_n+j]+1] );
            System.out.println();
        }

        for ( int i=0; i<cls.length; i++ )
            if ( cls[i] != null )
            {
                System.out.print( "Clue #" + i + " " + cls[i].toString() );
                System.out.println( "" );
            }
    }    
}

/** the class of unsolved clue.  A clue contains an event list of
 * n events, a lower bound, and an upper bound.  Some event in the
 * list might be true; the number of true events would be within
 * the lower bound and the upper bound.
 */
class Clue implements Cloneable {
    int idx;             // the id of the player
    int[] list;          // the sorted list of choices
    int n;               // the number of cards in the list
    int lb;              // the lower bound of the number held in the list
    int hb;              // the upper bound
    
    public Clue( int _idx, int[] _list, int _n, int _lb, int _hb ) {
        idx = _idx;
        list = _list;
        n = _n;
        lb = _lb;
        hb = _hb;
    }

    public Object clone() {
        int listtmp[] = new int[list.length];
        System.arraycopy( list, 0, listtmp, 0, list.length );
        return( new Clue( idx, listtmp, n, lb, hb ) );
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append( "[" );
        sb.append( idx );
        sb.append( "] " );
        sb.append( lb );
        sb.append( "-" );
        sb.append( hb );
        sb.append( " of " );
        sb.append( n );
        sb.append( ":" );

        for ( int j=0; j<n; j++ )
        {
            sb.append( " " );
            sb.append( list[j]+1 );
        }
        return sb.toString();
    }
}    

class InvalidClueException extends Exception {
    Clue _clue;
    public InvalidClueException( Clue clue ) {
        _clue = clue;
    }

    public String toString() {
        return "Invalid Clue: " + _clue.toString();
    }
}
