package com.goatsandtigers.minimax;

import java.util.LinkedList;

public abstract class VanillaMinimax implements Cloneable
{
    public static final int UNLIMITED_SEARCH_DEPTH = -1;
    public static final int MINI_HAS_WON           = Integer.MAX_VALUE;
    public static final int STALE_MATE             = 0;
    public static final int MAX_HAS_WON            = Integer.MIN_VALUE;

    private int player = 1; // Must always be 1 or -1

    /**
     * Returns whether it is the "mini" player or "max" player's turn
     * to move
     * @return 1 or -1 
     */
    public int getPlayer()
    {
        return this.player;
    }

    /**
     * Looks maxSearchDepth moves ahead to determine the best move then
     * makes this "perfect" move.
     * @param maxSearchDepth the number of plies to evaluate. This can be
     * any positive integer, or VanillaMinimax.UNLIMITED_SEARCH_DEPTH to
     * evaluate all plies until the end of the game is reached.
     * Note: if the game would end before this many moves have been made
     * then the number of plies evaluated will be less than maxSearchDepth.
     */
    public final void makePerfectMove(int maxSearchDepth)
    {
        if(maxSearchDepth == 0)
        {
            return;
        }

        LinkedList moves     = this.listAllLegalMoves();
        int        bestScore = 0;
        Object     bestMove  = null;

        if(moves.isEmpty())
        {
            this.staleMate();
            return;
        }

        for(Object move : moves)
        {
            VanillaMinimax tempBoard = (VanillaMinimax)this.clone();
            tempBoard.doMove(move);
            int score = tempBoard.evaluate(maxSearchDepth == VanillaMinimax.UNLIMITED_SEARCH_DEPTH ? VanillaMinimax.UNLIMITED_SEARCH_DEPTH : maxSearchDepth - 1);
            if(score * player < bestScore || bestMove == null)
            {
                bestScore = score * player;
                bestMove  = move;
            }
        }
        doMove(bestMove);
    }

    public final int evaluate(int maxSearchDepth)
    {
        int currentScore = this.getCurrentScore();
        if(currentScore == VanillaMinimax.MINI_HAS_WON || currentScore == VanillaMinimax.MAX_HAS_WON)
        {
            return currentScore;
        }
        LinkedList moves = this.listAllLegalMoves();
        if(moves.isEmpty())
        {
            return VanillaMinimax.STALE_MATE;
        }
        int bestScore = 0;
        for(Object move : moves)
        {
            VanillaMinimax tempBoard = (VanillaMinimax)this.clone();
            tempBoard.doMove(move);
            int score = tempBoard.evaluate(maxSearchDepth == VanillaMinimax.UNLIMITED_SEARCH_DEPTH ? VanillaMinimax.UNLIMITED_SEARCH_DEPTH : maxSearchDepth - 1);
            if(score == VanillaMinimax.MINI_HAS_WON && player == -1)
            {
                return VanillaMinimax.MINI_HAS_WON;
            }
            else if(score == VanillaMinimax.MAX_HAS_WON && player == 1)
            {
                return VanillaMinimax.MAX_HAS_WON;
            }
            if(score * player > bestScore)
            {
                bestScore = score * player;
            }
        }
        return bestScore;
    }

    public Object clone()
    {
        try
        {
            return super.clone();
        }
        catch(Exception e)
        {
            return null;
        }
    }

    public abstract int getCurrentScore();
    public abstract LinkedList listAllLegalMoves();
    public abstract void moveAction(Object move);

    /**
     * Calls the abstract method moveAction and keeps track of the fact
     * that it is now the other player's turn. 
     * @param move An object holding details of the move being performed. 
     */
    public final void doMove(Object move)
    {
        this.moveAction(move);
        player *= -1;
    }
    public abstract void staleMate();
}
