package unlh.ri;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.algorithm.generator.BarabasiAlbertGenerator;
import org.graphstream.algorithm.generator.Generator;
import org.graphstream.algorithm.generator.RandomGenerator;
import org.graphstream.algorithm.generator.WattsStrogatzGenerator;
import org.graphstream.graph.BreadthFirstIterator;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.DefaultGraph;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSourceEdge;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class MesuresRI {

    private static final String FILE_PATH = "data/com-dblp.ungraph.txt";
    private static final String OUTPUT_DATA = "data/dd_dblp.dat";

    public static void main(String... args) throws IOException {
        Graph graph = new DefaultGraph("g");
        graph.addAttribute("ui.quality");
        graph.addAttribute("ui.antialias");
        graph.addAttribute("ui.stylesheet", "url('styles/style.css')");

        FileSourceEdge fs = new FileSourceEdge();
        try {
            fs.addSink(graph);
            fs.readAll(FILE_PATH);

            int nodeCount = graph.getNodeCount(),
                    edgeCount = graph.getEdgeCount();
            int[] dd = Toolkit.degreeDistribution(graph);
            double avgDegree = Toolkit.averageDegree(graph),
                    avgDistance = computeAverageDistance(graph, 1000),
                    avgClusteringCoef = Toolkit.averageClusteringCoefficient(graph);
            boolean isGraphConex = Toolkit.isConnected(graph);

            for (int k = 0; k < dd.length; k++) {
                if (dd[k] != 0) {
                    generateData(OUTPUT_DATA, String.format(Locale.US, "%6d%20.8f%n", k, (double) dd[k] / graph.getNodeCount()));
                }
            }
            // Random and Barbasi Albert graph part
            Graph randomGraph = generateRandomGraph("random-graph", nodeCount, avgDegree);
            Graph bbaGraph = generateBarbasiAlbert("barbasi-albert", nodeCount, avgDegree);

            System.out.println("Noeud du reseau aleatoire = " + randomGraph.getNodeCount());
            System.out.println("Noeud du reseau Barabasi-Albert = " + bbaGraph.getNodeCount());

            System.out.println("Liens du reseau aleatoire = " + randomGraph.getEdgeCount());
            System.out.println("Liens du reseau Barabasi-Albert = " + bbaGraph.getEdgeCount());

            System.out.println("degré moyen du reseau aleatoire= " + Toolkit.averageDegree(randomGraph));
            System.out.println("degré moyen du reseau Barabasi-Albert= " + Toolkit.averageDegree(bbaGraph));

            System.out.println("coefficient de clustering du reseau aleatoire : " + Toolkit.averageClusteringCoefficient(randomGraph));
            System.out.println("coefficient de clustering du reseau Barabasi-Albert : " + Toolkit.averageClusteringCoefficient(bbaGraph));

            System.out.println("Le reseau aleatoire" + ((Toolkit.isConnected(randomGraph)) ? " est" : " n'est pas") + " connexe");
            System.out.println("Le reseau Barabasi-Albert" + ((Toolkit.isConnected(bbaGraph)) ? " est" : " n'est pas") + " connexe");

            System.out.println("la distance moyenne dans le reseau aléatoire = " + Math.log(randomGraph.getNodeCount()) / Math.log(Toolkit.averageDegree(randomGraph)));
            System.out.println("la distance moyenne dans le reseau de Barabasi-Albert  = " + Math.log(bbaGraph.getNodeCount()) / Math.log(Math.log(bbaGraph.getNodeCount())));

            System.out.println("Coeficient de clustering =  " + Toolkit.averageClusteringCoefficient(variantFn(100, 4, 0.9)));
            double avgDistRandomGraph = computeAverageDistance(randomGraph, 100);

            // Propagation dans le réseau
            double beta = 1.0 / 7.0;
            double mu = 1.0 / 14.0;
            System.out.println("Le taux de propagation est :" + beta / mu);

            Propagation propagation = new Propagation();
            Graph propGraph = generateRandomGraph("propagation-graph", nodeCount, avgDegree);

            System.out.println("Le seuil épidémique du réseau de collaboration = " + avgDegree / Propagation.degVariance(graph));
            System.out.println("Le seuil épidémique du réseau aleatoire = " + Toolkit.averageDegree(propGraph) / Propagation.degVariance(propGraph));

            // Simulation 1 case
            propagation.withoutInfection(graph);
            propagation.infectRandomNode(graph);
            propagation.spread(graph, 1.0 / 7.0, 1.0 / 14.0);
            propagation.generateData("data/epidemie_first_case.dat");

            // Simulation 2nd case
            propagation.infectRandomNode(graph);
            Propagation.randomImmunition(graph);
            Graph s2Graph = propagation.spread(graph, 1.0 / 7.0, 1.0 / 14.0);
            //propagation.generateData("src/main/resources/deuxiemeCas");
            Graph gNewAleaatoire = Propagation.removeInfected(s2Graph);
            System.out.println("Le seuil épidémique du réseau avec stratégies d'immunisation aleatoire = " + Toolkit.averageDegree(gNewAleaatoire) / Propagation.degVariance(gNewAleaatoire));
            Propagation.reset();

            // Simulation 3rd case
            propagation.infectRandomNode(graph);
            Propagation.selectiveImmunition(graph);
            Graph s3Graph = propagation.spread(graph, 1.0 / 7.0, 1.0 / 14.0);
            //propagation.generateData("src/main/resources/troisiemeCas");
            Graph g4 = Propagation.removeInfected(s3Graph);
            System.out.println("Le seuil épidémique du réseau avec stratégies d'immunisation seclective = " + Toolkit.averageDegree(g4) / Propagation.degVariance(g4));

        } catch (IOException e) {
            e.printStackTrace();
            fs.end();
        } finally {
            fs.removeSink(graph);
        }
    }

    public static Graph variantFn(int node, int degree, double p) {
        Graph graph = new SingleGraph("graph");
        Generator g = new WattsStrogatzGenerator(node, degree, p);
        g.addSink(graph);
        g.begin();
        while (g.nextEvents()) ;
        g.end();
        graph.display(false);
        return graph;
    }

    private static double computeAverageDistance(Graph graph, int size) {
        double distance = 0, nb = 0;
        List<Node> randomNodeSet = Toolkit.randomNodeSet(graph, size);
        for (Node currNode : randomNodeSet) {
            BreadthFirstIterator<Node> bfi = new BreadthFirstIterator<>(currNode);
            while (bfi.hasNext()) {
                Node opNode = bfi.next();
                Integer key = bfi.getDepthOf(opNode);
                distance += bfi.getDepthOf(opNode);
                nb++;
            }
        }
        return distance / nb;
    }

    /*
     * Writting into a file
     * */
    public static void generateData(String fileName, String line) throws IOException {
        BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
        try {
            writer.write(line);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writer.flush();
            writer.close();
        }
    }

    public static double normalize(Map<Integer, Double> map) {
        return map
                .values()
                .stream()
                .reduce(0.0, Double::sum)
                .doubleValue();
    }

    public static Graph generateRandomGraph(String graphName, int size, double degree) {
        Graph graph = new SingleGraph(graphName);
        System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer");
        graph.addAttribute("ui.quality");
        graph.addAttribute("ui.antialias");
        Generator g = new RandomGenerator(degree);
        g.addSink(graph);
        g.begin();
        for (int i = 0; i < size; i++)
            g.nextEvents();
        g.end();
        return graph;
    }

    public static Graph generateBarbasiAlbert(String graphName, int size, double degree) {
        Graph graph = new SingleGraph(graphName);
        Generator g = new BarabasiAlbertGenerator((int) degree);
        g.addSink(graph);
        g.begin();
        for (int i = 0; i < size; i++) {
            g.nextEvents();
        }
        g.end();
        return graph;
    }
}


