package org.example;

import org.example.entity.BellmanFordResult;
import org.example.entity.PathResult;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSource;
import org.graphstream.stream.file.FileSourceFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Cette classe fournit des méthodes utilitaires pour manipuler des graphes représentant
 * des villes et calculer les plus courts chemins pour un vélo électrique.
 */
public class GraphManipulation {


    /**
     * Reconstruit le chemin le plus court jusqu'à un nœud de destination
     * à partir des résultats de Bellman-Ford.
     *
     * @param bellmanFordResult Résultat de l'algorithme Bellman-Ford contenant les coûts et prédécesseurs.
     * @param endNode Le nœud de destination du chemin à reconstruire.
     * @return Un objet PathResult contenant la liste des nœuds du chemin (dans l'ordre source → destination)
     *         et le coût total pour atteindre endNode.
     */
    public static PathResult getPath(BellmanFordResult bellmanFordResult, Node endNode){
        Map<Node, Node> pred = bellmanFordResult.pred();
        List<Node> chemin = new ArrayList<>();
        Node current = endNode;
        while (current != null) {
            chemin.add(current);
            current = pred.get(current);
        }

        return new PathResult(chemin.reversed(),bellmanFordResult.mapCost().get(endNode));
    }


    /**
     * Marque un chemin dans le graphe pour l'affichage.
     * Les nœuds et les arêtes du chemin seront mis en évidence.
     *
     * @param chemin Liste des nœuds représentant le chemin (de la source à la destination).
     */
    public static void markPath( List<Node> chemin) {
        for (int i = 0; i < chemin.size(); i++) {
            Node node = chemin.get(i);
            if (i < chemin.size() - 1) {
                Node nextNode = chemin.get(i + 1);
                Edge edge = node.getEdgeBetween(nextNode);
                if (edge != null) {
                    edge.setAttribute("ui.class", "inPath");
                }
            }
        }
    }


    /**
     * Calcule le coût en électricité pour parcourir une arête d'un graphe représentant
     * une ville pour un vélo électrique.
     * @param edge L'arête dont on souhaite calculer le coût.
     * @return Le coût en Wh pour parcourir cette arête. Les descentes donnent un coût négatif.
     */
    public static Float getCostOfTheArete(Edge edge) {
        Integer aHauteur = edge.getSourceNode().getAttribute("hauteur",Integer.class);
        Integer bHauteur = edge.getTargetNode().getAttribute("hauteur",Integer.class);
        return (aHauteur<=bHauteur)? (5*(bHauteur-aHauteur)+2f) : ((bHauteur-aHauteur)/2f);
    }


    /**
     * Charge un graphe depuis un fichier .dgs et retourne l'objet Graph correspondant.
     *
     * @param path Chemin vers le fichier .dgs.
     * @return L'objet Graph chargé depuis le fichier.
     */
    public static Graph getTheFileFromPath(String path) {
        Graph graph = new SingleGraph("LoadedGraph");
        try {
            FileSource fs = FileSourceFactory.sourceFor(path);
            if (fs == null) {
                throw new IOException("Format de fichier non reconnu : " + path);
            }
            fs.addSink(graph);
            fs.readAll(path);
            fs.removeSink(graph);
        } catch (Exception e) {
            System.err.println("Erreur lors du chargement du graphe : " + e.getMessage());
            return graph;
        }

        return graph;
    }

    /**
     * Implémente l'algorithme de Bellman-Ford pour calculer le plus court chemin
     * depuis un nœud source dans un graphe orienté avec des coûts qui peuvent être négatifs.
     *
     * @param graph Le graphe orienté sur lequel calculer les plus courts chemins.
     * @param startNode Le nœud source depuis lequel on calcule les plus courts chemins.
     * @return Un objet BellmanFordResult contenant les maps des coûts et des prédécesseurs.
     *   1. mapCost : les coûts minimaux pour atteindre chaque nœud depuis startNode.
     *   2. pred : les prédécesseurs de chaque nœud pour reconstruire le plus court chemin.
     */
    public static BellmanFordResult BellmanFord(Graph graph, Node startNode){
        Map<Node,Float> mapCost = new HashMap<>();
        Map<Node, Node> pred = new HashMap<>();
        graph.forEach( node -> {
            float value = node == startNode ? 0f: Float.POSITIVE_INFINITY;
            mapCost.put(node, value);
            pred.put(node, null);
        });
        AtomicBoolean updated = new AtomicBoolean(true);
        for (int k = 1; k < graph.getNodeCount() - 1 && updated.get(); k++) {
            updated.set(false);
            graph.edges().forEach(edge ->{
                float edgeCost = edge.hasAttribute("weight")
                        ? ((Number) edge.getAttribute("weight")).floatValue()
                        : getCostOfTheArete(edge);

                Node sourceNode  =edge.getSourceNode();//u
                Node targetNode  =edge.getTargetNode();//v
                if (mapCost.get(sourceNode) +edgeCost<mapCost.get(targetNode)) {
                    mapCost.put(targetNode,mapCost.get(sourceNode) +edgeCost);
                    pred.put(targetNode, sourceNode);
                    updated.set(true);
                }
            });
        }
        return new BellmanFordResult(mapCost,pred);
    }
}
