package Cluedo.g1;

import java.util.Arrays;
import java.util.Vector;
import java.util.HashSet;

public class ProbabilisticApproach extends ClueBroomSolver {
	private static final double NUMBER_OF_DEVIATIONS = 1.0;
	private static final double QUERY_INCREMENT = 1.0;
	private static final double ANSWER_INCREMENT = 1.0;
	private double SURETY = .33333333333333333;

	private CardDistribution _queriedCards;
	private CardDistribution[] _agedQueries;
	private CardDistribution _answeredCards;

    public ProbabilisticApproach( int n, int p, int c, int[] holds ) {
		super( n, p, c, holds );
		_queriedCards = new CardDistribution( n, p );
		_answeredCards = new CardDistribution( n, p );
		_agedQueries = new CardDistribution[p];

		for( int i = 0; i < p; i++ ) {
			_agedQueries[i] = new CardDistribution( n, p );
		}

		SURETY = ( Math.max( c(), k() ) - _rand.nextDouble() * Math.min( c(), k() ) ) / n();
	}

	public void observe( int interrogator, int interrogatee, int[] cards, boolean response ) {
		super.observe( interrogator, interrogatee, cards, response );

		if( response ) {
			answered( interrogatee, cards );
		}

		age( interrogator, interrogatee, cards );
		asked( interrogator, cards );
	}

    public int[] guess() {
        int[] ret = new int[k()];
        int j=0;

		// add the cards we definitely know
        for ( int i=0; i<n(); i++ )
            if ( logic.state( p(), i ) > 0 )
            {
                ret[j++] = i;
                if ( j >= ret.length )
                    return ret;
            }

		// add the cards we're guessing about
		int raw_unknown[] = logic.unknown_cards( p() );
		HashSet unknown = new HashSet();

		// hash all the unknown cards
		for( int i = 0; i < raw_unknown.length; i++ ) {
			unknown.add( new Integer( raw_unknown[i] ) );
		}

		//logic.printstates();

		// remove the cards probably owned by players
		//System.out.println( "***************GROUP1PLAYER3 GUESS " );

		for( int i = 1; i < p(); i++ ) {
			int cards[] = probablyDoesOwn( i );
			Arrays.sort( cards );

			//System.out.print( "player[" + i + "] probably owns " );

			for( int k = 0; k < cards.length; k++ ) {
				//System.out.print( ( cards[k] + 1 ) + "[" + logic.state( i, cards[k] ) + "] " );
				unknown.remove( new Integer( cards[k] ) );
			}

			//System.out.println();
		}

		// add whatever's left
		Object blah[] = unknown.toArray();
		Integer[] remaining_unknown = new Integer[blah.length];

		for( int i = 0; i < blah.length; i++ ) {
			remaining_unknown[i] = ( Integer )blah[i];
		}
		

        for( int i = 0; i < remaining_unknown.length; i++ ) {
			ret[j++] = remaining_unknown[i].intValue();

			if ( j >= ret.length )
				return ret;
		}

		// all else fails
        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.
        throw new RuntimeException( "Program internal error" );
	}

	//private boolean last_holdout = false;
	private int last_holdout = 0;

	public int who() {
		if( last_holdout == 1 ) {
			for( int i = 1; i < p(); i++ ) {
				int cards[] = probablyDoesOwn( i );

				if( cards != null ) {
					Arrays.sort( cards );
					//System.out.print( "player[" + i + "] probably owns " );
					int unknown = 0;

					for( int k = 0; k < cards.length; k++ ) {
						//System.out.print( cards[k] + " " );
						if( logic.state( i, cards[k] ) == 0 ) {
							unknown++;
						}
					}

					//System.out.println();

					if( unknown > 0 ) {
						return( i );
					}
				}
			}
		}

		return( super.who() );
	}

	public int[] quest( int player ) {
		if( last_holdout == 1 ) {
			int cards[] = probablyDoesOwn( player );
			int[] unknown = new int[n()];
			int j = 0;

			if( cards.length == c() ) {
				for( int i = 0; i < n(); i++ ) {
					if( logic.state( player, i ) == 0 ) {
						unknown[j++] = i;
					}
				}
			}
			else {
				HashSet guess = new HashSet();

				for( int i = 0; i < cards.length; i++ ) {
					guess.add( new Integer( cards[i] ) );
				}

				for( int i = 0; i < n(); i++ ) {
					if( logic.state( player, i ) == 0 && !guess.contains( new Integer( i ) ) ) {
						unknown[j++] = i;
					}
				}
			}

			last_holdout = -1;
			//System.out.println( "questioning that last holdout!" );
			return( resizeArray( unknown, j ) );
		}

		return( super.quest( player ) );
	}

