package net.woodyfolsom.msproj.policy;

import java.util.ArrayList;
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.GameTreeNode;
import net.woodyfolsom.msproj.tree.MonteCarloProperties;

public abstract class MonteCarlo implements Policy {
	protected static final int ROLLOUT_DEPTH_LIMIT = 100;

	protected int numStateEvaluations = 0;
	protected Policy movePolicy;

	protected long searchTimeLimit;
	protected volatile long elapsedTime = 0L;

	public MonteCarlo(Policy movePolicy, long searchTimeLimit) {
		this.movePolicy = movePolicy;
		this.searchTimeLimit = searchTimeLimit;
	}

	/**
	 * Descend the tree from the specified node and return a list of nodes to
	 * grow.
	 * 
	 * @param node
	 * @return
	 */
	public abstract List<GameTreeNode<MonteCarloProperties>> descend(
			GameTreeNode<MonteCarloProperties> node);

	private GameTreeNode<MonteCarloProperties> buildTree(GameConfig gameConfig, GameState gameState, Player player) {
		System.out.println(player + " is thinking for up to "
				+ (searchTimeLimit / 1000.0) + " seconds...");
		long startTime = System.currentTimeMillis();

		if (gameState.getPlayerToMove() != player) {
			throw new RuntimeException("getAction(..." + player
					+ ") was requested but GameState.playerToMove was "
					+ gameState.getPlayerToMove());
		}

		GameTreeNode<MonteCarloProperties> rootNode = new GameTreeNode<MonteCarloProperties>(
				gameState, new MonteCarloProperties());

		do {

			// TODO these return types may need to be lists for some MC methods
			List<GameTreeNode<MonteCarloProperties>> selectedNodes = descend(rootNode);
			List<GameTreeNode<MonteCarloProperties>> newLeaves = new ArrayList<GameTreeNode<MonteCarloProperties>>();

			for (GameTreeNode<MonteCarloProperties> selectedNode : selectedNodes) {
				Player playerToMove = selectedNode.getGameState()
						.getPlayerToMove();

				for (GameTreeNode<MonteCarloProperties> newLeaf : grow(
						gameConfig, selectedNode, playerToMove)) {
					newLeaves.add(newLeaf);
				}
			}

			for (GameTreeNode<MonteCarloProperties> newLeaf : newLeaves) {
				int reward = rollout(gameConfig, newLeaf, player);
				update(newLeaf, reward);
			}

			elapsedTime = System.currentTimeMillis() - startTime;
		} while (elapsedTime < searchTimeLimit);

		return rootNode;
	}
	
	@Override
	public Action getAction(GameConfig gameConfig, GameState gameState,
			Player player) {
		return getBestAction(buildTree(gameConfig,gameState,player));
	}
	
	public Map<Action, MonteCarloProperties> getQvalues(GameConfig gameConfig, GameState gameState,
			Player player) {
		GameTreeNode<MonteCarloProperties> rootNode = buildTree(gameConfig,gameState,player);
		Map<Action,MonteCarloProperties> qValues = new HashMap<Action,MonteCarloProperties>();
		for (Action action : rootNode.getActions()) {
			qValues.put(action, rootNode.getChild(action).getProperties());
		}
		return qValues;
	}

	public long getElapsedTime() {
		return elapsedTime;
	}

	public abstract Action getBestAction(GameTreeNode<MonteCarloProperties> node);

	public abstract List<GameTreeNode<MonteCarloProperties>> grow(
			GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node,
			Player player);

	public abstract int rollout(GameConfig gameConfig,
			GameTreeNode<MonteCarloProperties> node, Player player);

	public abstract void update(GameTreeNode<MonteCarloProperties> node,
			int reward);

	public long getSearchTimeLimit() {
		return searchTimeLimit;
	}

	public int getNumStateEvaluations() {
		return numStateEvaluations;
	}
}