package bagaturchess.bitboard.bughouse;

import bagaturchess.bitboard.bughouse.eval.BughouseDropPieceEval;
import bagaturchess.bitboard.impl.Constants;
import bagaturchess.bitboard.impl1.BoardImpl;

import java.util.*;

public class BughouseBoardsImpl {

    private int timeForEvaluation;
    private int depth;
    private Character teamMoving;

    BoardImpl[] boards;
    ArrayList<ArrayList<Character>> availablePieces;
    ArrayList<ArrayList<String>> availableMoves;

    StockfishThread stockThread;
    PositionsComparator positionsComparator;
    BughouseMoveGen moveGenerator;
    StringBuilder stringBuilder;
    BughouseDropPieceEval bugEval;
    BughousePositionsBuilder builder;


    public BughouseBoardsImpl(int numberOfBoards, int timeForEachPosition, int depth) {
        this.boards = generateAllBughouseBoardsFromStartingPosition(numberOfBoards);
        this.timeForEvaluation = timeForEachPosition;
        this.availableMoves = new ArrayList<>();
        this.availablePieces = new ArrayList<>();
        this.stringBuilder = new StringBuilder();
        this.stockThread = new StockfishThread();
        this.bugEval = new BughouseDropPieceEval();
        this.builder = new BughousePositionsBuilder();
        this.depth = depth;
        this.teamMoving = '0';

        if (stockThread.startEngine()) {
            System.out.println("Engine has started..");
        } else {
            System.out.println("Oops! Something went wrong..");
        }

        for (int i = 0; i<numberOfBoards; i++) {
            this.availablePieces.add(new ArrayList<>());
        }

        this.positionsComparator = new PositionsComparator("", "");
        this.moveGenerator = new BughouseMoveGen();
    }

    private BoardImpl[] generateAllBughouseBoardsFromStartingPosition(int numberOfBoards) {
        BoardImpl[] boards = new BoardImpl[numberOfBoards];
        for (int i = 0; i < numberOfBoards; i++) {
            boards[i] = new BoardImpl(Constants.INITIAL_BOARD, null);
        }
        return boards;
    }

    private void generateAllBughouseBoardsFromFEN(ArrayList<String> fenBoards, int numberOfBoards) {
        BoardImpl[] boards = new BoardImpl[numberOfBoards];
        for (int i = 0; i < numberOfBoards; i++) {
            boards[i] = (new BoardImpl(fenBoards.get(i), null));
        }
        this.boards = boards;
    }

    /* NOT IN USE */
    public void playNextMove() {

        ArrayList<String> positions = new ArrayList<>();
        for (BoardImpl board : boards) {
            positions.add(board.toString());
        }

        GameConfiguration nextPosition = minimax(positions, depth, availablePieces, teamMoving);
        System.out.println(nextPosition);
        generateAllBughouseBoardsFromFEN(nextPosition.positions, 2);
        setPieces(createTakenPiecesFromString(nextPosition.takenPieces));

    }

    public void setPieces( ArrayList<ArrayList<Character>> takenPieces) {
        this.availablePieces = takenPieces;
    }

    private GameConfiguration minimax(ArrayList<String> currentPositions, int depth,
                        ArrayList<ArrayList<Character>> takenPieces,
                        Character teamToMove) {
        int highestSeenValue = Integer.MIN_VALUE;
        int lowestSeenValue = Integer.MAX_VALUE;
        int currentValue;
        GameConfiguration bestMove = null;

        ArrayList<GameConfiguration> moves = generatePossibleMoves(currentPositions, takenPieces,
                teamToMove);
        for (int j = 0; j < moves.size(); j++) {
            GameConfiguration currentMove = moves.get(j);
            currentValue = teamToMove == '0' ?
                    min(currentMove.getPositions(), depth - 1,currentMove.getTakenPieces(), '1'):
                    max(currentMove.getPositions(), depth - 1, currentMove.getTakenPieces(), '0');
            if (teamToMove == '0' &&
                    currentValue >= highestSeenValue) {
                highestSeenValue = currentValue;
                bestMove = currentMove;
            } else if (teamToMove == '1' && currentValue <= lowestSeenValue) {
                lowestSeenValue = currentValue;
                bestMove = currentMove;
            }
        }
        return bestMove;
    }