	public double rate() {
		for( int i = 1; i < p(); i++ ) {
			int holds = 0;

			for( int j = 0; j < n(); j++ ) {
				if( logic.state( i, j ) == 1 ) {
					holds++;
					break;
				}
			}

			if( holds < SURETY * c()  ) {
				return( 0.0 );
			}
		}

		if( last_holdout == 0 ) {
			last_holdout = 1;
			//System.out.println( "getting to that last holdout!" );
			return( 0.5 );
		}

		//return( super.rate() );
		return( 1.0 );
	}

	/**
	 * Tracks the Relative Age of Interrogators' Queries Per Player.
	 * - A interrogator seems unlikely to ask an interrogatee about 
	 *   cards that the interrogator already knows the interrogatee 
	 *   owns.
	 * - Most interrogators probably will normalize to this behavior.
	 * - Check to see if the oldest cards agree across multiple 
	 *   interrogators
	 * - Only use "positive" responses to an interrogation?
	 */
	protected void age( int interrogator, int interrogatee, int[] cards ) {
		_agedQueries[interrogator].hit_all_cols( interrogatee, 1.0 );
		/*
		for( int i = 1; i < p(); i++ ) {
			_agedQueries[interrogator].hit_all_cols( i, 1.0 );
		}
		*/

		_agedQueries[interrogator].zero_out_cols( interrogatee, cards );
	}

	/**
	 * Tracks Interrogators' Queries.
	 */
	protected void asked( int player, int[] cards ) {
		_queriedCards.hit( player, cards, 1.0 );
	}

	/**
	 * Tracks Interrogatees' Positive Responses.
	 */
	protected void answered( int player, int[] cards ) {
		_answeredCards.hit( player, cards, 1.0 );
	}

	class CountableCard implements Comparable {
		private int _value;
		private int _count;

		public CountableCard( int value ) {
			_value = value;
			_count = 1;
		}

		public void increment() {
			_count++;
		}

		public int intValue() {
			return( _value );
		}

		public int count() {
			return( _count );
		}

		public boolean equals( Object o ) {
			CountableCard otherCard = ( CountableCard )o;
			return( otherCard.intValue() == intValue() );
		}

		public int compareTo( Object o ) {
			CountableCard otherCard = ( CountableCard )o;
			return( count() - otherCard.count() );
		}
	}

	public int[] probablyDoesOwn( int player ) {
		try {
			CountableCard card_counts[] = new CountableCard[n()];

			for( int i = 1; i < p(); i++ ) {
				if( i != player ) { // only check what other players think player owns
					int ownership_opinion[] = maximallyPossibleCards( _agedQueries[i], player );
					//System.out.print( "OPINION: " + i + "->" + player + ": " );

					for( int j = 0; j < ownership_opinion.length; j++ ) {
						/*
						System.out.print( ( ownership_opinion[j] + 1 ) + "[" + 
							logic.state( player, ownership_opinion[j] ) + "] " );
						*/
						if( card_counts[ownership_opinion[j]] == null ) {
							card_counts[ownership_opinion[j]] = new CountableCard( ownership_opinion[j] );
							continue;
						}

						card_counts[ownership_opinion[j]].increment();
					}

					//System.out.println();
				}
			}

			// remove holes from the array
			Vector cards_array_wo_holes = new Vector();

			for( int i = 0; i < card_counts.length; i++ ) {
				if( card_counts[i] != null ) {
					cards_array_wo_holes.add( card_counts[i] );
				}
			}

			Object[] blah = cards_array_wo_holes.toArray();
			CountableCard cards[] = new CountableCard[blah.length];

			for( int i = 0; i < cards.length; i++ ) {
				cards[i] = ( CountableCard )blah[i];
			}

			// sort and remove the top c cards
			Arrays.sort( cards );
			int owned_cards[] = new int[c()];
			int j = 0;

			for( int i = cards.length - 1; i > -1 && j < c(); i-- ) {
				owned_cards[j++] = cards[i].intValue();
			}

			return( resizeArray( owned_cards, j ) );
		}
		catch( Exception e ) {
			return( new int[]{} );
		}
	}

