package univlehavre.etu.mn211377.TP_Reseaux_Complexes;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Random;

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.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSourceEdge;

public class PropagationReseau {

    // Paramètres (simulation jour par jour)
    private static final int NB_JOURS = 90;      // 3 mois ~ 90 jours

    // plusieurs simulations 
    private static final int NB_RUNS = 20;

    // 1 mail / semaine / voisin -> proba de transmission par voisin et par jour
    private static final double BETA = 1.0 / 7.0;

    // 2 mises à jour / mois ~ 2/30 par jour -> proba de guérison par jour
    private static final double MU = 2.0 / 30.0;

    private enum Scenario {
        RIEN,
        IMMUN_ALEA_50,
        IMMUN_SELECTIVE
    }

    // générer graphe DBLP
    private static Graph lireDBLP() throws IOException {
        Graph g = new SingleGraph("DBLP");
        FileSourceEdge fse = new FileSourceEdge();
        InputStream in = PropagationReseau.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 DBLP :");
        try {
            fse.readAll(in);
        } finally {
            fse.removeSink(g);
            in.close();
        }
        return g;
    }

    // générer graphe réseau aléatoire ou Barabasi-Albert
    private static Graph genererGraph(String typeGraphe, int nbNodes, int nbDegree) {
        Graph reseau = null;
        Generator gen = null;

        if (typeGraphe.equals("ReseauAleatoire")) {
            reseau = new SingleGraph("Random");
            gen = new RandomGenerator(nbDegree);
        } else if (typeGraphe.equals("ReseauAP")) {
            reseau = new SingleGraph("Barabasi-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;
    }

    // moments <k> et <k^2>
    private static double[] momentsDegres(Graph g) {
        int N = g.getNodeCount();
        long sumK = 0;
        long sumK2 = 0;
        for (Node n : g) {
            int k = n.getDegree();
            sumK += k;
            sumK2 += (long) k * (long) k;
        }
        return new double[] { (double) sumK / N, (double) sumK2 / N };
    }

    // moments sur réseau "modifié" (immunisation = suppression des nœuds immunisés)
    // on calcule donc le degré effectif uniquement vers les non-immunisés
    private static double[] momentsDegresEffectifs(Graph g, boolean[] immunise) {
        long sumK = 0;
        long sumK2 = 0;
        int count = 0;

        for (Node u : g) {
            int ui = u.getIndex();
            if (immunise[ui]) continue;

            int kEff = 0;
            for (Edge e : u) {
                Node v = e.getOpposite(u);
                if (!immunise[v.getIndex()]) kEff++;
            }

            sumK += kEff;
            sumK2 += (long) kEff * (long) kEff;
            count++;
        }

        if (count == 0) return new double[] { 0.0, 0.0 };
        return new double[] { (double) sumK / count, (double) sumK2 / count };
    }

    // seuil réseau : lambda_c (seuil épidémique) = <k>/<k^2>
    private static double seuilEpidemique(double kMoyen, double k2Moyen) {
        if (k2Moyen <= 0) return Double.POSITIVE_INFINITY;
        return kMoyen / k2Moyen;
    }

    // seuil théorique réseau aléatoire "même <k>" (formule du cours) : 1/(<k>+1)
    private static double seuilAleatoireTheorique(double kMoyen) {
        return 1.0 / (kMoyen + 1.0);
    }

    // Immunisation
    private static class ImmunisationInfo {
        boolean[] immunise;
        int nbImmunises;

        // uniquement pour l'immunisation sélective :
        // groupe 0 = les "convainqueurs" (50% tirés au hasard, pas immunisés)
        // groupe 1 = les voisins convaincus (immunisés)
        double degMoyGroupe0;
        double degMoyGroupe1;
        int tailleGroupe0;
        int tailleGroupe1;
    }

    private static ImmunisationInfo construireImmunisation(Graph g, Scenario sc, Random rnd) {
        int N = g.getNodeCount();
        ImmunisationInfo info = new ImmunisationInfo();
        info.immunise = new boolean[N];

        if (sc == Scenario.RIEN) {
            info.nbImmunises = 0;
            return info;
        }

        if (sc == Scenario.IMMUN_ALEA_50) {
            int[] idx = indicesMelanges(N, rnd);
            int cible = N / 2;
            for (int i = 0; i < cible; i++) {
                info.immunise[idx[i]] = true;
            }
            info.nbImmunises = cible;
            return info;
        }

        // IMMUN_SELECTIVE :
        // on tire 50% des nœuds (groupe 0 = "convainqueurs", pas immunisés)
        // chacun essaye de convaincre un voisin non déjà immunisé (pour éviter les doublons)
        int[] idx = indicesMelanges(N, rnd);
        int g0 = N / 2;
        info.tailleGroupe0 = g0;

        boolean[] inG1 = new boolean[N];
        long sumK0 = 0;
        long sumK1 = 0;

        for (int t = 0; t < g0; t++) {
            int ui = idx[t];
            Node u = g.getNode(ui);
            int ku = u.getDegree();
            sumK0 += ku;

            if (ku == 0) continue;

            // choisir un voisin au hasard, mais éviter les voisins déjà immunisés
            Node v = null;

            // on limite le nombre d'essais pour éviter de boucler trop longtemps
            int essaisMax = ku;
            for (int essais = 0; essais < essaisMax; essais++) {
                int pick = rnd.nextInt(ku);

                Iterator<Node> it = u.neighborNodes().iterator();
                Node tmp = null;
                for (int c = 0; c <= pick && it.hasNext(); c++) {
                    tmp = it.next();
                }
                if (tmp == null) continue;

                int viTmp = tmp.getIndex();
                if (!info.immunise[viTmp]) {
                    v = tmp;
                    break;
                }
            }

            if (v == null) continue;

            int vi = v.getIndex();
            info.immunise[vi] = true;

            // pour le degré moyen du groupe 1, on compte chaque convaincu une seule fois
            if (!inG1[vi]) {
                inG1[vi] = true;
                sumK1 += v.getDegree();
            }
        }

        int nbImm = 0;
        int g1 = 0;
        for (int i = 0; i < N; i++) {
            if (info.immunise[i]) nbImm++;
            if (inG1[i]) g1++;
        }
        info.nbImmunises = nbImm;
        info.tailleGroupe1 = g1;

        info.degMoyGroupe0 = (g0 > 0) ? (double) sumK0 / g0 : 0.0;
        info.degMoyGroupe1 = (g1 > 0) ? (double) sumK1 / g1 : 0.0;

        return info;
    }


    private static int[] indicesMelanges(int N, Random rnd) {
        int[] idx = new int[N];
        for (int i = 0; i < N; i++) idx[i] = i;
        for (int i = N - 1; i > 0; i--) {
            int j = rnd.nextInt(i + 1);
            int tmp = idx[i];
            idx[i] = idx[j];
            idx[j] = tmp;
        }
        return idx;
    }

    // Simulation SIS (jour par jour)
    private static double[] simuler(Graph g, boolean[] immunise, Random rnd) {
        int N = g.getNodeCount();

        int nonImmunises = 0;
        for (int i = 0; i < N; i++) if (!immunise[i]) nonImmunises++;

        boolean[] infecte = new boolean[N];

        // patient zéro = un nœud non immunisé
        int p0;
        do { p0 = rnd.nextInt(N); } while (immunise[p0]);
        infecte[p0] = true;

        IntList infectes = new IntList();
        infectes.add(p0);

        boolean[] newly = new boolean[N];
        IntList newList = new IntList();

        double[] frac = new double[NB_JOURS + 1];

        for (int day = 0; day <= NB_JOURS; day++) {
            // fraction d'infectés parmi la population NON immunisée (consigne TP)
            frac[day] = (nonImmunises > 0) ? (double) infectes.size() / nonImmunises : 0.0;
            if (day == NB_JOURS) break;

            IntList nextInfectes = new IntList();

            // infection + guérison
            for (int idx = 0; idx < infectes.size(); idx++) {
                int ui = infectes.get(idx);
                Node u = g.getNode(ui);

                // guérison
                if (rnd.nextDouble() < MU) {
                    infecte[ui] = false;
                    continue;
                }

                // reste infecté
                nextInfectes.add(ui);

                // tente d'infecter les voisins (susceptibles et non immunisés)
                for (Edge e : u) {
                    Node v = e.getOpposite(u);
                    int vi = v.getIndex();
                    if (immunise[vi]) continue;
                    if (infecte[vi]) continue;
                    if (newly[vi]) continue;

                    if (rnd.nextDouble() < BETA) {
                        newly[vi] = true;
                        newList.add(vi);
                    }
                }
            }

            // applique de nouvelles infections
            for (int i = 0; i < newList.size(); i++) {
                int vi = newList.get(i);
                infecte[vi] = true;
                nextInfectes.add(vi);
                newly[vi] = false; // reset pour le jour suivant
            }
            newList.clear();

            infectes = nextInfectes;
        }

        return frac;
    }

    // petite liste d'int
    private static class IntList {
        private int[] a = new int[16];
        private int n = 0;

        void add(int x) {
            if (n == a.length) a = Arrays.copyOf(a, a.length * 2);
            a[n++] = x;
        }

        int get(int i) { return a[i]; }
        int size() { return n; }
        void clear() { n = 0; }
    }

    // création des fichiers .dat
    private static void ecrireCourbe(String fichier, double[] courbe) throws IOException {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(fichier))) {
            bw.write("# jour\tfraction_infectes\n");
            for (int j = 0; j < courbe.length; j++) {
                bw.write(j + "\t" + courbe[j] + "\n");
            }
        }
        System.out.println("Création fichier : " + fichier);
    }

