package fr.iut_fbleau.HexGame;

import fr.iut_fbleau.GameAPI.*;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.Random;


public class Simulation extends AbstractGame {

    //ATTRIBUTS
    private HexPly bestmove;
    private float bestoutcome;
    private int MAXDEPTH = 9;
    private int EVALDEPTH = 10;
    private LinkedList<Integer[]> taken = new LinkedList<Integer[]>();

    //ATTRIBUTS QUE JE NE VOUDRAIS PAS CRÉER IDÉALEMENT
    private IBoard simCurrentBoard;
    private EnumMap<Player, AbstractGamePlayer> simmapPlayers;

    //CONSTRUCTEUR
    public Simulation(IBoard b, EnumMap<Player,AbstractGamePlayer> m){
        super(b, m);
        simCurrentBoard = b;
        simmapPlayers = m;
    }

    //METHODES
    /*Le jeu de Hex ne peut jamais finir avec le résultat null. En utilisant cette propriété, on peut avoir cet algorithme simplifié du monte-carlo*/
    private float MonteCarlo(HexBoard position, Player current){
        RandomBot simplay = new RandomBot(current, new Random().nextLong());
        HexBoard simpos = position; 
        LinkedList<Integer[]> ctaken = taken;
        HexPly testmove;
        float wins = 0;
        float losses = 0;
        int count = 0;
        for(int i=0; i<EVALDEPTH; i++){
            while(!simpos.isGameOver()){
                count++;
                testmove = (HexPly) simplay.giveYourMove(simpos);
                if(!ctaken.contains(new Integer[]{testmove.getRow(), testmove.getCol()}) && simpos.isLegal(testmove)){
                    ctaken.add(new Integer[]{testmove.getRow(), testmove.getCol()});
                    simpos.doPly(testmove);
                    if(simpos.getResult()==Result.LOSS){
                        losses++;
                    } else if(simpos.getResult()==Result.WIN){
                        wins++;
                    }
                }
            }
            //System.out.println("count:"+count);
            for (int j=0; j<count; j++) {
                simpos.undoPly();
            }
            ctaken = taken;
            count = 0;
        }
        //System.out.println("    wins : "+wins+"/losses : "+losses);
        //System.out.println("    eval : "+(wins-losses)/EVALDEPTH);
        return (wins-losses)/EVALDEPTH;
    }

    private float explMAX(HexBoard position, int depth){
        if (position.getResult()==Result.LOSS) {
            return -1.0f;
        } else if (position.getResult()==Result.WIN){
            return 1.0f;
        } else if (depth==MAXDEPTH) {
            return MonteCarlo(position, Player.PLAYER1);
        } else {
                float bestcase = -1.0f;
                HexPly bestcasemove;
                HexPly testmove;
                for (int i=0; i<position.getSize(); i++) {
                        for (int j=0; j<position.getSize(); j++) {
                                if(depth==0){
                                    //System.out.println("MAX New Line :");
                                }
                                Integer[] t = new Integer[]{i, j}; 
                                testmove = new HexPly(Player.PLAYER1, i, j);
                                if(!taken.contains(t) && position.isLegal(testmove)){
                                        //System.out.println("  MAX test move : "+Integer.toString(i)+","+Integer.toString(j));
                                        taken.add(t);
                                        position.doPly(testmove);
                                        float val = explMIN(position, depth+1);  
                                        if (val >= bestcase) {
                                                //System.out.println("     MAX new best case");
                                                bestcase = val;
                                                bestcasemove = testmove;
                                                if (depth==0) {
                                                    this.bestoutcome = bestcase;
                                                    this.bestmove = bestcasemove;
                                                }
                                        }
                                        position.undoPly();
                                        taken.remove(t);
                                }
                        }
                }
                return bestcase;
        }
    }


    private float explMIN(HexBoard position, int depth){
        if (position.getResult()==Result.LOSS) {
            return -1.0f;
        } else if (position.getResult()==Result.WIN){
            return 1.0f;
        } else if (depth==MAXDEPTH) {
            return MonteCarlo(position, Player.PLAYER2);
        } else {
                float bestcase = 1.0f;
                HexPly bestcasemove;
                HexPly testmove;
                for (int i=0; i<position.getSize(); i++) {
                        for (int j=0; j<position.getSize(); j++) {
                                if(depth==0){
                                    //System.out.println("MIN New Line :");
                                }
                                Integer[] t = new Integer[]{i, j}; 
                                testmove = new HexPly(Player.PLAYER2, i, j);
                                if(!taken.contains(t) && position.isLegal(testmove)){
                                        //System.out.println("  MIN test move : "+Integer.toString(i)+","+Integer.toString(j));
                                        taken.add(t);
                                        position.doPly(testmove);
                                        float val = explMAX(position, depth+1);  
                                        if (val <= bestcase) {
                                                //System.out.println("     MIN new best case");
                                                bestcase = val;
                                                bestcasemove = testmove;
                                                if (depth==0) {
                                                    this.bestoutcome = bestcase;
                                                    this.bestmove = bestcasemove;
                                                }
                                        }
                                        
                                        position.undoPly();
                                        taken.remove(t);
                                }
                        }
                }
                return bestcase;
        }
    }

