package net.woodyfolsom.msproj.policy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.woodyfolsom.msproj.Action;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.Player;
import net.woodyfolsom.msproj.tree.MonteCarloProperties;

public class RootParallelization implements Policy {
	private int numTrees = 1;
	private long timeLimit = 1000L;

	public RootParallelization(int numTrees, long timeLimit) {
		this.numTrees = numTrees;
		this.timeLimit = timeLimit;
	}

	@Override
	public Action getAction(GameConfig gameConfig, GameState gameState,
			Player player) {
		Action bestAction = Action.NONE;
		
		List<PolicyRunner> policyRunners = new ArrayList<PolicyRunner>();
		List<Thread> simulationThreads = new ArrayList<Thread>();
		
		for (int i = 0; i < numTrees; i++) {
			PolicyRunner policyRunner = new PolicyRunner(new MonteCarloUCT(
					new RandomMovePolicy(), timeLimit), gameConfig, gameState,
					player);
			policyRunners.add(policyRunner);
			
			Thread simThread = new Thread(policyRunner);
			simulationThreads.add(simThread);
		}

		for (Thread simThread : simulationThreads) {
			simThread.start();
		}

		for (Thread simThread : simulationThreads) {
			try {
				simThread.join();
			} catch (InterruptedException ie) {
				System.out
						.println("Interrupted while waiting for Monte Carlo simulations to finish.");
			}
		}
		
		Map<Action,Integer> totalReward = new HashMap<Action,Integer>();
		Map<Action,Integer> numSims = new HashMap<Action,Integer>();
		
		for (PolicyRunner policyRunner : policyRunners) {
			Map<Action, MonteCarloProperties> qValues = policyRunner.getQvalues();
			for (Action action : qValues.keySet()) {
				if (totalReward.containsKey(action)) {
					totalReward.put(action, totalReward.get(action) + qValues.get(action).getWins());
				} else {
					totalReward.put(action, qValues.get(action).getWins());
				}
				if (numSims.containsKey(action)) {
					numSims.put(action, numSims.get(action) + qValues.get(action).getVisits());
				} else {
					numSims.put(action, qValues.get(action).getVisits());
				}
			}
		}
		
		double bestValue = 0.0;
		int totalRollouts = 0;
		int bestWins = 0;
		int nValidActions = totalReward.keySet().size();
		
		for (Action action : totalReward.keySet())
		{
			if (action.isPass() && !gameState.getResult().isWinner(player) && nValidActions > 1) {
				continue; //If the best rated action is PASS and I'm not winning and there are other valid actions,
				//keep searching.
			}
			int totalWins = totalReward.get(action);
			int totalSims = numSims.get(action);
			
			totalRollouts += totalSims;
			
			double value = ((double)totalWins) / ((double)totalSims);
			
			if (bestAction.isNone() || bestValue < value) {
				bestAction = action;
				bestValue = value;
				bestWins = totalWins;
			}
			
		}
		
		System.out.println("Action " + bestAction + " selected for "
				+ player
				+ " with simulated win ratio of "
				+ (bestValue * 100.0 + "% among " + numTrees + " parallel simulations."));
		System.out.println("It won "
				+ bestWins + " out of "
				+ totalRollouts + " rollouts among "
				+ " valid actions from the current state.");
		
		return bestAction;
	}

	@Override
	public Action getAction(GameConfig gameConfig, GameState gameState,
			Collection<Action> prohibitedActions, Player player) {
		throw new UnsupportedOperationException(
				"Prohibited actions not supported by this class.");
	}

	@Override
	public int getNumStateEvaluations() {
		// TODO Auto-generated method stub
		return 0;
	}

	class PolicyRunner implements Runnable {
		Map<Action,MonteCarloProperties> qValues;
		
		private GameConfig gameConfig;
		private GameState gameState;
		private Player player;
		private MonteCarlo policy;

		public PolicyRunner(MonteCarlo policy, GameConfig gameConfig,
				GameState gameState, Player player) {
			this.policy = policy;
			this.gameConfig = gameConfig;
			this.gameState = gameState;
			this.player = player;
		}

		public Map<Action,MonteCarloProperties> getQvalues() {
			return qValues;
		}
		
		@Override
		public void run() {
			qValues = policy.getQvalues(gameConfig, gameState, player);
		}
	}

	@Override
	public void setState(GameState gameState) {
		// TODO Auto-generated method stub
		
	}
}