package org.example;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.algorithm.generator.Generator;
import org.graphstream.algorithm.generator.RandomGenerator;
import org.graphstream.graph.*;
import org.graphstream.graph.implementations.*;
import org.graphstream.stream.file.FileSourceEdge;
import org.graphstream.algorithm.generator.BarabasiAlbertGenerator;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;


public class DBLPNetworkAnalysis {
    public static void main(String[] args) throws Exception {
        String filePath = "C:/Users/celia/IdeaProjects/TP_RI/com-dblp.ungraph.txt/com-dblp.ungraph.txt"; // Chemin du fichier d'entrée
        Graph graph = new SingleGraph("DBLP Collaboration Network");
        // --- Chargement des données ---
        FileSourceEdge fileSource = new FileSourceEdge();
        fileSource.addSink(graph);
        try {
            fileSource.readAll(filePath);
        } finally {
            fileSource.removeSink(graph);
        }
        // --- Mesures de base ---

        int nodeCount = graph.getNodeCount();
        int edgeCount = graph.getEdgeCount();
        double averageDegree = Toolkit.averageDegree(graph);
        double clusteringCoefficient = Toolkit.averageClusteringCoefficient(graph);
        double randomClusteringCoefficient = averageDegree / nodeCount;
        boolean isConnected = Toolkit.isConnected(graph);
        double minAverageDegreeForConnectivity = Math.log(nodeCount);

        // Estimation de la distance moyenne par échantillonnage

        int sampleSize = 1000;
        List<Node> nodes = new ArrayList<>();
        graph.nodes().forEach(nodes::add);

        Random random = new Random();
        double totalDistance = 0;
        int pairCount = 0;
        Map<Integer, Integer> distanceDistribution = new HashMap<>(); // Pour stocker les distances

        for (int i = 0; i < sampleSize; i++) {
            Node startNode = nodes.get(random.nextInt(nodes.size()));

            // BFS à partir du nœud de départ
            BreadthFirstIterator bfs = new BreadthFirstIterator(startNode, true);
            Map<Node, Integer> distances = new HashMap<>();

            while (bfs.hasNext()) {
                Node node = bfs.next();
                int depth = bfs.getDepthOf(node);
                distances.put(node, depth);

                // Enregistrer la distribution des distances
                if (depth > 0) {
                    distanceDistribution.put(depth, distanceDistribution.getOrDefault(depth, 0) + 1);
                }
            }

            for (int distance : distances.values()) {
                if (distance > 0) {
                    totalDistance += distance;
                    pairCount++;
                }
            }
        }

        // Calculer la distance moyenne dans un réseau aléatoire

        double degreeAverage = Toolkit.averageDegree(graph);
        double NetworkDistance = Math.log(graph.getNodeCount()) / Math.log(degreeAverage);
        System.out.println("Distance moyenne dans notre réseau  : " + NetworkDistance);

        // Sauvegarder la distribution des distances dans un fichier
        try (PrintWriter writer = new PrintWriter(new FileWriter("distance_distribution.dat"))) {
            for (Map.Entry<Integer, Integer> entry : distanceDistribution.entrySet()) {
                writer.printf(Locale.US, "%d %f%n", entry.getKey(),
                        (double) entry.getValue() / pairCount);
            }
            System.out.println("Distribution des distances sauvegardée dans 'distance_distribution.dat'.");
        } catch (IOException e) {
            e.printStackTrace();
        }


        //Calcul de la distribution des degrés
        calculateDegreeDistribution(graph, "degree_distribution.txt");

        // Résultats des mesures principales
        System.out.println("Nombre de noeuds : " + nodeCount);
        System.out.println("Nombre de liens : " + edgeCount);
        System.out.println("Degré moyen : " + averageDegree);
        System.out.println("Coefficient de clustering (réel) : " + clusteringCoefficient);
        System.out.println("Coefficient de clustering (aléatoire) : " + randomClusteringCoefficient);
        System.out.println("Le réseau est-il connexe ? : " + (isConnected ? "Oui" : "Non"));
        System.out.println("Degré moyen minimal pour qu'un graphe aléatoire soit connexe : " + minAverageDegreeForConnectivity);

        // Génération et comparaison des réseaux aléatoires et Barabasi-Albert
        compareWithGeneratedNetworks(graph);

        System.out.println("Analyse du réseau généré par la méthode de copie :");
        Graph grapheCopie = genererGrapheCopie(nodeCount, 0.5);
        printGraphMeasures(grapheCopie);
    }