    private int min(ArrayList<String> currentPositions, int depth,
                    String takenPieces,
                    Character teamToMove) {

        if (depth == 0) {
            float res = 0;
            for (String currentEvaluatedPosition: currentPositions) {
                if (builder.isEndgame(currentEvaluatedPosition)) {
                    return 10000;
                }
                if (teamToMove == '0') {
                    res += stockThread.getEvalScore(currentEvaluatedPosition, timeForEvaluation);
                } else {
                    res -= stockThread.getEvalScore(currentEvaluatedPosition, timeForEvaluation);
                }
            }

            ArrayList<ArrayList<Character>> takenPiecesList = createTakenPiecesFromString(takenPieces);

            for (ArrayList<Character> currentBoardPieces: takenPiecesList) {
                res += (bugEval.getPiecesListEval(currentBoardPieces)/100);
            }
            return (int) (res*100);
        }

        for (String currentEvaluatedPosition: currentPositions) {
            if (builder.isEndgame(currentEvaluatedPosition)) {
            return 10000;
            }
        }

        int lowestSeenValue = Integer.MAX_VALUE;
        ArrayList<ArrayList<Character>> takenPiecesList = createTakenPiecesFromString(takenPieces);
            ArrayList<GameConfiguration> moves = generatePossibleMoves(currentPositions, takenPiecesList,
                    teamToMove);
            Character nextTeam = teamToMove == '0' ? '1' : '0';
        for (GameConfiguration move: moves) {
            final int currentValue = max(move.getPositions(), depth - 1, move.getTakenPieces(), nextTeam);
            if (currentValue <= lowestSeenValue) {
                lowestSeenValue = currentValue;
            }
        }
        return lowestSeenValue;
    }


    private int max(ArrayList<String> currentPositions, int depth,
                    String takenPieces,
                    Character teamToMove) {

        if (depth == 0) {
            float res = 0;
            for (String currentEvaluatedPosition : currentPositions) {
                if (builder.isEndgame(currentEvaluatedPosition)) {
                    return 10000;
                }
                if (teamToMove == '0') {
                    res += stockThread.getEvalScore(currentEvaluatedPosition, timeForEvaluation);
                } else {
                    res -= stockThread.getEvalScore(currentEvaluatedPosition, timeForEvaluation);
                }
            }

            ArrayList<ArrayList<Character>> takenPiecesList = createTakenPiecesFromString(takenPieces);
            for (ArrayList<Character> currentBoardPieces : takenPiecesList) {
                res += (bugEval.getPiecesListEval(currentBoardPieces) / 100);
            }

            return (int) (res * 100);

        }

        for (String currentEvaluatedPosition: currentPositions) {
            if (builder.isEndgame(currentEvaluatedPosition)) {
                return 10000;
            }
        }

        int highestSeenValue = Integer.MIN_VALUE;
        ArrayList<ArrayList<Character>> takenPiecesList = createTakenPiecesFromString(takenPieces);
        ArrayList<GameConfiguration> moves = generatePossibleMoves(currentPositions, takenPiecesList,
                teamToMove);
        Character nextTeam = teamToMove == '0' ? '1' : '0';
        for (GameConfiguration move: moves) {
            final int currentValue = min(move.getPositions(), depth - 1, move.getTakenPieces(), nextTeam);
            if (currentValue >= highestSeenValue) {
                highestSeenValue = currentValue;
            }
        }
        return highestSeenValue;
    }


    /*

    Used for generating possible moves of ply 1. Given list of positions in FEN standard, takenPieces and
    teamToMove, it creates a list of game configurations which contains all configs moves that could be reached

     */
    public ArrayList<GameConfiguration> generatePossibleMoves(ArrayList<String> currentPositions,
                                                                                         ArrayList<ArrayList<Character>> takenPieces,
                                                                                         Character teamToMove) {
        ArrayList<GameConfiguration> allMovesConfiguration = new ArrayList<>();

        for (int i = 0; i < currentPositions.size(); i++) {

            // classic moves
            String currentPosition = currentPositions.get(i);
            ArrayList<String> currentPositionPossibleMoves =
                    moveGenerator.generateFENsFromClassicMovesFromPosition(currentPosition);
            for (String moveMadeFEN : currentPositionPossibleMoves) {

                // make a local copy of game information, so it can be updated and passed onto next recursion call
                ArrayList<String> positionsAfterMove = new ArrayList<>(currentPositions);

                positionsComparator.compareTwoPositions(currentPosition, moveMadeFEN);
                char takenPiece = positionsComparator.getPieceIfTakeMove();

                ArrayList<ArrayList<Character>> updatedTakenPieces = new ArrayList<>();
                deepClonePieces(updatedTakenPieces, takenPieces);
                String takenPiecesString = buildTakenPieces(updatedTakenPieces);

                // if a move was a taking one, add the taken piece to takenPiece accordingly
                if (takenPiece != 'x') {
                    if (i == 0) {
                        updatedTakenPieces.get(1).add(takenPiece);
                    } else {
                        updatedTakenPieces.get(0).add(takenPiece);
                    }
                }

                positionsAfterMove.set(i, moveMadeFEN);
                GameConfiguration currentConfig = new GameConfiguration(positionsAfterMove, takenPiecesString);
                currentConfig.swapSides(teamToMove);
                allMovesConfiguration.add(currentConfig);
            }

            //bughouse moves generation

            ArrayList<String> currentBughouseMoves = (i == 1) ?
                    moveGenerator.generateBughouseMovesFromFEN(currentPositions.get(i),
                            takenPieces.get(0)) :
                    moveGenerator.generateBughouseMovesFromFEN(currentPositions.get(i),
                            takenPieces.get(1));

            for (String currentBughouseMove: currentBughouseMoves) {
                String[] FENParts = currentBughouseMove.split(" ");
                String pieceUsed = FENParts[FENParts.length - 1];

                ArrayList<ArrayList<Character>> updatedTakenPieces = new ArrayList<>(takenPieces.size());
                deepClonePieces(updatedTakenPieces, takenPieces);

                if (i == 1) {
                    updatedTakenPieces.get(0).remove(Character.valueOf(pieceUsed.charAt(0)));
                } else {
                    updatedTakenPieces.get(1).remove(Character.valueOf(pieceUsed.charAt(0)));
                }

                ArrayList<String> positionsAfterMove = new ArrayList<>();
                deepClonePositions(positionsAfterMove, currentPositions);

                String takenPiecesString = buildTakenPieces(updatedTakenPieces);

                positionsAfterMove.set(i, currentBughouseMove.substring(0, currentBughouseMove.length() - 1).trim());

                GameConfiguration currentConfig= new GameConfiguration(positionsAfterMove, takenPiecesString);
                currentConfig.swapSides(teamToMove);
                allMovesConfiguration.add(currentConfig);
            }

        }
        return allMovesConfiguration;
    }


