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

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 App {

    // Affiche les statistiques globales du graphe
    private static void printStatsGraph(Graph g, int N, double K) {
        int L = g.getEdgeCount();
        double C = Toolkit.averageClusteringCoefficient(g);
        double p = 1.0 / 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);
        System.out.println("Coefficient de clustering moyen : " + C);

        System.out.println("p = " + p);
        System.out.println("p > ln N / N : " + (p > Math.log(N) / N));
        System.out.println("<k> > ln N : " + (K > Math.log(N)));
        System.out.println("Pour qu'il soit connexe, il faut <k> > " + Math.log(N));
    }

    // É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 = N;
        long[] distributionDistances = new long[distanceMax+1];
        double D = Math.log(N) / Math.log(K); // <d> = ln N / ln <k>
        int sommeDistances = 0;
        int 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);

                sommeDistances += distance;
                paireCompter++;

                if (distance > 0) {
                    int d = distance;
                    if (d > distanceMax) {
                        d = distanceMax;      // on regroupe les grandes distances dans la dernière classe
                    }
                    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 == "ReseauAleatoire") {
            reseau = new SingleGraph("Random");
            gen = new RandomGenerator(nbDegree);
        } 
        // reseau Attachement Préférentiel
        else if (typeGraphe == "ReseauAP") {
            reseau = new SingleGraph("Barabàsi-Albert");
            gen = new BarabasiAlbertGenerator(nbDegree);
        }
        
        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{
        // partie 1
        String filePath = "com-dblp.ungraph.txt";
		Graph g = new SingleGraph("g");
        FileSourceEdge fse = new FileSourceEdge();
        
		fse.addSink(g);
        System.out.println("Lecture du fichier pour le graph réel :");
		try {
			fse.readAll(filePath);
		} catch( IOException e) {
			e.printStackTrace();
		}

        // partie 2 et 3
        System.out.println("Réseau réel :");
        int N = g.getNodeCount();
        double K = Toolkit.averageDegree(g);
        printStatsGraph(g, N, K);

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

        // partie 6

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

        // Partie réseau attachement préférentiel
        System.out.println("\nRéseau Attachement Préférentiel :");
        Graph g2 = genererGraph("ReseauAP", N, (int) K);
        double K2 = Toolkit.averageDegree(g2);
        printStatsGraph(g2, N, K2);
        ecrireDistribDegree(g2, N, "distrib_degrees_AP.dat");
        ecrireDistribDistance(g2, N, K2, "distrib_distances_AP.dat");
    }
}
/*
Réel :
Noeuds : 317080
Arêtes : 1049866
Degré moyen : 6.62208890914917
Degré max : 343
Coefficient de clustering moyen : 0.6324308280637396
p = 3.1537782263151257E-6
p > ln N / N : false
<k> > ln N : false
Pour qu'il soit connexe, il faut <k> > 12.666909386951092
Création fichier distribution degrés : distrib_degrees_reel.dat
Distance moyenne avec formule : 6.700611818856679
Distance moyenne avec BFS: -6.688828324082251
*/

/*
Aleatoire :
Noeuds : 317088
Arêtes : 1111676
Degré moyen : 7.011782169342041
Degré max : 23
Coefficient de clustering moyen : 2.5696693675904325E-5
p = 3.1536986577858514E-6
p > ln N / N : false
<k> > ln N : false
Pour qu'il soit connexe, il faut <k> > 12.666934616858626
Création fichier distribution degrés : distrib_degrees_Aleatoire.dat
Distance moyenne avec formule : 6.5038957130983075
Distance moyenne avec BFS: 6.724713725663493
*/

/*
Attachement Préférentiel :
Noeuds : 317082
Arêtes : 1267958
Degré moyen : 7.997666358947754
Degré max : 2124
Coefficient de clustering moyen : 4.7271754439064036E-4
p = 3.153758333806397E-6
p > ln N / N : false
<k> > ln N : false
Pour qu'il soit connexe, il faut <k> > 12.666915694487653
Création fichier distribution degrés : distrib_degrees_AP.dat
Distance moyenne avec formule : 6.092353581775491
Distance moyenne avec BFS: 4.847179625459661
*/