    // --- Calcul de la distribution des degrés ---
    public static void calculateDegreeDistribution(Graph graph, String outputFile) throws IOException {
        int[] distribution = Toolkit.degreeDistribution(graph);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
            writer.write("# Degree\tProbability\n");
            for (int k = 0; k < distribution.length; k++) {
                if (distribution[k] != 0) {
                    double probability = (double) distribution[k] / graph.getNodeCount();
                    writer.write(k + "\t" + probability + "\n");
                }
            }
        }
        System.out.println("Distribution des degrés écrite dans : " + outputFile);
    }

    // --- Comparaison avec des réseaux générés ---
    public static void compareWithGeneratedNetworks(Graph initialGraph) throws Exception {

        // --- Générer un graphe aléatoire ---
        System.out.println("\nGénération du réseau aléatoire...");
        Graph randomGraph = createRandomGraph(initialGraph);
        System.out.println("Réseau aléatoire :");
        printGraphMeasures(randomGraph);
        calculateDegreeDistribution(randomGraph, "random_degree_distribution.txt");
        double degreeAverage = Toolkit.averageDegree(randomGraph);
        double NetworkDistance = Math.log(randomGraph.getNodeCount()) / Math.log(degreeAverage);
        System.out.println("Distance moyenne du réseau Aléatoire : " + NetworkDistance);


        // --- Générer un graphe Barabási-Albert ---
        System.out.println("\nGénération du réseau Barabási-Albert...");
        Graph baGraph = generateBarabasiAlbertGraph(initialGraph);
        System.out.println("Réseau Barabási-Albert :");
        printGraphMeasures(baGraph);
        calculateDegreeDistribution(baGraph, "barabasi_degree_distribution.txt");


        // --- Sauvegarder les résultats ---
        try (FileWriter writer = new FileWriter("comparison_results.txt")) {
            writer.write("Comparaison des réseaux générés :\n");

            writer.write("\nRéseau aléatoire :\n");
            writer.write(captureGraphMeasures(randomGraph));

            writer.write("\nRéseau Barabási-Albert :\n");
            writer.write(captureGraphMeasures(baGraph));
        }
        System.out.println("\nLes résultats de la comparaison ont été sauvegardés dans 'comparison_results.txt'.");
    }

    // Méthode pour capturer les métriques d'un graphe
    public static String captureGraphMeasures(Graph graph) {
        return String.format("Nombre de noeuds : %d\nNombre de liens : %d\nDegré moyen : %.4f\nCoefficient de clustering : %.4f\n",
                graph.getNodeCount(),
                graph.getEdgeCount(),
                Toolkit.averageDegree(graph),
                Toolkit.averageClusteringCoefficient(graph));
    }

    // --- Génération d'un réseau aléatoire ---
    public static Graph createRandomGraph(Graph initialGraph) {

        double averageDegree = Toolkit.averageDegree(initialGraph);

        Generator generator = new RandomGenerator(averageDegree, false);
        Graph graphAlea = new SingleGraph("graphe aléatoire");
        int N = 317080;

        // graphe aléatoire générer de taille N
        generator.addSink(graphAlea);
        generator.begin();
        while (graphAlea.getNodeCount() < N) {
            generator.nextEvents();
        }
        generator.end();
        if (graphAlea.getNodeCount() == N) {
            System.out.println("Graph aléatoire générer !");
        }


        return graphAlea;
    }


    // --- Génération d'un réseau Barabasi-Albert ---
    public static Graph generateBarabasiAlbertGraph(Graph initialGraph) {
        int nodeCount = initialGraph.getNodeCount();
        double averageDegree = Toolkit.averageDegree(initialGraph);

        Graph barabasiGraph = new SingleGraph("Barabasi-Albert Network");
        BarabasiAlbertGenerator generator = new BarabasiAlbertGenerator((int) averageDegree);
        generator.addSink(barabasiGraph);
        generator.begin();
        while (barabasiGraph.getNodeCount() < nodeCount) {
            generator.nextEvents();
        }
        generator.end();
        return barabasiGraph;
    }
    public static Graph genererGrapheCopie(int nombreNoeuds, double probabilitéCopie) {
        Graph grapheCopie = new SingleGraph("GrapheCopie");
        Random aleatoire = new Random();

        // Ajouter un premier nœud pour démarrer le réseau
        grapheCopie.addNode("0");

        for (int i = 1; i < nombreNoeuds; i++) {
            Node nouveauNoeud = grapheCopie.addNode(String.valueOf(i));

            // Choisir un nœud aléatoire existant
            Node noeudCible = grapheCopie.getNode(aleatoire.nextInt(grapheCopie.getNodeCount()));

            // Parcourir les voisins de noeudCible
            noeudCible.edges().forEach(edge -> {
                if (edge != null) { // Vérifier que l'arête n'est pas null
                    Node voisin = edge.getOpposite(noeudCible);

                    // Connecter au voisin avec probabilité p
                    String edgeIdVoisin = nouveauNoeud.getId() + "-" + voisin.getId();
                    if (aleatoire.nextDouble() < probabilitéCopie && grapheCopie.getEdge(edgeIdVoisin) == null) {
                        grapheCopie.addEdge(edgeIdVoisin, nouveauNoeud, voisin);
                    }
                }
            });

            // Connecter le nouveau nœud au nœud cible
            String edgeIdCible = nouveauNoeud.getId() + "-" + noeudCible.getId();
            if (grapheCopie.getEdge(edgeIdCible) == null) {
                grapheCopie.addEdge(edgeIdCible, nouveauNoeud, noeudCible);
            }
        }

        return grapheCopie;
    }



    // --- Affichage des mesures d'un graphe ---
    public static void printGraphMeasures(Graph graph) {
        System.out.println("Nombre de nœuds : " + graph.getNodeCount());
        System.out.println("Nombre de liens : " + graph.getEdgeCount());
        System.out.println("Degré moyen : " + Toolkit.averageDegree(graph));
        System.out.println("Coefficient de clustering : " + Toolkit.averageClusteringCoefficient(graph));
    }
}