    // moyenne de plusieurs runs (même immunisation, patient zéro aléatoire à chaque run)
    private static double[] moyenneRuns(Graph g, boolean[] immunise, long seedBase, String prefixOut) throws IOException {
        double[] moyenne = new double[NB_JOURS + 1];

        for (int r = 0; r < NB_RUNS; r++) {
            Random rnd = new Random(seedBase + 1000L * r);
            double[] courbe = simuler(g, immunise, rnd);

            for (int j = 0; j <= NB_JOURS; j++) {
            	moyenne[j] += courbe[j];
            }
        }
        for (int j = 0; j <= NB_JOURS; j++) {
        	moyenne[j] /= NB_RUNS;
        }
        return moyenne;
    }

    // exécution complète sur un graphe
    private static void executerPropagation(Graph g, String nomReseau, String outDir, long seed) throws IOException {
        int N = g.getNodeCount();
        double lambda = BETA / MU;

        double[] mk = momentsDegres(g);
        double kMoyen = mk[0];
        double k2Moyen = mk[1];
        double lambdaC = seuilEpidemique(kMoyen, k2Moyen);
        double lambdaCTheo = seuilAleatoireTheorique(kMoyen);

        System.out.println("Reseau : " + nomReseau);
        System.out.println("N = " + N + ", L = " + g.getEdgeCount());
        System.out.println("<k> = " + kMoyen + " ; <k^2> = " + k2Moyen);

        System.out.println("beta = " + BETA + " ; mu = " + MU + " ; lambda (taux de propagation) = beta/mu = " + lambda);
        System.out.println("Seuil épidémique du réseau (lambda_c = <k>/<k^2>) : " + lambdaC);
        System.out.println("Seuil théorique réseau aléatoire (même <k>) : " + lambdaCTheo);
        System.out.println("Epidémie attendue ? (lambda > lambda_c) : " + (lambda > lambdaC));

        // Scénarios
        for (Scenario sc : Scenario.values()) {
            Random rndImm = new Random(seed + sc.ordinal() * 9999L);
            ImmunisationInfo imm = construireImmunisation(g, sc, rndImm);

            System.out.println("\nScénario : " + sc);
            System.out.println("Immunisés = " + imm.nbImmunises + " / " + N);

            if (sc == Scenario.IMMUN_SELECTIVE) {
                System.out.println("Degré moyen groupe 0 (convainqueurs, 50% tirés au hasard) : " + imm.degMoyGroupe0);
                System.out.println("Degré moyen groupe 1 (voisins convaincus, immunisés) : " + imm.degMoyGroupe1);
            }

            // seuil après immunisation (réseau modifié)
            if (sc != Scenario.RIEN) {
                double[] eff = momentsDegresEffectifs(g, imm.immunise);
                double lambdaCEff = seuilEpidemique(eff[0], eff[1]);
                System.out.println("Seuil épidémique après immunisation (réseau modifié) : " + lambdaCEff);
                System.out.println("Epidémie attendue après immunisation ? : " + (lambda > lambdaCEff));
            }

            // simulations multiples + fichiers
            String base = outDir + "/" + nomReseau + "_" + sc.name().toLowerCase();
            double[] moyenne = moyenneRuns(g, imm.immunise, seed + 123456L + sc.ordinal() * 100000L, base);
            ecrireCourbe(base + "_moyenne.dat", moyenne);
        }
    }

