package univlehavre.etu.mn211377.TP_Reseaux_Complexes;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;

import org.graphstream.graph.BreadthFirstIterator;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSourceEdge;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.algorithm.generator.BarabasiAlbertGenerator;
import org.graphstream.algorithm.generator.Generator;
import org.graphstream.algorithm.generator.RandomGenerator;


public class MesureInteraction {

    // Affiche les statistiques globales du graphe
    private static void printStatsGraph(Graph g, int N, double K, String typeGraphe) {
        int L = g.getEdgeCount();
        double C = Toolkit.averageClusteringCoefficient(g);
        double pER = K / (N - 1); // ER pour Erdős–Rényi
        double lnN = Math.log(N);
        
        int degreeMax = 0;
        for (Node n : g) {
            int d = n.getDegree();
            if (d > degreeMax) {
                degreeMax = d;
            }
        }

        System.out.println("Noeuds : " + N);
        System.out.println("Arêtes : " + L);
        System.out.println("Degré moyen : " + K);
        System.out.println("Degré max : " + degreeMax);

        if (typeGraphe.equals("ReseauReel")) {
        	// pER = <k>/(N-1) : c’est le p qu’aurait un graphe ER ayant la même taille et le même degré moyen.
            // Dans ER, le clustering attendu vaut environ pER, donc ça sert de valeur de comparaison pour le DBLP.
            System.out.println("Comparaison ER (même N et <k>) : clustering attendu ≈ pER = " + pER);
        }
        
        if (typeGraphe.equals("ReseauAleatoire")) {
        	// seuil de connexité ~ p > ln(N)/N (équivalent à <k> > ln(N)).
            // "true" ici signifie : un ER de cette taille serait connexe avec haute probabilité (pour N grand).
            System.out.println("pER = " + pER);
            System.out.println("ln(N) = " + lnN);
            System.out.println("pER > ln(N)/N ? " + (pER > lnN / N));
            // vérifie avec haute probabilité si le graphe est connexe pour un ER (connexe si K > lnN)
            System.out.println("<k> > ln(N) ? " + (K > lnN)); 
            System.out.println("Seuil ER de connexité : <k> ≈ ln(N)");
        }
        
        System.out.println("Coefficient de clustering moyen : " + C);
        System.out.println("Graphe connexe ? " + Toolkit.isConnected(g));
    }

