package game.training;

import java.util.List;
import java.util.Random;

import game.logic.Vector;
import game.logic.GameBoard;

/**
 * A class that represents a training player with specific type.
 */
public class TrainPlayer {
	public static final int AGENT = 0;
	public static final int TRAINER = 1;
	public static final int RANDOM = 2;
	
	private int role; 			/* Red or Black player */
	private Vector weights;
	private int type; 			/* Agent, Random, Trainer */
	private int minimaxDepth;
	private int totalMoves;		/* total moves of the player so far, for epsilon function */
	public double epsilon;
	
	/**
	 * The most simplest constructor.
	 * With the lack of type and depth, it creates a random player.
	 * 
	 * @param role the role of the player (Red or Black)
	 */
	public TrainPlayer(int role){
		this.role = role;
		this.type = RANDOM;
		weights = new Vector(GameBoard.VectorSize);
		weights.setZero();
		this.minimaxDepth = 0;
		this.totalMoves = 0;
	}
	
	/**
	 * A constructor that initializes the player with a depth for the MiniMax tree. Could be used
	 * to initialize a simple trainer.
	 * 
	 * @param role the role of the player (Red or Black)
	 * @param type the type of the player
	 * @param minimaxDepth the depth for the MiniMax tree
	 */
	public TrainPlayer(int role, int type, int minimaxDepth){
		this.role = role;
		this.type = type;
		weights = new Vector(GameBoard.VectorSize);
		weights.setZero();
		this.minimaxDepth = minimaxDepth;
		this.totalMoves = 0;
	}
	
	/**
	 * A constructor that initializes a mature player with weight vector.
	 * 
	 * @param role the role of the player (Red or Black)
	 * @param type the type of the player
	 * @param minimaxDepth the depth for the MiniMax tree
	 * @param weights the weight vector of the player
	 */
	public TrainPlayer(int role, int type, int minimaxDepth, Vector weights){
		this.role = role;
		this.type = type;
		this.weights = new Vector(weights.getSize());
		for (int i = 0; i < weights.getSize(); i++){
			this.weights.setValue(i, weights.getValue(i));
		}
		
		this.minimaxDepth = minimaxDepth;
		this.totalMoves = 0;
	}
	
	/* Getters and Setters */
	public int getRole(){
		return this.role;
	}
	
	public int getType(){
		return this.type;
	}
	
	public Vector getWeights(){
		return this.weights;
	}
	
	public void setRole(int newRole){
		this.role = newRole;
	}
	
	public void setWeights(Vector newWeights){
		for (int i = 0; i < newWeights.getSize(); i++){
			this.weights.setValue(i, newWeights.getValue(i));
		}
	}
	
	/**
	 * This procedure chooses the next move for the player according to its type.
	 * For a RANDOM player - a random move is selected.
	 * For a TRAINER player - the best move using MiniMax algorithm is selected (always exploits).
	 * For an AGENT player - choose a move using an Epsilon-greedy policy. With a probability of Epsilon
	 * the player will make a random move (explore), and with probability of (1-epsilon) will use
	 * the MiniMax algorithm to determine the best move (exploit).
	 * The Epsilon-greedy function was set to sqrt(1/# of moves) which converges to zero, for reasons
	 * that are explained in the project report.
	 * 
	 * @param board the current board data structure
	 * @param watch a boolean for debugging purposes. When set to true, we shall print out some statistics
	 * regarding the choice of the move.
	 * 
	 * @return the move chosen by the player.
	 */
	public int chooseMove(GameBoard board, boolean watch){
		this.totalMoves++;
		this.epsilon = Math.sqrt(1.0/totalMoves);
		int move;
		Best myMove = new Best();
		boolean isRandom = false;
		
		/* **************
		 * AGENT Player
		 ****************/
		if (this.type == AGENT){
			Random r = new Random(System.currentTimeMillis());
			double epsilonCheck = r.nextDouble();
			
			/* In probability of Epsilon will choose a random move */
			if (epsilonCheck < this.epsilon){
				move = randomMove(board.getPossibleMoves());
				isRandom = true;
			} else {
				GameBoard b1 = GameBoard.CopyBoard(board);
				myMove = MiniMax.chooseMove(MiniMax.MAX, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
						b1, this.minimaxDepth, this.role, this.weights);
				move = myMove.getMove();
				/* The only possible option that the move will be -1, is that for every playing
				 * option, we shall loose, a sure loose (the MIN player will always win). So,
				 * it does not really matter what we do, so just play a random move. */
				if (move == -1){
					move = randomMove(board.getPossibleMoves());
					isRandom = true;
				}
			}
			
		}
		
		/* **************
		 * TRAINER Player
		 ****************/
		else if (this.type == TRAINER){
			GameBoard b1 = GameBoard.CopyBoard(board);
			myMove = MiniMax.chooseMove(MiniMax.MAX, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
					b1, this.minimaxDepth, this.role, this.weights);
			
			move = myMove.getMove();
			
			if (move == -1){
				move = randomMove(board.getPossibleMoves());
				isRandom = true;
			}
		}
		
		/* ***********************
		 * RANDOM Player - default
		 *************************/
		else {
			move = randomMove(board.getPossibleMoves());
			isRandom = true;
		}
		
		if (watch){
			System.out.println("Player" + this.role + " chose move " + move);
			System.out.println("Move in the data structure:");
			System.out.println("Move: " + myMove.getMove() + ", Score: " + myMove.getScore());
			System.out.println("Epsilon is: " + epsilon);
			System.out.println("Was it a random move? " + isRandom);
			System.out.println();
		}
		
		return move;
	}
	
	/**
	 * This procedure selects a random move out of a list of possible options.
	 * 
	 * @param options list of possible options
	 * 
	 * @return a move that was selected randomly
	 */
	public int randomMove(List<Integer> options){
		Random r = new Random();
		
		int randomMove = r.nextInt(options.size());
		
		return options.get(randomMove);
	}
	
}
