package Cluedo.g1;

import java.io.*;
import java.util.*;
import Cluedo.*;
import orbital.logic.imp.*;
import orbital.logic.sign.ParseException;
import orbital.moon.logic.*;

/**
 * A "mind" contains the entire set of knowledge interpreted from player
 * actions and interrogations throughout the game. When some action occurs, the
 * corresponding <code>interpretXXX()</code> method is called to register the
 * action with this mind.
 * 
 * @author Mark Ayzenshtat
 * @author Hanhua Feng
 * @author Stephen Lee
 */
public class Mind {
	private static final Formula[] kZeroFormulae = new Formula[0];

	private PlayerBase mPlayer;
	private Logic mLogic;
	private List mPremises;	
	private Map mTermFormulae;
	//private double[][] mProb;
	private Claim[][] mClaims;
	private String[][] mLogicTerms;

	public Mind(PlayerBase iPlayer) {
		int dim1 = iPlayer.getNumPlayers() + 1;	// numPlayers + 1 for hidden cards
		int dim2 = iPlayer.getNumCards();
		
		mPlayer = iPlayer;
		// propositional inference is much, much, MUCH faster than the default
		mLogic = new ClassicalLogic(ClassicalLogic.PROPOSITIONAL_INFERENCE);
		mPremises = new ArrayList();
		mLogicTerms = new String[dim1][dim2];
		mTermFormulae = new HashMap();
		//mProb = new double[dim1][dim2];
		mClaims = new Claim[dim1][dim2];

		// pre-build term formulae map for use later
		try {
			for (int i = 0, n = mPlayer.getNumPlayers() + 1; i < n; i++) {
				for (int j = 1, m = mPlayer.getNumCards(); j <= m; j++) {
					String term = getLogicTerm(i, j);
					mTermFormulae.put(term, (Formula) mLogic.createExpression(term));
				}
			}
		} catch (ParseException ex) {
			throw new RuntimeException("Error parsing logic expression: " + ex);
		}		
	}

	/**
	 * Rebuilds the matrix of probabilities given all of the premises that have
	 * been accepted so far. This method should be called once at the begining of
	 * each move.
	 */
	public void rebuildProbabilities() {		
		Formula[] allPremises = (Formula[]) mPremises.toArray(kZeroFormulae);
		Inference inf = mLogic.inference();

		int[] numUnknown = new int[mPlayer.getNumCards()];
		for (int j = 0, m = mPlayer.getNumCards(); j < m; j++) {
			numUnknown[j] = 0;

			// go through, mark as true, false, or unknown, and set probabilities
			// as appropriate - we cannot set probabilities of unknowns until we
			// know how many there are per card, since they must all add up to 1.0
			for (int i = 0, n = mPlayer.getNumPlayers() + 1; i < n; i++) {
				String term = getLogicTerm(i, j + 1);
				Formula claim = (Formula) mTermFormulae.get(term);

				boolean c1 = inf.infer(allPremises, claim);

				if (c1) {
					mClaims[i][j] = Claim.kTrue;
					//mProb[i][j] = 1;
				} else {
					boolean c2 = inf.infer(allPremises, claim.not());					
					if (c2) {
						mClaims[i][j] = Claim.kFalse;
						//mProb[i][j] = 0;
					} else {
						mClaims[i][j] = Claim.kUnknown;
						numUnknown[j]++;
					}
				}
			}
			
/*			
			// now that we know how many unknowns there are for this card,
			// go back and set the probabilities
			if (numUnknown[j] > 0) {
				double d = 1d / numUnknown[j];
				for (int i = 0, n = mPlayer.getNumPlayers() + 1; i < n; i++) {
					if (mClaims[i][j] == Claim.kUnknown) {
						//mProb[i][j] = d;
					}
				}
			}
*/			
		}

		//writeMatrix();
	}
	
	public void printMatrix() {
		for (int i = 0; i < mClaims.length; i++) {
			System.out.println(Arrays.asList(mClaims[i]));
		}
		System.out.println();
	}

/*
	private void writeMatrix() {
		try {
			PrintWriter pw =
				new PrintWriter(
					new FileWriter("matrix" + mPlayer.getRoundNumber() + ".csv"));

			for (int i = 0; i < mProb.length; i++) {
				for (int j = 0; j < mProb[i].length; j++) {
					pw.print(mProb[i][j]);
					pw.print(',');
				}
				pw.println();
			}

			pw.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
*/

	/**
	 * Add our own cards to our knowledge.
	 */
	public void interpretOwnCards() {		
		interpretPlayerHasCards(mPlayer.getMyIndex(), mPlayer.getMyCards());
	}

