package org.example;

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 java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import java.util.*;

public class Propagation {

    public static void main(String[] args) throws Exception {
        String filePath = "C:/Users/celia/IdeaProjects/TP_RI/com-dblp.ungraph.txt/com-dblp.ungraph.txt";
        Graph graph = new SingleGraph("DBLP Collaboration Network");

        // --- Chargement des données ---
        FileSourceEdge fileSource = new FileSourceEdge();
        fileSource.addSink(graph);
        try {
            fileSource.readAll(filePath);
        } finally {
            fileSource.removeSink(graph);
        }
// Appliquer une immunisation aléatoire (immuniser 50 % des nœuds)
        Set<Node> immune = new HashSet<>();
        Random random = new Random();
        int immuneCount = (int) (0.5 * graph.getNodeCount()); // Immuniser 50 % des nœuds
        List<Node> nodes = new ArrayList<>(graph.nodes().toList());
        Collections.shuffle(nodes, random);
        for (int i = 0; i < immuneCount; i++) {
            Node node = nodes.get(i);
            node.setAttribute("immune", true); // Marquer comme immunisé
            immune.add(node);
        }

        // Groupes
        Set<Node> group0 = new HashSet<>(); // Groupe 0 : 50 % des nœuds non immunisés
        Set<Node> group1 = new HashSet<>(); // Groupe 1 : Un voisin immunisé de chaque nœud du Groupe 0

        // Sélectionner 50 % des nœuds non immunisés pour le Groupe 0
        List<Node> nonImmuneNodes = new ArrayList<>();
        for (Node node : graph) {
            if (!immune.contains(node)) { // Nœuds non immunisés
                nonImmuneNodes.add(node);
            }
        }
        Collections.shuffle(nonImmuneNodes, random); // Mélanger les nœuds non immunisés
        int targetCount = (int) (0.5 * nonImmuneNodes.size()); // 50 % des nœuds non immunisés
        for (int i = 0; i < targetCount; i++) {
            group0.add(nonImmuneNodes.get(i)); // Ajouter au groupe 0
        }

        // Pour chaque nœud du Groupe 0, ajouter un voisin immunisé au Groupe 1
        for (Node node : group0) {
            List<Node> neighbors = node.neighborNodes().toList();
            for (Node neighbor : neighbors) {
                if (immune.contains(neighbor)) { // Vérifier si le voisin est immunisé
                    group1.add(neighbor); // Ajouter un seul voisin immunisé au groupe 1
                    break; // Sortir de la boucle après avoir trouvé un voisin immunisé
                }
            }
        }
        // --- Paramètres ---
        int daysToSimulate = 90; // 3 mois
        double infectionProbability = 1.0; // β = 1
        double recoveryProbability = 0.5; // μ = 0.5


        // Nombre d'itérations pour chaque scénario
        int iterations = 10;

// Tableau pour stocker les résultats des scénarios
        double[][] results = new double[3][daysToSimulate];
        String[] fileNames = {"scenario1_no_control.dat", "scenario2_random_immunization.dat", "scenario3_selective_immunization.dat"};

// --- Scénarios avec itérations ---
        for (int scenario = 0; scenario < 3; scenario++) {
            double[] averageFractions = new double[daysToSimulate];
            Arrays.fill(averageFractions, 0); // Initialiser à zéro pour chaque scénario

            for (int iteration = 0; iteration < iterations; iteration++) {
                // Cloner le graphe pour garantir une nouvelle simulation à chaque itération
                Graph graphClone = cloneGraph(graph);

                // Définir les paramètres spécifiques au scénario
                double immunizationFraction = 0;
                boolean randomImmunization = false;

                if (scenario == 1) { // Immunisation aléatoire
                    immunizationFraction = 0.5;
                    randomImmunization = true;
                } else if (scenario == 2) { // Immunisation sélective
                    immunizationFraction = 0.5;
                    randomImmunization = false;
                }

                // Exécuter la simulation et capturer les résultats
                double[] fractions = simulateAndCaptureResults(graphClone, infectionProbability, recoveryProbability, daysToSimulate, immunizationFraction, randomImmunization);

                // Ajouter les résultats de cette itération à la moyenne
                for (int day = 0; day < daysToSimulate; day++) {
                    averageFractions[day] += fractions[day];
                }
            }

            // Diviser par le nombre d'itérations pour obtenir la moyenne
            for (int day = 0; day < daysToSimulate; day++) {
                averageFractions[day] /= iterations;
            }

            // Stocker les résultats du scénario
            results[scenario] = averageFractions;

            // Enregistrer les résultats dans un fichier
            writeResultsToFile(fileNames[scenario], averageFractions);
        }





        epidemicThresholdAnalysis(graph,infectionProbability,recoveryProbability);


        // Calcul du degré moyen pour le Groupe 0 (non immunisé)
        double totalDegreeGroup0 = 0;
        for (Node node : group0) {
            totalDegreeGroup0 += node.getDegree();
        }
        double averageDegreeGroup0 = group0.isEmpty() ? 0 : totalDegreeGroup0 / group0.size();
        System.out.printf("Degré moyen (Groupe 0 - Non immunisé) : %.9f%n", averageDegreeGroup0);

        // Calcul du degré moyen pour le Groupe 1 (voisins immunisés)
        double totalDegreeGroup1 = 0;
        for (Node node : group1) {
            totalDegreeGroup1 += node.getDegree();
        }
        double averageDegreeGroup1 = group1.isEmpty() ? 0 : totalDegreeGroup1 / group1.size();
        System.out.printf("Degré moyen (Groupe 1 - Voisins immunisés) : %.9f%n", averageDegreeGroup1);


        // Réseau modifié après immunisation aléatoire
        System.out.println("Analyse après immunisation aléatoire :");
        Graph randomImmunizedGraph = cloneGraph(graph);
        applyRandomImmunization(randomImmunizedGraph, 0.5); // 50% des nœuds immunisés
        epidemicThresholdAnalysis(randomImmunizedGraph, infectionProbability, recoveryProbability);

        // Réseau modifié après immunisation sélective
        System.out.println("Analyse après immunisation sélective :");
        Graph selectiveImmunizedGraph = cloneGraph(graph);
        applySelectiveImmunization(selectiveImmunizedGraph, 0.5); // 50% des voisins des hubs immunisés
        epidemicThresholdAnalysis(selectiveImmunizedGraph, infectionProbability, recoveryProbability);

        Graph barabasiGraph = DBLPNetworkAnalysis.generateBarabasiAlbertGraph(graph); // Exemple : 3 connexions par nœud


        System.out.println("Simulation sur réseau Barabási-Albert :");
        runSimulations(barabasiGraph);


    }
    // --- Méthode pour écrire les résultats dans un fichier ---
    private static void writeResultsToFile(String fileName, double[] data) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
            for (int day = 0; day < data.length; day++) {
                writer.write(day + " " + data[day]);
                writer.newLine();
            }
        } catch (IOException e) {
            System.err.println("Erreur lors de l'écriture du fichier : " + e.getMessage());
        }
    }


    private static void runSimulations(Graph graph) {
        int daysToSimulate = 90; // 3 mois
        double infectionProbability = 1.0; // β = 1
        double recoveryProbability = 0.5; // μ = 0.5

        // Scénario 1 : Aucune immunisation
        System.out.println("Scénario 1 : Aucune immunisation");
        simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0, false);

        // Scénario 2 : Immunisation aléatoire
        System.out.println("Scénario 2 : Immunisation aléatoire");
        applyRandomImmunization(graph, 0.5); // Immunisation aléatoire de 50 %
        simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0, true);

        // Scénario 3 : Immunisation sélective
        System.out.println("Scénario 3 : Immunisation sélective");
        applySelectiveImmunization(graph, 0.5); // Immunisation sélective de 50 %
        simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0, false);
    }

    private static Graph cloneGraph(Graph original) {
        Graph clone = new SingleGraph("Clone");

        // Ajouter tous les nœuds au clone
        original.nodes().toList().forEach(node -> clone.addNode(node.getId()));

        // Ajouter toutes les arêtes au clone
        original.edges().toList().forEach(edge -> {
            String sourceId = edge.getSourceNode().getId();
            String targetId = edge.getTargetNode().getId();
            String edgeId = sourceId + "-" + targetId;
            if (clone.getEdge(edgeId) == null) {
                clone.addEdge(edgeId, sourceId, targetId);
            }
        });

        return clone;
    }

    private static void applyRandomImmunization(Graph graph, double fraction) {
        Random random = new Random();
        int immunizedCount = (int) (fraction * graph.getNodeCount());
        for (int i = 0; i < immunizedCount; i++) {
            Node node = graph.getNode(random.nextInt(graph.getNodeCount()));
            if (node != null) {
                graph.removeNode(node);
            }
        }
    }
    private static void applySelectiveImmunization(Graph graph, double fraction) {
        Random random = new Random();

        // Créer une liste des nœuds
        var nodes = graph.nodes().toList();

        for (Node node : nodes) {
            if (node != null && random.nextDouble() < fraction) {
                // Parcourir les voisins et les supprimer
                var neighbors = node.neighborNodes().toList();
                for (Node neighbor : neighbors) {
                    if (graph.getNode(neighbor.getId()) != null) { // Vérifier que le voisin existe toujours
                        graph.removeNode(neighbor.getId());
                    }
                }
            }
        }
    }



    private static void epidemicThresholdAnalysis(Graph graph, double infectionRate, double recoveryRate) {
        double avgDegree = Toolkit.averageDegree(graph);   // Utilisation de Toolkit pour le degré moyen
        double avgDegreeSquared = calculateSquaredAverageDegree(graph); // Calcul direct du carré moyen des degrés

        // Calcul des seuils
        double tau = infectionRate / recoveryRate;         // Taux de propagation
        double cReal = avgDegree / avgDegreeSquared;       // Seuil épidémique réel
        double cTheoretical = 1.0 / (avgDegree + 1);       // Seuil théorique pour un réseau aléatoire avec normalisation

        // Affichage des résultats
        System.out.println("Taux de propagation (τ) : " + tau);
        System.out.println("Seuil épidémique réel (c_réel) : " + cReal);
        System.out.println("Seuil épidémique théorique (c_théorique) : " + cTheoretical);

    }




    private static double calculateSquaredAverageDegree(Graph graph) {
        double totalDegreeSquared = 0;
        for (Node node : graph) {
            totalDegreeSquared += Math.pow(node.getDegree(), 2); // Somme des carrés des degrés
        }
        return totalDegreeSquared / graph.getNodeCount(); // Moyenne des carrés des degrés
    }



    private static double[] simulateAndCaptureResults(Graph graph, double infectionRate, double recoveryRate, int days, double immunizationFraction, boolean randomImmunization) {
        double[] infectedFractions = new double[days];
        Random random = new Random();

        // initialisation
        for (Node node : graph) {
            node.setAttribute("state", "susceptible");
            node.removeAttribute("immune");
        }

        // Immunization
        if (immunizationFraction > 0) {
            int immunizedCount = (int) (immunizationFraction * graph.getNodeCount());
            if (randomImmunization) {
                // Récupérer tous les nœuds dans une liste
                List<Node> allNodes = new ArrayList<>(graph.nodes().toList());

                // Mélanger la liste pour une sélection aléatoire unique
                Collections.shuffle(allNodes, random);

                // Immuniser les premiers `immunizedCount` nœuds
                for (int i = 0; i < immunizedCount; i++) {
                    Node node = allNodes.get(i);
                    if (node != null) {
                        node.setAttribute("immune", true);
                    }
                }
            } else {
                // Immunisation sélective : immuniser un voisin pour 50% des nœuds existants
                List<Node> nodes = new ArrayList<>( graph.nodes().toList());

                // Calculer le nombre de nœuds à immuniser (50% des nœuds existants)
                int nodesToImmunize = (int) (immunizationFraction * nodes.size());

                // Mélanger les nœuds pour garantir une sélection aléatoire
                Collections.shuffle(nodes, random);

                // Sélectionner les premiers `nodesToImmunize` nœuds et immuniser un voisin pour chacun
                for (int i = 0; i < nodesToImmunize; i++) {
                    Node node = nodes.get(i);

                    // Vérifier que le nœud a des voisins
                    if (node.getDegree() > 0) {
                        // Prendre un voisin aléatoire
                        List<Node> neighbors = node.neighborNodes().toList();
                        Node neighbor = neighbors.get(random.nextInt(neighbors.size()));

                        // Immuniser ce voisin
                        neighbor.setAttribute("immune", true);
                    }
                }

            }

        }
        // Infect patient zero
        Node patientZero = graph.getNode(new Random().nextInt(graph.getNodeCount()));
        patientZero.setAttribute("state", "infected");

        // Simulation
        for (int day = 0; day < days; day++) {
            int infectedCount = 0;
            int totalNonImmune = 0;

            for (Node node : graph) {
                if (!node.hasAttribute("immune")) {
                    totalNonImmune++;
                    if (node.getAttribute("state").equals("infected")) {
                        infectedCount++;
                        for (Node neighbor : node.neighborNodes().toList()) {
                            if (!neighbor.hasAttribute("immune") && neighbor.getAttribute("state").equals("susceptible") && random.nextDouble() < infectionRate) {
                                neighbor.setAttribute("nextState", "infected");
                            }
                        }
                        if (random.nextDouble() < recoveryRate) {
                            node.setAttribute("nextState", "susceptible");
                        }
                    }
                }
            }
            // mise à jour de l'état des noeuds
            for (Node node : graph) {
                if (node.hasAttribute("nextState")) {
                    node.setAttribute("state", node.getAttribute("nextState"));
                    node.removeAttribute("nextState");
                }
            }

            infectedFractions[day] = (double) infectedCount / totalNonImmune;
        }

        return infectedFractions;
    }

}