    // main
    public static void main(String[] args) throws IOException {
        String outDir = "target/dat";
        new java.io.File(outDir).mkdirs();

        // 1) Réseau réel DBLP
        Graph dblp = lireDBLP();
        int N = dblp.getNodeCount();
        double K = Toolkit.averageDegree(dblp);

        
        // 2) Réseau aléatoire (même N, <k> proche)
        Graph rand = genererGraph("ReseauAleatoire", N, (int) Math.round(K));

        // 3) Réseau BA (même N, <k> proche ~ 2m)
        int m = (int) Math.round(K / 2.0);
        if (m < 1) m = 1;
        Graph ba = genererGraph("ReseauAP", N, m);
        

        // Exécution propagation sur les 3 réseaux
        executerPropagation(dblp, "dblp", outDir, 42L);
        executerPropagation(rand, "aleatoire", outDir, 4242L);
        executerPropagation(ba, "ba", outDir, 424242L);

        System.out.println("\nFichiers .dat générés dans : " + outDir);
    }
}
/*
Pour nb run = 20 :
 
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8
Lecture du fichier DBLP :
Reseau : dblp
N = 317080, L = 1049866
<k> = 6.622089062697111 ; <k^2> = 144.00627601867038
beta = 0.14285714285714285 ; mu = 0.06666666666666667 ; lambda (taux de propagation) = beta/mu = 2.142857142857143
Seuil épidémique du réseau (lambda_c = <k>/<k^2>) : 0.045984725428484516
Seuil théorique réseau aléatoire (même <k>) : 0.1311976272875176
Epidémie attendue ? (lambda > lambda_c) : true

Scénario : RIEN
Immunisés = 0 / 317080
Création fichier : target/dat/dblp_rien_moyenne.dat

Scénario : IMMUN_ALEA_50
Immunisés = 158540 / 317080
Seuil épidémique après immunisation (réseau modifié) : 0.08679070598619239
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/dblp_immun_alea_50_moyenne.dat

Scénario : IMMUN_SELECTIVE
Immunisés = 130567 / 317080
Degré moyen groupe 0 (convainqueurs, 50% tirés au hasard) : 6.601791346032547
Degré moyen groupe 1 (voisins convaincus, immunisés) : 10.501987485352348
Seuil épidémique après immunisation (réseau modifié) : 0.2539906419128171
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/dblp_immun_selective_moyenne.dat
Reseau : aleatoire
N = 317088, L = 1109004
<k> = 6.994928852558281 ; <k^2> = 55.92929407609244
beta = 0.14285714285714285 ; mu = 0.06666666666666667 ; lambda (taux de propagation) = beta/mu = 2.142857142857143
Seuil épidémique du réseau (lambda_c = <k>/<k^2>) : 0.12506735456094978
Seuil théorique réseau aléatoire (même <k>) : 0.12507928693824613
Epidémie attendue ? (lambda > lambda_c) : true

Scénario : RIEN
Immunisés = 0 / 317088
Création fichier : target/dat/aleatoire_rien_moyenne.dat

Scénario : IMMUN_ALEA_50
Immunisés = 158544 / 317088
Seuil épidémique après immunisation (réseau modifié) : 0.22242899079179332
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/aleatoire_immun_alea_50_moyenne.dat

Scénario : IMMUN_SELECTIVE
Immunisés = 154894 / 317088
Degré moyen groupe 0 (convainqueurs, 50% tirés au hasard) : 6.988558381269553
Degré moyen groupe 1 (voisins convaincus, immunisés) : 7.694901029090862
Seuil épidémique après immunisation (réseau modifié) : 0.251242509550492
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/aleatoire_immun_selective_moyenne.dat
Reseau : ba
N = 317082, L = 951240
<k> = 5.999962154899994 ; <k^2> = 157.88398584593259
beta = 0.14285714285714285 ; mu = 0.06666666666666667 ; lambda (taux de propagation) = beta/mu = 2.142857142857143
Seuil épidémique du réseau (lambda_c = <k>/<k^2>) : 0.03800234788064521
Seuil théorique réseau aléatoire (même <k>) : 0.14285791521029825
Epidémie attendue ? (lambda > lambda_c) : true

Scénario : RIEN
Immunisés = 0 / 317082
Création fichier : target/dat/ba_rien_moyenne.dat

Scénario : IMMUN_ALEA_50
Immunisés = 158541 / 317082
Seuil épidémique après immunisation (réseau modifié) : 0.07269251057665028
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/ba_immun_alea_50_moyenne.dat

Scénario : IMMUN_SELECTIVE
Immunisés = 139253 / 317082
Degré moyen groupe 0 (convainqueurs, 50% tirés au hasard) : 5.981752354280596
Degré moyen groupe 1 (voisins convaincus, immunisés) : 8.449649199658175
Seuil épidémique après immunisation (réseau modifié) : 0.3808805999112885
Epidémie attendue après immunisation ? : true
Création fichier : target/dat/ba_immun_selective_moyenne.dat

Fichiers .dat générés dans : target/dat

*/