	/**
	 * Computes a list of cards that are _REALLY_ unknown.  
	 * In other words, we can't guess these cards status by the player's 
	 * questioning or answering behavior.  This may probably a good list 
	 * of cards to ask a player about.
	 */
	protected int[] reallyUnknown( int player ) {
		HashSet probables = new HashSet();
		int unknown[] = new int[n()];
		int j = 0;

		// retrieve what we probably think
		//int probably_owns[] = maximallyPossibleCards( _answeredCards, player );
		int probably_owns[] = probablyDoesOwn( player );
		int probably_not_owns[] = probablyDoesNotOwn( player );

		// add all the probably owns
		for( int i = 0; i < probably_owns.length; i++ ) {
			probables.add( new Integer( probably_owns[i] ) );
		}

		for( int i = 0; i < probably_not_owns.length; i++ ) {
			// only add something from probably_not_owns if a card 
			// doesn't already exist w/i the probablY_owns
			if( !probables.contains( new Integer( probably_not_owns[i] ) ) ) {
				probables.add( new Integer( probably_not_owns[i] ) );
				continue;
			}

			// resolve conflict: remove it and add it to the unknown
			probables.remove( new Integer( probably_not_owns[i] ) );
			unknown[j++] = probably_not_owns[i];
		}

		// add whatever's left
		for( int i = 0; i < n(); i++ ) {
			if( !probables.contains( new Integer( i ) ) ) {
				unknown[j++] = i;
			}
		}

		// make sure something's returned
		if( j == 0 ) {
			for( int i = 0; i < n(); i++ ) {
				if( logic.state( player, i ) < 1 ) {
					unknown[j++] = i;
				}
			}
		}

		Arrays.sort( unknown, 0, j );
		return( resizeArray( unknown, j ) );
	}

	/**
	 * Computes a list of cards that are probably owned by the given player.
	 * Presently, it simply take the c cards w/ the most number of hits.
	 */
	protected int[] maximallyPossibleCards( CardDistribution cardDistrib, int player ) {
		HashSet owns = new HashSet();
		int probably_owns[] = new int[c()];
		int j = 0;

		// collect definitely owned cards
		for( int i = 0; i < n(); i++ ) {
			if( logic.state( player, i ) > 0 ) {
				owns.add( new Integer( i ) );
				probably_owns[j++] = i;
			}
		}

		// take the top "possible yes" cards and use those as remaining 
		// unknown owned cards
		CardDistribution cardDistribCopy = ( CardDistribution )cardDistrib.clone();
		int remaining_cards = c() - owns.size();

		while( remaining_cards > 0 && cardDistribCopy.any_non_zero_cols( player ) ) {
			int top_card = cardDistribCopy.max_col( player );

			if( logic.state( player, top_card ) == 0 && !owns.contains( new Integer( top_card ) ) ) {
				owns.add( new Integer( top_card ) );
				probably_owns[j++] = top_card;
				remaining_cards--;
			}

			cardDistribCopy.zero_out( player, top_card );
		}

		return( resizeArray( probably_owns, j ) );
	}

	/**
	 * Computes a list of cards that are probably unowned by the given player.
	 * It computes the number of known cards that lie w/i some standard dev's 
	 * of the average.  Moreover, it produces two lists of cards which lie 
	 * inside or outside some standard dev's away from the average.  
	 */
	protected int[] probablyDoesNotOwn( int player ) {
		double total_dev = NUMBER_OF_DEVIATIONS * _queriedCards.standard_deviation( player );
		double upper_bound = _queriedCards.average( player ) + total_dev;
		double lower_bound = _queriedCards.average( player ) - total_dev;

		int outside_cards[] = new int[n()];
		int inside_cards[] = new int[n()];
		int unowned[] = new int[n()];
		int truth_inside = 0, truth_outside = 0;
		int j = 0, k = 0, l = 0;

		for( int i = 0; i < n(); i++ ) {
			double hits = _queriedCards.count( player, i );

			if( logic.state( player, i ) == 0 ) {
				// collect the positions of the unknown cards
				if( hits < lower_bound || hits > upper_bound ) {
					outside_cards[j++] = i;
				}
				else {
					inside_cards[k++] = i;
				}
			}
			else if( logic.state( player, i ) != 0 ) { // self-validation
				// count the positions of the known cards
				if( hits < lower_bound || hits > upper_bound ) {
					truth_inside++;
				}
				else {
					truth_outside++;
				}

				// collect definitely unowned cards
				if( logic.state( player, i ) < 0 ) {
					unowned[l++] = i;
				}
			}
		}

		int probably_unowned[];

		if( truth_inside > truth_outside ) {
			probably_unowned = new int[j + l];
			System.arraycopy( outside_cards, 0, probably_unowned, 0, j );
			System.arraycopy( unowned, 0, probably_unowned, j, l );
			return( probably_unowned );
		}
		else if( truth_inside < truth_outside ) {
			probably_unowned = new int[k + l];
			System.arraycopy( inside_cards, 0, probably_unowned, 0, k );
			System.arraycopy( unowned, 0, probably_unowned, k, l );
			return( probably_unowned );
		}

		// guess we're screwed
		return( resizeArray( unowned, l ) );
	}
}