    private ArrayList<ArrayList<Character>> createTakenPiecesFromString(String takenPieces) {
        ArrayList<ArrayList<Character>> pieces = new ArrayList<>();
        pieces.add(new ArrayList<>());
        pieces.add(new ArrayList<>());

        String[] split = takenPieces.split(" ");
        if (!split[0].equals("0")) {
            for (int i = 1; i < split[0].length(); i++) {
                pieces.get(0).add(split[0].charAt(i));
            }
        }
        if (!split[1].equals("1")) {
            for (int i = 0; i < split[1].length(); i++) {
                pieces.get(1).add(split[1].charAt(i));
            }
        }
        return pieces;
    }


    private String buildTakenPieces(ArrayList<ArrayList<Character>> takenPieces) {
        stringBuilder.setLength(0);
        for (int i = 0; i < takenPieces.size(); i++) {
            stringBuilder.append(i);
            for (int j = 0; j < takenPieces.get(i).size(); j++) {
                stringBuilder.append(takenPieces.get(i).get(j));
            }
            stringBuilder.append(" ");
        }
        return stringBuilder.toString();
    }


    private void deepClonePieces(ArrayList<ArrayList<Character>> updatedTakenPieces,
                                 ArrayList<ArrayList<Character>> takenPieces) {
        for (int i = 0; i < takenPieces.size(); i++) {
            updatedTakenPieces.add(new ArrayList<>());
            updatedTakenPieces.get(i).addAll(takenPieces.get(i));
        }
    }

    private void deepClonePositions(ArrayList<String> updatedPositions,
                                    ArrayList<String> positions) {
            updatedPositions.addAll(positions);
    }


    public static class GameConfiguration {
        public final ArrayList<String> positions;
        public final String takenPieces;

        public GameConfiguration(ArrayList<String> positions, String takenPieces) {
            this.positions= positions;
            this.takenPieces= takenPieces;
        }

        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
            for (String currPos: positions) {
                res.append(currPos)
                        .append(" ");
            }

            res.append(" ").append(takenPieces);
            return res.toString();
        }

        public ArrayList<String> getPositions() {
            return positions;
        }

        public String getTakenPieces() {
            return takenPieces;
        }

        public void swapSides(Character teamToMove) {
            StringBuilder builder = new StringBuilder();
            int i = 0;
            for (String position: positions) {
                builder.setLength(0);
                String[] positionSplit = position.split(" ");
                builder.append(positionSplit[0]).append(" ").append(swapTeam(teamToMove))
                .append(" ")
                .append(positionSplit[2]).append(" ")
                .append(positionSplit[3]).append(" ")
                .append(positionSplit[4]).append(" ")
                .append(positionSplit[5]).append(" ");
                positions.set(i, builder.toString());
                i++;
            }
        }

        public Character swapTeam(Character team) {
            return team.equals('0') ? 'b' : 'w';
        }
    }

    public  static void main(String[] args) {
        BughouseBoardsImpl game = new BughouseBoardsImpl(2, 1, 2);

        /*

        Ukazka generovania tahov a nasledne pouzitie minimaxu z pozicie

        */

            ArrayList<String> currentPos = new ArrayList<>();
            String firstPos = "r1b1kb1r/ppp1pppp/2n5/3P4/3qp3/5P2/PPP1P1PP/R1BQKBNR w KQkq - 0 1";
            String secondPos = "r1bqkb1r/pppppppp/2n5/8/4n3/4NNp1/PPPP1PPP/RNBQKB1R w KQkq - 0 1";
            currentPos.add(firstPos);
            currentPos.add(secondPos);
            ArrayList<ArrayList<Character>> pieces = new ArrayList<>();
            pieces.add(new ArrayList<>());
            pieces.add(new ArrayList<>());
            pieces.get(0).add('n');
            Character teamMoving = '0';

            GameConfiguration move = game.minimax(currentPos, 2, pieces, teamMoving);
            System.out.println(move);
    }
}