    // Écrit la distribution des degrés dans un fichier
    private static void ecrireDistribDegree(Graph g, int N, String outputFile) throws IOException {
        int[] distributionDegres = Toolkit.degreeDistribution(g);

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) {
            bw.write("# k\tp(k)\n");
            for (int k = 0; k < distributionDegres.length; k++) {
                if (distributionDegres[k] != 0) {
                    double pk = (double) distributionDegres[k] / N;
                    bw.write(k + "\t" + pk + "\n");
                }
            }
        }
        System.out.println("Création fichier distribution degrés : " + outputFile);
    }

    private static void ecrireDistribDistance(Graph g, int N, double K, String outputFile) throws IOException {
        // Distance moyenne
        // <d> = 1/(N(N-1))*somme des distances
        // ou <d> = ln N / ln <k>
        int distanceMax = 50;
        long[] distributionDistances = new long[distanceMax+1];
        double D = Math.log(N) / Math.log(K); // <d> = ln N / ln <k>
        long sommeDistances = 0;
        long paireCompter = 0; // nombre de paires (source, cible) effectivement prises en compte

        // 1000 sommets choisis au hasards
        for (Node source : Toolkit.randomNodeSet(g, 1000)) {
            BreadthFirstIterator it = new BreadthFirstIterator(source);

            while (it.hasNext()) {
                Node u = it.next();
                int distance = it.getDepthOf(u);

                if (distance > 0) {
                    int d = distance;
                    if (d > distanceMax) {
                        d = distanceMax;      // on regroupe les grandes distances dans la dernière classe
                    }
                    sommeDistances += distance;
                    paireCompter++;
                    distributionDistances[d]++;
                }
            }
        }

        // moyenne sur toutes les paires (source, cible) explorées
        double moyenneDistances = (double) sommeDistances / paireCompter;

        System.out.println("Distance moyenne avec formule : " + D);
        System.out.println("Distance moyenne avec BFS: " + moyenneDistances);

        long totalPourDistribution = 0L;
        for (int d = 1; d <= distanceMax; d++) {
            totalPourDistribution += distributionDistances[d];
        }

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) {
            bw.write("# d\tp(d)\n");
            for (int d = 1; d <= distanceMax; d++) {
                if (distributionDistances[d] > 0) {
                    double proba = distributionDistances[d] / (double) totalPourDistribution;
                    bw.write(d + "\t" + proba + "\n");
                }
            }
        }
        System.out.println("Création fichier distribution distances.");
    }

    private static Graph genererGraph(String typeGraphe, int nbNodes, int nbDegree) {
        Graph reseau = null;
        Generator gen = null;
        // reseau Aleatoire
        if (typeGraphe.equals("ReseauAleatoire")) {
            reseau = new SingleGraph("Random");
            gen = new RandomGenerator(nbDegree);
        } 
        // reseau Attachement Préférentiel
        else if (typeGraphe.equals("ReseauAP")) {
            reseau = new SingleGraph("Barabàsi-Albert");
            BarabasiAlbertGenerator ba = new BarabasiAlbertGenerator(nbDegree);
            ba.setExactlyMaxLinksPerStep(true);
            gen = ba;
        }
        
        gen.addSink(reseau);
        gen.begin();
        for(int i=0; i<nbNodes; i++) {
            gen.nextEvents();
        }
        gen.end();

        return reseau;
    }

    public static void main(String[] args) throws IOException{
    	String outDir = "target/dat/";
    	new java.io.File(outDir).mkdirs();
    	
        // partie 1
    	Graph g = new SingleGraph("g");
        FileSourceEdge fse = new FileSourceEdge();
    	InputStream in = MesureInteraction.class.getClassLoader().getResourceAsStream("com-dblp.ungraph.txt");
    	if (in == null) {
    	    throw new IOException("Resource com-dblp.ungraph.txt introuvable");
    	}
    	fse.addSink(g);
    	System.out.println("Lecture du fichier pour le graph réel :");
    	try {
	    	fse.readAll(in);
	    } catch( IOException e) {
			e.printStackTrace();
		}
    	
    	fse.removeSink(g);
    	in.close();
    	
        // partie 2 et 3
        System.out.println("Réseau réel :");
        int N = g.getNodeCount();
        double K = Toolkit.averageDegree(g);
        printStatsGraph(g, N, K, "ReseauReel");

        // partie 4
        ecrireDistribDegree(g, N, outDir + "distrib_degrees_reel.dat");
        
        // partie 5
        ecrireDistribDistance(g, N, K, outDir + "distrib_distances_reel.dat");

        // partie 6 avec les deux réseaux différents

        // Partie réseau aléatoire
        System.out.println("\nRéseau Aléatoire :");
        Graph g1 = genererGraph("ReseauAleatoire", N, (int) Math.round(K));
        double K1 = Toolkit.averageDegree(g1);
        printStatsGraph(g1, N, K1, "ReseauAleatoire");
        ecrireDistribDegree(g1, N, outDir + "distrib_degrees_Aleatoire.dat");
        ecrireDistribDistance(g1, N, K1, outDir + "distrib_distances_Aleatoire.dat");

        // Partie réseau attachement préférentiel
        System.out.println("\nRéseau Attachement Préférentiel :");
        int m = (int) Math.round(K / 2.0); // pour viser <k> ≈ 2m, ou m ≈ k/2. m doit être un int ici.
        System.out.println("m calculé = " + m);
        Graph g2 = genererGraph("ReseauAP", N, m);
        double K2 = Toolkit.averageDegree(g2);
        printStatsGraph(g2, N, K2, "ReseauAP");
        ecrireDistribDegree(g2, N, outDir + "distrib_degrees_AP.dat");
        ecrireDistribDistance(g2, N, K2, outDir + "distrib_distances_AP.dat");

    }
}
/*
Lecture du fichier pour le graph réel :
Réseau réel :
Noeuds : 317080
Arêtes : 1049866
Degré moyen : 6.62208890914917
Degré max : 343
Comparaison ER (même N et <k>) : clustering attendu ≈ pER = 2.088466568000142E-5
Coefficient de clustering moyen : 0.6324308280637396
Graphe connexe ? true
Création fichier distribution degrés : target/dat/distrib_degrees_reel.dat
Distance moyenne avec formule : 6.700611818856679
Distance moyenne avec BFS: 6.805221556772918
Création fichier distribution distances.

Réseau Aléatoire :
Noeuds : 317080
Arêtes : 1110146
Degré moyen : 7.002131938934326
Degré max : 21
pER = 2.2083240892441084E-5
ln(N) = 12.666909386951092
pER > ln(N)/N ? false
<k> > ln(N) ? false
Seuil ER de connexité : <k> ≈ ln(N)
Coefficient de clustering moyen : 2.9537639995935495E-5
Graphe connexe ? false
Création fichier distribution degrés : target/dat/distrib_degrees_Aleatoire.dat
Distance moyenne avec formule : 6.508485221187103
Distance moyenne avec BFS: 6.723662929353368
Création fichier distribution distances.

Réseau Attachement Préférentiel :
m calculé = 3
Noeuds : 317080
Arêtes : 951240
Degré moyen : 5.999962329864502
Degré max : 1862
Coefficient de clustering moyen : 3.5764565665108363E-4
Graphe connexe ? true
Création fichier distribution degrés : target/dat/distrib_degrees_AP.dat
Distance moyenne avec formule : 7.069561506361703
Distance moyenne avec BFS: 5.3803822146391616
Création fichier distribution distances.
*/