	/**
	 * Add a player's answer to our knowledge.
	 */
	public void interpretPlayerAnswered(int iPlayerIndex, int[] iAskedAboutCards,
			int iResponse) {		
		StringBuffer expr = new StringBuffer(50);

		if (iResponse == 0) {
			// player does not have any of the asked about cards

			expr.append("~(");
			for (int i = 0; i < iAskedAboutCards.length; i++) {
				String term = getLogicTerm(iPlayerIndex, iAskedAboutCards[i]);

				if (i > 0) {
					expr.append('|');
				}

				expr.append(term);
			}
			expr.append(')');

			// e.g. "~(p2c3 | p2c5 | p2c7 | p2c9)"
			acceptPremise(expr.toString());
		} else {
			// player has a particular card
			String term = getLogicTerm(iPlayerIndex, iResponse);
			acceptPremise(term);

			// none of the other players can have that card
			expr.append("~(");
			for (int i = 0, n = mPlayer.getNumPlayers() + 1, count = 0; i < n; i++) {
				if (i == iPlayerIndex) {
					// ignore our index
					continue;
				}

				if (count > 0) {
					expr.append('|');
				}

				count++;
				term = getLogicTerm(i, iResponse);
				expr.append(term);
			}
			expr.append(')');

			acceptPremise(expr.toString());
		}
	}

	public void interpretOtherPlayerAnswered(MoveResult iResult) {		
		StringBuffer expr = new StringBuffer(50);

		// player does not have any of the asked about cards

		if (!iResult.response) {
			expr.append('~');
		}

		expr.append("(");
		for (int i = 0; i < iResult.cardlist.length; i++) {
			String term = getLogicTerm(iResult.player, iResult.cardlist[i]);

			if (i > 0) {
				expr.append('|');
			}

			expr.append(term);
		}
		expr.append(')');

		acceptPremise(expr.toString());
	}

	/**
	 * Add the fact that a player exited to our knowledge.
	 */
	public void interpretPlayerExited(int iPlayerIndex, int[] iPlayerCards) {		
		// once a player exits, we can accept the premise that all of his
		// now-revealed cards belong to him
		interpretPlayerHasCards(iPlayerIndex, iPlayerCards);
	}

	private void interpretPlayerHasCards(int iPlayerIndex, int[] iCards) {		
		StringBuffer expr = new StringBuffer(50);
		StringBuffer expr2 = new StringBuffer(50);
		Set c = new HashSet();

		int count;
		expr2.append("~(");
		for (int i = 0; i < iCards.length; i++) {
			c.add(new Integer(iCards[i]));
			String term = getLogicTerm(iPlayerIndex, iCards[i]);			

			if (i > 0) {
				expr.append('&');
			}

			expr.append(term);

			count = 0;
			for (int j = 0, n = mPlayer.getNumPlayers() + 1; j < n; j++) {
				if (j == iPlayerIndex) {
					// ignore our index
					continue;
				}

				if (i > 0 || count > 0) {
					expr2.append('|');
				}
				count++;

				term = getLogicTerm(j, iCards[i]);
				expr2.append(term);
			}
		}
		expr2.append(')');

		// e.g. "p2c3 & p2c5 & p2c7 & p2c9"
		acceptPremise(expr.toString());

		// e.g. "~(p1c3 | p3c3 | p4c3 | p5c3 | ...)"
		acceptPremise(expr2.toString());
		
		// this player can't have any other cards
		StringBuffer expr3 = new StringBuffer(50);		
		count = 0;
		expr3.append("~(");
		for (int i = 1, n = mPlayer.getNumCards(); i <= n; i++) {
			if (!c.contains(new Integer(i))) {
				if (count > 0) {
					expr3.append('|');
				}				
				count++;
				
				expr3.append(getLogicTerm(iPlayerIndex, i));
;			}
		}
		expr3.append(')');
		
		acceptPremise(expr3.toString());
	}

	/**
	 * Combines a player index and a card number into an identifier we can use as
	 * a term in a logic expression.
	 */
	private String getLogicTerm(int iPlayerIndex, int iCard) {
		int cardIndex = iCard - 1;
		String term = mLogicTerms[iPlayerIndex][cardIndex];
		if (term == null) {
			term = "p" + iPlayerIndex + "c" + iCard;
			mLogicTerms[iPlayerIndex][cardIndex] = term;			
		}		

		return term;
	}

	private String getLogicTermHiddden(int iCard) {
		return getLogicTerm(mPlayer.getNumPlayers(), iCard);
	}

	/**
	 * Add the given premise to our list of accepted premises.
	 */
	private void acceptPremise(String iPremise) {
		try {			
			mPremises.add((Formula) mLogic.createExpression(iPremise));
		} catch (ParseException ex) {
			throw new RuntimeException("Error parsing logic expression: " + ex);
		}
	}

	/**
	 * Typesafe enum of claim types.
	 */
	public static class Claim {
		public static final Claim kTrue = new Claim("1");
		public static final Claim kFalse = new Claim("0");
		public static final Claim kUnknown = new Claim("?");

		private final String mLabel;

		private Claim(String iLabel) {
			mLabel = iLabel;
		}

		public String toString() {
			return mLabel;
		}
	}
}