package net.woodyfolsom.msproj.policy;

import java.util.ArrayList;
import java.util.List;

import net.woodyfolsom.msproj.Action;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.GoGame;
import net.woodyfolsom.msproj.Player;
import net.woodyfolsom.msproj.StateEvaluator;
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);
	
	@Override
	public Action getAction(GameConfig gameConfig, GameState gameState,
			Player player) {
		long startTime = System.currentTimeMillis();
		
		//If for some reason no moves are evaluated within the time limit, pass.
		//Note that this may lose the game by forfeit even when picking any random move could
		//result in a win.
		
		GameTreeNode<MonteCarloProperties> rootNode = new GameTreeNode<MonteCarloProperties>(gameState, new MonteCarloProperties());
		StateEvaluator stateEvaluator = new StateEvaluator(gameConfig);
		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>>();
			
			Player nextPlayer = GoGame.getNextPlayer(player);
			
			for (GameTreeNode<MonteCarloProperties> selectedNode: selectedNodes) {
				for (GameTreeNode<MonteCarloProperties> newLeaf : grow(gameConfig, selectedNode, nextPlayer)) {
					newLeaves.add(newLeaf);
				}
			}
			
			for (GameTreeNode<MonteCarloProperties> newLeaf : newLeaves) {
				int reward = rollout(gameConfig, stateEvaluator, newLeaf, player);
				update(newLeaf, reward);
			}
			
			elapsedTime = System.currentTimeMillis() - startTime;
		//} while (elapsedTime < searchTimeLimit);
		//TODO: for debugging, temporarily specify the number of state evaluations rather than time limit
		} while (numStateEvaluations < searchTimeLimit);
		
		return getBestAction(rootNode);
	}
	
	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, StateEvaluator stateEvaluator, GameTreeNode<MonteCarloProperties> node, Player player);
	
	public abstract void update(GameTreeNode<MonteCarloProperties> node, int reward);
	
	public long getSearchTimeLimit() {
		return searchTimeLimit;
	}
	
	public int doRollout() {
		return 0;
	}
	
	public int getNumStateEvaluations() {
		return numStateEvaluations;
	}
}