package fr.iut_fbleau.HexGame;

import fr.iut_fbleau.GameAPI.*;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Random;

/**
 * Lance un grand nombre de parties Hex entre 2 bots aléatoires et affiche des stats.
 *
 * Exemples :
 *   java fr.iut_fbleau.HexGame.HexSimMain
 *   java fr.iut_fbleau.HexGame.HexSimMain --games 10000 --size 7 --seed 123
 *   java fr.iut_fbleau.HexGame.HexSimMain --games 5000 --size 11 --csv results.csv
 * 
 * À seed identique, la suite de nombres
 * pseudo-aléatoires générée est identique, donc les bots "aléatoires" joueront les mêmes coups
 * dans le même ordre (tant que le code et l'ordre des appels à Random ne changent pas).</p>
 *
 * Intérêt :
 *
 * Reproductibilité</b> : relancer exactement la même simulation pour déboguer / analyser.</li>
 * Comparaison équitable</b> : comparer 2 bots sur les mêmes tirages aléatoires.</li>
 * Si aucun seed n'est fourni, on utilise généralement l'heure courante, ce qui rend chaque exécution différente.</p>
 *
 * long seed;
 * 
 */
public class HexSimMain {

    private static class Stats {
        long win = 0;
        long draw = 0;
        long loss = 0;

        long totalMoves = 0;
        long minMoves = Long.MAX_VALUE;
        long maxMoves = Long.MIN_VALUE;

        void record(Result r, int moves) {
            if (r == Result.WIN) win++;
            else if (r == Result.DRAW) draw++;
            else if (r == Result.LOSS) loss++;
            totalMoves += moves;
            minMoves = Math.min(minMoves, moves);
            maxMoves = Math.max(maxMoves, moves);
        }

        long games() { return win + draw + loss; }

        double winRate() { return games() == 0 ? 0.0 : (double) win / games(); }
        double drawRate() { return games() == 0 ? 0.0 : (double) draw / games(); }
        double lossRate() { return games() == 0 ? 0.0 : (double) loss / games(); }
        double avgMoves() { return games() == 0 ? 0.0 : (double) totalMoves / games(); }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Games: ").append(games()).append("\n");
            sb.append("WIN:  ").append(win).append(String.format(" (%.2f%%)\n", 100.0 * winRate()));
            sb.append("DRAW: ").append(draw).append(String.format(" (%.2f%%)\n", 100.0 * drawRate()));
            sb.append("LOSS: ").append(loss).append(String.format(" (%.2f%%)\n", 100.0 * lossRate()));
            sb.append(String.format("Moves: avg=%.2f, min=%d, max=%d\n", avgMoves(), minMoves, maxMoves));
            return sb.toString();
        }
    }

    private static class Args {
        int size = 7;
        int games = 1000;
        long seed = System.currentTimeMillis();
        int progressEvery = 0;     // 0 = pas de progress
        String csvPath = null;     // si non null, export par partie
    }

    public static void main(String[] args) {
        Args a = parseArgs(args);

        System.out.println("Hex random-vs-random simulation");
        System.out.println("  size=" + a.size + " games=" + a.games + " seed=" + a.seed +
                (a.csvPath != null ? " csv=" + a.csvPath : ""));

        Random master = new Random(a.seed);
        Stats stats = new Stats();

        BufferedWriter csv = null;
        try {
            if (a.csvPath != null) {
                csv = new BufferedWriter(new FileWriter(a.csvPath));
                csv.write("game_index,result_p1,moves\n");
            }

            for (int i = 0; i < a.games; i++) {
                // Nouveau plateau, nouveaux bots (seeds dérivés du seed principal)
                HexBoard board = new HexBoard(a.size);

                EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
                players.put(Player.PLAYER1, new RandomBot(Player.PLAYER1, new Random(master.nextLong())));
                players.put(Player.PLAYER2, new RandomBot(Player.PLAYER2, new Random(master.nextLong())));

                int moves = runOneGame(board, players);

                Result res = board.getResult();
                stats.record(res, moves);

                if (csv != null) {
                    csv.write(i + "," + res + "," + moves + "\n");
                }

                if (a.progressEvery > 0 && (i + 1) % a.progressEvery == 0) {
                    System.out.println("Progress: " + (i + 1) + "/" + a.games);
                }
            }

            System.out.println("\n=== SUMMARY (Result is from PLAYER1 perspective) ===");
            System.out.println(stats);

        } catch (IOException e) {
            System.err.println("I/O error: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (csv != null) {
                try { csv.close(); } catch (IOException ignored) {}
            }
        }
    }

    /**
     * Boucle de jeu (même logique que AbstractGame.run, mais on compte les coups).
     * On ne modifie pas GameAPI.
     */
    private static int runOneGame(IBoard board, EnumMap<Player, AbstractGamePlayer> players) {
        int moves = 0;
        int guardMaxMoves = ((HexBoard) board).getSize() * ((HexBoard) board).getSize(); // au pire : plateau rempli

        while (!board.isGameOver()) {
            AbstractGamePlayer p = players.get(board.getCurrentPlayer());
            IBoard safe = board.safeCopy();
            AbstractPly ply = p.giveYourMove(safe);

            if (!board.isLegal(ply)) {
                throw new IllegalStateException("Illegal move: " + ply + " by " + board.getCurrentPlayer());
            }
            board.doPly(ply);
            moves++;

            if (moves > guardMaxMoves) {
                throw new IllegalStateException("Too many moves (" + moves + "), something is wrong.");
            }
        }
        return moves;
    }

    private static Args parseArgs(String[] args) {
        Args a = new Args();
        for (int i = 0; i < args.length; i++) {
            String s = args[i];
            switch (s) {
                case "--size":
                    a.size = Integer.parseInt(nextArg(args, ++i, "--size requires a value"));
                    break;
                case "--games":
                    a.games = Integer.parseInt(nextArg(args, ++i, "--games requires a value"));
                    break;
                case "--seed":
                    a.seed = Long.parseLong(nextArg(args, ++i, "--seed requires a value"));
                    break;
                case "--progress":
                    a.progressEvery = Integer.parseInt(nextArg(args, ++i, "--progress requires a value"));
                    break;
                case "--csv":
                    a.csvPath = nextArg(args, ++i, "--csv requires a value");
                    break;
                case "--help":
                case "-h":
                    printHelpAndExit();
                    break;
                default:
                    // compat: si l'utilisateur donne juste un nombre, on l'interprète comme size ou games
                    // ex: "7 10000"
                    if (isInt(s)) {
                        int v = Integer.parseInt(s);
                        if (a.size == 11) a.size = v;
                        else a.games = v;
                    } else {
                        System.err.println("Unknown arg: " + s);
                        printHelpAndExit();
                    }
            }
        }
        return a;
    }

    private static String nextArg(String[] args, int idx, String errMsg) {
        if (idx < 0 || idx >= args.length) throw new IllegalArgumentException(errMsg);
        return args[idx];
    }

    private static boolean isInt(String s) {
        try { Integer.parseInt(s); return true; } catch (NumberFormatException e) { return false; }
    }

    private static void printHelpAndExit() {
        System.out.println("Usage: java fr.iut_fbleau.HexGame.HexSimMain [options]\n" +
                "Options:\n" +
                "  --size N       Board size (default 7)\n" +
                "  --games N      Number of games (default 1000)\n" +
                "  --seed N       Random seed (default current time)\n" +
                "  --progress N   Print progress every N games (default 0)\n" +
                "  --csv FILE     Write per-game results to CSV\n" +
                "  -h, --help     Show this help\n");
        System.exit(0);
    }
}