        private float explMAXAB(HexBoard position, int depth, float A, float B){
        if (position.getResult()==Result.LOSS) {
            return -1.0f;
        } else if (position.getResult()==Result.WIN){
            return 1.0f;
        } else if (depth==MAXDEPTH) {
            return MonteCarlo(position, Player.PLAYER1);
        } else {
                float bestcase = A;
                HexPly bestcasemove;
                HexPly testmove;
                for (int i=0; i<position.getSize(); i++) {
                        for (int j=0; j<position.getSize(); j++) {
                                if(depth==0){
                                    //System.out.println("MAX New Line :");
                                }
                                Integer[] t = new Integer[]{i, j}; 
                                testmove = new HexPly(Player.PLAYER1, i, j);
                                if(!taken.contains(t) && position.isLegal(testmove)){
                                        //System.out.println("  MAX test move : "+Integer.toString(i)+","+Integer.toString(j));
                                        taken.add(t);
                                        position.doPly(testmove);
                                        float val = explMINAB(position, depth+1, bestcase, B);  
                                        if (val >= bestcase) {
                                                //System.out.println("     MAX new best case");
                                                bestcase = val;
                                                bestcasemove = testmove;
                                                if (depth==0) {
                                                    this.bestoutcome = bestcase;
                                                    this.bestmove = bestcasemove;
                                                }
                                                if(bestcase>=B){
                                                    return bestcase;
                                                }
                                        }
                                        position.undoPly();
                                        taken.remove(t);
                                }
                        }
                }
                return bestcase;
        }
    }
    private float explMINAB(HexBoard position, int depth, float A, float B){
        if (position.getResult()==Result.LOSS) {
            return -1.0f;
        } else if (position.getResult()==Result.WIN){
            return 1.0f;
        } else if (depth==MAXDEPTH) {
            return MonteCarlo(position, Player.PLAYER2);
        } else {
                float bestcase = B;
                HexPly bestcasemove;
                HexPly testmove;
                for (int i=0; i<position.getSize(); i++) {
                        for (int j=0; j<position.getSize(); j++) {
                                if(depth==0){
                                    //System.out.println("MIN New Line :");
                                }
                                Integer[] t = new Integer[]{i, j}; 
                                testmove = new HexPly(Player.PLAYER2, i, j);
                                if(!taken.contains(t) && position.isLegal(testmove)){
                                        //System.out.println("  MIN test move : "+Integer.toString(i)+","+Integer.toString(j));
                                        taken.add(t);
                                        position.doPly(testmove);
                                        float val = explMAXAB(position, depth+1, A, bestcase);  
                                        if (val <= bestcase) {
                                                //System.out.println("     MIN new best case");
                                                bestcase = val;
                                                bestcasemove = testmove;
                                                if (depth==0) {
                                                    this.bestoutcome = bestcase;
                                                    this.bestmove = bestcasemove;
                                                }
                                                if(bestcase<=A){
                                                    return bestcase;
                                                }
                                        }
                                        
                                        position.undoPly();
                                        taken.remove(t);
                                }
                        }
                }
                return bestcase;
        }
    }  


    private AbstractPly GiveBestMove(IBoard board) {
        if (!(board instanceof HexBoard)) {
            throw new IllegalArgumentException("Ce joueur attend un HexBoard.");
        }
        HexBoard hb = (HexBoard) board;
        float bestcase;
        if(hb.getCurrentPlayer()==Player.PLAYER1){
            bestcase = explMAXAB(hb, 0, -1.0f, 1.0f);
        } else {
            bestcase = explMINAB(hb, 0, -1.0f, 1.0f);
        }
        return this.bestmove;
    }

    @Override
    public Result run(){
        while(!simCurrentBoard.isGameOver()) {
            AbstractGamePlayer player = simmapPlayers.get(simCurrentBoard.getCurrentPlayer());
            IBoard board = simCurrentBoard.safeCopy();
            AbstractPly ply = GiveBestMove(board);
            HexPly concretePly = (HexPly) ply;

            if (simCurrentBoard.isLegal(ply)) {
                simCurrentBoard.doPly(ply);
                taken.add(new Integer[]{concretePly.getRow(), concretePly.getCol()});
                System.out.println("Player "+player+" goes ("+concretePly.getRow()+","+concretePly.getCol()+")");
            }
            else throw new IllegalStateException("Player "+ player + " is a bloody cheat. He tried playing : "+concretePly.getRow()+","+concretePly.getCol()+" I give up.");
        }
	    return simCurrentBoard.getResult();
    }

    public Result runForArena(){
        while(!simCurrentBoard.isGameOver()){
            AbstractGamePlayer player = simmapPlayers.get(simCurrentBoard.getCurrentPlayer());
            IBoard board = simCurrentBoard.safeCopy();
            AbstractPly ply;
            if(player.jesuisMinimax()){
                ply = GiveBestMove(board);
            } else {
                ply = player.giveYourMove(board);
            }
            HexPly concretePly = (HexPly) ply;

            if (simCurrentBoard.isLegal(ply)) {
                simCurrentBoard.doPly(ply);
                taken.add(new Integer[]{concretePly.getRow(), concretePly.getCol()});
                System.out.println("Player "+player+" goes ("+concretePly.getRow()+","+concretePly.getCol()+")");
            }
            else throw new IllegalStateException("Player "+ player + " is a bloody cheat. He tried playing : "+concretePly.getRow()+","+concretePly.getCol()+" I give up.");
        }
        return simCurrentBoard.getResult();
    }

    
}
