package org.example;

import org.example.entity.BellmanFordResult;
import org.example.entity.PathResult;
import org.graphstream.algorithm.BellmanFord;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;

import static org.example.GraphManipulation.*;
import static org.example.Utils.getRandomNode;
import static org.example.Utils.saveDat;

/**
 * Classe de test pour comparer les performances de l'algorithme Bellman-Ford
 * implémenté manuellement avec celui de la bibliothèque GraphStream.
 */
public class MainTest {
    private static final int DEFAULT_TESTS_PER_GRAPHCOMPARATE= 5;
    private static final int DEFAULT_TESTS_PER_GRAPH_MY_BELLMAN = 25;
    private static final int DEFAULT_TIMEOUT_SECONDS = 90;
    private static final String GNUPLOTPATH = "gnuplot/dat/";
    private static final String GRAPHPATH = "src/main/resources/";

    public static void main(String[] args) {

        System.setProperty("org.graphstream.ui", "swing");
        File[] dgsFiles = new File(GRAPHPATH).listFiles((dir, name) -> name.endsWith(".dgs"));
        if(dgsFiles !=null) {
            getAllInfoGraph(dgsFiles);
            setUpComparatorOnlyMyAlgorithme(dgsFiles);
            setUpComparatorBetween(dgsFiles);
        }

    }

    /**
     * Affiche les informations de base (nombre de nœuds et d'arêtes) pour chaque graphe.
     * @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
     */
    public static void getAllInfoGraph(File[] dgsFiles) {
        for (File file : dgsFiles) {
            Graph graphFile = getTheFileFromPath(file.getPath());
            System.out.println(file.getName() + " : n Node(" +graphFile.getNodeCount() +") - n Edge(" +graphFile.getEdgeCount()+")");
        }
    }

    /**
     * Effectue plusieurs tests du Bellman-Ford maison sur chaque graphe,
     * avec des nœuds aléatoires, et sauvegarde les résultats dans des fichiers.
     * @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
     */
    public static void setUpComparatorOnlyMyAlgorithme(File[] dgsFiles) {
        for (File file : dgsFiles) {
            System.out.println(file.getName());
            System.out.println("==============================");
            List<List<Double>> result = new ArrayList<>(Collections.emptyList());
            Graph graphFile = getTheFileFromPath(file.getPath());
            for (int i = 0; i < DEFAULT_TESTS_PER_GRAPH_MY_BELLMAN; i++) {
                result.add(
                        BellmanComparatorBetween(graphFile, getRandomNode(graphFile), getRandomNode(graphFile), false));
            }
            String filename = String.format(GNUPLOTPATH + "2_myBellman/" + (file.getName().split("\\.")[0].split("_")[1]) + ".dat");

            saveDat(filename, result);
        }
    }

    /**
     * Effectue la comparaison entre l'algorithme maison et GraphStream pour chaque graphe.
     * Les arêtes sont préalablement pondérées pour l'équité des tests.
     * @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
     */
    public static void setUpComparatorBetween(File[] dgsFiles) {
        Arrays.sort(dgsFiles, (a, b) -> a.getName().equals("graphe_lehavre.dgs") ? -1
                : b.getName().equals("graphe_lehavre.dgs") ? 1 : a.getName().compareTo(b.getName()));// juste pour tester leHavre en premier car plus rapide
        assert dgsFiles != null;
        for (File file : dgsFiles) {
            System.out.println(file.getName());
            System.out.println("==============================");
            List<List<Double>> result = new ArrayList<>(Collections.emptyList());
            Graph graphFile = getTheFileFromPath(file.getPath());
            // Ajout du poids avant de calculer le temps pour équiter entre algorithme
            graphFile.edges().forEach(edge -> {
                double cost = getCostOfTheArete(edge);
                edge.setAttribute("weight", cost);
            });
            for (int i = 0; i < DEFAULT_TESTS_PER_GRAPHCOMPARATE; i++) {
                result.add(
                        BellmanComparatorBetween(graphFile, getRandomNode(graphFile), getRandomNode(graphFile), true));

            }
            String filename = String.format(GNUPLOTPATH + "3_compare/" + (file.getName().split("\\.")[0].split("_")[1]) + ".dat");
            saveDat(filename, result);
        }

    }

    /**
     * Compare les temps d'exécution du Bellman-Ford maison et de GraphStream pour un même couple de nœuds.
     * Les résultats sont renvoyés sous forme de liste de durées en millisecondes.
     *
     * @param graph Le graphe sur lequel effectuer les calculs.
     * @param startNode Le nœud de départ.
     * @param endNode Le nœud d'arrivée.
     * @param withGraphStream Si vrai, exécute également l'algorithme GraphStream.
     * @return Liste des durées (en ms) pour chaque algorithme exécuté.
     */
    public static ArrayList<Double> BellmanComparatorBetween(Graph graph, Node startNode, Node endNode,
            Boolean withGraphStream) {
        ArrayList<Double> results = new ArrayList<>();
        System.out.println("BellmanFord : Node(" + startNode.getId() + ") -> Node(" + endNode.getId() + ")");
        if (graph != null) {
            ExecutorService executor = Executors.newSingleThreadExecutor(); // Pour le timeOut
            try {
                if (withGraphStream) {
                    // Calcule le temps de BellmanFord de GRAPHSTREAM
                    Future<Double> futureGS = executor.submit(() -> {
                        long startGS = System.nanoTime();
                        BellmanFord bf = new BellmanFord();
                        bf.init(graph);
                        bf.setSource(startNode.getId());
                        bf.compute();
                        long endGS = System.nanoTime();
                        double cost = bf.getShortestPathValue(endNode);
                        System.out.println("GraphStream Bellman-Ford = " + (endGS - startGS) / 1_000_000.0 + " ms");
                        results.add((endGS - startGS) / 1_000_000.0);
                        return cost;
                    });

                    try {
                        futureGS.get(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                    } catch (TimeoutException e) {
                        System.out.println("⚠ GraphStream Bellman-Ford > : timeout résultat fixé à "
                                + DEFAULT_TIMEOUT_SECONDS + "s");
                        results.add((double) DEFAULT_TIMEOUT_SECONDS * 1_000);
                    }
                }

                // Calcule le temps de BellmanFord du Mien
                Future<Float> futureMine = executor.submit(() -> {
                    long startMine = System.nanoTime();
                    BellmanFordResult result = BellmanFord(graph, startNode);
                    PathResult pathResult = getPath(result, endNode);
                    long endMine = System.nanoTime();

                    System.out.println("Mon Bellman-Ford = " + (endMine - startMine) / 1_000_000.0 + " ms");
                    results.add((endMine - startMine) / 1_000_000.0);
                    return pathResult.cost();
                });

                try {
                    futureMine.get(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                } catch (TimeoutException e) {
                    System.out
                            .println("⚠ Mon Bellman-Ford > : timeout résultat fixé à " + DEFAULT_TIMEOUT_SECONDS + "s");
                    results.add((double) DEFAULT_TIMEOUT_SECONDS * 1_000);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return results;
    }

}
