package com.goatsandtigers.minimax;

import java.util.LinkedList;

public abstract class MinimaxAlphaBeta 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;
    public static final int MAX_TURN               = 1;
    public static final int MIN_TURN               = -1;

    private int player = MinimaxAlphaBeta.MAX_TURN; // Must always be 1 or -1

    private class AlphaBeta
    {
        int alpha = MinimaxAlphaBeta.MINI_HAS_WON;
        int beta  = MinimaxAlphaBeta.MAX_HAS_WON;
    }

    public final int getPlayer()
    {
        return this.player;
    }

    public final void makePerfectMove(int maxSearchDepth)
    {
        if(maxSearchDepth == 0)
        {
            return;
        }

        LinkedList moves = this.listAllLegalMoves();
        if(moves.isEmpty())
        {
            this.staleMate();
            return;
        }
        else if(moves.size() == 1)
        {
            doMove(moves.get(0));
            return;
        }

        int     bestScore = this.player == MinimaxAlphaBeta.MAX_TURN ? MinimaxAlphaBeta.MINI_HAS_WON : MinimaxAlphaBeta.MAX_HAS_WON;
        Object  bestMove  = null;

        for(Object move : moves)
        {
            MinimaxAlphaBeta tempBoard = (MinimaxAlphaBeta)this.clone();
            tempBoard.doMove(move);
            int score = tempBoard.evaluate(maxSearchDepth == MinimaxAlphaBeta.UNLIMITED_SEARCH_DEPTH ? MinimaxAlphaBeta.UNLIMITED_SEARCH_DEPTH : maxSearchDepth - 1, new AlphaBeta());
            if(score * player < bestScore || bestMove == null)
            {
                bestScore = score * player;
                bestMove  = move;
            }
        }
        doMove(bestMove);
    }

    public final int evaluate(int maxSearchDepth, AlphaBeta alphaBeta)
    {
        int currentScore = this.getCurrentScore();
        if(currentScore == MinimaxAlphaBeta.MINI_HAS_WON || currentScore == MinimaxAlphaBeta.MAX_HAS_WON)
        {
            return currentScore;
        }
        LinkedList moves = this.listAllLegalMoves();
        if(moves.isEmpty())
        {
            return MinimaxAlphaBeta.STALE_MATE;
        }
        int bestScore = 0;
        for(Object move : moves)
        {
            MinimaxAlphaBeta tempBoard = (MinimaxAlphaBeta)this.clone();
            tempBoard.doMove(move);
            int score;
            if(maxSearchDepth == 0)
            {
                score = tempBoard.getCurrentScore();
            }
            else
            {
                score = tempBoard.evaluate(maxSearchDepth == MinimaxAlphaBeta.UNLIMITED_SEARCH_DEPTH ? MinimaxAlphaBeta.UNLIMITED_SEARCH_DEPTH : maxSearchDepth - 1, alphaBeta);

                // Alpha-beta pruning
                if(this.player != MinimaxAlphaBeta.MIN_TURN)
                {
                    if(score < alphaBeta.alpha)
                    {
                        return score;
                    }
                    else if(score < alphaBeta.beta)
                    {
                        alphaBeta.beta = score;
                    }
                }
                else
                {
                    if(score > alphaBeta.beta)
                    {
                        return score;
                    }
                    else if(score > alphaBeta.alpha)
                    {
                        alphaBeta.alpha = score;
                    }
                }
            }
            if(score == MinimaxAlphaBeta.MINI_HAS_WON && player == -1)
            {
                return MinimaxAlphaBeta.MINI_HAS_WON;
            }
            else if(score == MinimaxAlphaBeta.MAX_HAS_WON && player == 1)
            {
                return MinimaxAlphaBeta.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);
    public final void doMove(Object move)
    {
        this.moveAction(move);
        player *= -1;
    }
    public abstract void staleMate();
}
