package tpri;

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

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

/**
 * Simulation de propagation SIS sur le réseau DBLP.
 */
public class SimulerPropagation {

    // Paramètres SIS (par jour)
    private static final double BETA = 0.14;      
    private static final double MU   = 0.07;    
    private static final int    TMAX = 90;             
    private static final int    REPEATS = 10; //Nmbr de simulations 

    public static void main(String[] args) throws IOException {
        String fichier = "com-dblp.ungraph.txt"; 
        Graph graphe = chargerGraphe(fichier);

        System.out.println("Graphe chargé : " + graphe.getNodeCount() + " noeuds.");

        // Tableaux pour stocker les moyennes
        double[] moyScenario1 = new double[TMAX];
        double[] moyScenario2 = new double[TMAX];
        double[] moyScenario3 = new double[TMAX];

        for (int r = 0; r < REPEATS; r++) {
            System.out.println("Répétition " + (r + 1) + "/" + REPEATS);

            // Scénario 1 : Pas d'immunisation
            SimulationResult res1 = lancerSimulation(graphe, Scenario.NONE);
            // Scénario 2 : Immunisation Aléatoire
            SimulationResult res2 = lancerSimulation(graphe, Scenario.RANDOM_IMMUNIZATION);
            // Scénario 3 : Immunisation Sélective
            SimulationResult res3 = lancerSimulation(graphe, Scenario.SELECTIVE_IMMUNIZATION);

            for (int t = 0; t < TMAX; t++) {
                moyScenario1[t] += res1.fractionInfected[t];
                moyScenario2[t] += res2.fractionInfected[t];
                moyScenario3[t] += res3.fractionInfected[t];
            }
        }

        // Export des résultats moyens
        ecrireFichier(moyScenario1, moyScenario2, moyScenario3);
    }

    private static SimulationResult lancerSimulation(Graph baseGraph, Scenario scenario) {
        int n = baseGraph.getNodeCount();
        List<Node> listeNoeuds = new ArrayList<>(n);
        baseGraph.nodes().forEach(listeNoeuds::add);

        // États : 0 = susceptible, 1 = infecté, 2 = immunisé
        byte[] state = new byte[n];
        Map<Node, Integer> mappingIndex = new HashMap<>(n);

        for (int i = 0; i < n; i++) {
            mappingIndex.put(listeNoeuds.get(i), i);
            state[i] = 0; 
        }

        // Application de l'immunisation
        if (scenario == Scenario.RANDOM_IMMUNIZATION) {
            appliquerImmunoAleatoire(state, 0.5);
        } else if (scenario == Scenario.SELECTIVE_IMMUNIZATION) {
            appliquerImmunoSelective(listeNoeuds, state, 0.5);
        }

        // Patient zéro
        Random rng = new Random();
        int p0;
        do {
            p0 = rng.nextInt(n);
        } while (state[p0] != 0);
        state[p0] = 1;

        double[] fractionInfectes = new double[TMAX];

        for (int t = 0; t < TMAX; t++) {
            boolean[] infecterDemain = new boolean[n];
            boolean[] guerirDemain = new boolean[n];

            for (int i = 0; i < n; i++) {
                if (state[i] == 1) {
                    if (rng.nextDouble() < MU) guerirDemain[i] = true;

                    for (Node v : listeNoeuds.get(i).neighborNodes().toList()) {
                        int j = mappingIndex.get(v);
                        if (state[j] == 0 && rng.nextDouble() < BETA) {
                            infecterDemain[j] = true;
                        }
                    }
                }
            }

            int infectes = 0;
            int aRisque = 0;

            for (int i = 0; i < n; i++) {
                if (state[i] == 2) continue; // On ignore les immunisés

                aRisque++;
                if (state[i] == 1) {
                    if (guerirDemain[i]) state[i] = 0;
                } else if (state[i] == 0) {
                    if (infecterDemain[i]) state[i] = 1;
                }

                if (state[i] == 1) infectes++;
            }
            fractionInfectes[t] = (double) infectes / aRisque;
        }

        SimulationResult res = new SimulationResult();
        res.fractionInfected = fractionInfectes;
        return res;
    }

    private static void appliquerImmunoAleatoire(byte[] state, double g) {
        List<Integer> indices = new ArrayList<>(state.length);
        for (int i = 0; i < state.length; i++) indices.add(i);
        Collections.shuffle(indices);

        int cible = (int) Math.round(g * state.length);
        for (int k = 0; k < cible; k++) state[indices.get(k)] = 2;
    }

    private static void appliquerImmunoSelective(List<Node> nodes, byte[] state, double g) {
        int n = nodes.size();
        int nbTirages = (int) Math.round(g * n);
        Random rng = new Random();
        Set<Integer> vax = new HashSet<>();

        for (int k = 0; k < nbTirages; k++) {
            Node u = nodes.get(rng.nextInt(n));
            List<Node> voisins = u.neighborNodes().toList();
            if (!voisins.isEmpty()) {
                Node v = voisins.get(rng.nextInt(voisins.size()));
                vax.add(nodes.indexOf(v));
            }
        }
        for (int idx : vax) state[idx] = 2;
    }

    private static Graph chargerGraphe(String chemin) throws IOException {
        Graph g = new SingleGraph("DBLP");
        FileSourceEdge fs = new FileSourceEdge();
        fs.addSink(g);
        fs.readAll(chemin);
        return g;
    }

    private static void ecrireFichier(double[] s1, double[] s2, double[] s3) throws IOException {
        try (PrintWriter pw = new PrintWriter(new FileWriter("virus_dblp_scenarios.dat"))) {
            pw.println("# t  Scenario1  Scenario2  Scenario3");
            for (int t = 0; t < TMAX; t++) {
                pw.printf(Locale.US, "%d %.6f %.6f %.6f%n", 
                    t, s1[t]/REPEATS, s2[t]/REPEATS, s3[t]/REPEATS);
            }
        }
        System.out.println("Fichier de données généré.");
    }

    private enum Scenario { NONE, RANDOM_IMMUNIZATION, SELECTIVE_IMMUNIZATION }
    private static class SimulationResult { double[] fractionInfected; }
}