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é
                }
            }
        }

        // 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);

        // --- Paramètres ---
        int daysToSimulate = 90; // 3 mois
        double infectionProbability = 1.0; // β = 1
        double recoveryProbability = 0.5; // μ = 0.5

        // 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"};

        // --- Scenarios ---
        System.out.println("Scenario 1: No Control");
        results[0] = simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0, false);

        System.out.println("Scenario 2: Random Immunization");
        results[1] = simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0.5, true);

        System.out.println("Scenario 3: Selective Immunization");
        results[2] = simulateAndCaptureResults(graph, infectionProbability, recoveryProbability, daysToSimulate, 0.5, false);

        // Sauvegarde des résultats dans des fichiers
        saveResultsToFiles(results, fileNames);

        System.out.println("Les résultats ont été sauvegardés dans les fichiers pour Gnuplot.");

        // Calculer les degrés moyens des groupes 0 et 1





        epidemicThresholdAnalysis(graph,infectionProbability,recoveryProbability);

        // 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);

    }

    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 pour éviter les modifications concurrentes du graphe
        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);

        // Prédiction dans le réseau réel
        if (tau > cReal) {
            System.out.println("La maladie persiste dans le réseau réel (τ > c_réel).\n");
        } else {
            System.out.println("La maladie disparaît dans le réseau réel (τ < c_réel).\n");
        }

        // Comparaison avec le réseau aléatoire
        if (cReal < cTheoretical) {
            System.out.println("Le réseau réel est plus vulnérable que le réseau aléatoire (c_réel < c_théorique).\n");
        } else {
            System.out.println("Le réseau réel est plus résistant que le réseau aléatoire (c_réel >= c_théorique).\n");
        }
    }




    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 void saveResultsToFiles(double[][] results, String[] fileNames) {
        for (int i = 0; i < results.length; i++) {
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileNames[i]))) {
                for (int day = 0; day < results[i].length; day++) {
                    writer.write((day + 1) + "\t" + results[i][day]);
                    writer.newLine();
                }
            } catch (IOException e) {
                System.err.println("Erreur lors de l'écriture dans le fichier : " + fileNames[i]);
                e.printStackTrace();
            }
        }
    }





    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) {
                // Immunisation aléatoire : immuniser des nœuds directement
                for (int i = 0; i < immunizedCount; i++) {
                    Node node = graph.getNode(random.nextInt(graph.getNodeCount()));
                    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;
    }

    public static void calculateAverageDegrees(Graph graph, Set<Node> immune) {
        // Groupes 0 (non immunisés) et 1 (immunisés)
        Set<Node> group0 = new HashSet<>();
        Set<Node> group1 = immune; // Groupe 1 est directement le set 'immune' (les nœuds immunisés)

        // Ajouter 50 % des nœuds au groupe 0
        Random random = new Random();
        List<Node> nodes = new ArrayList<>();
        for (Node node : graph) {
            nodes.add(node);
        }
        Collections.shuffle(nodes); // Mélanger les nœuds pour une sélection aléatoire

        int targetConvincers = graph.getNodeCount() / 2; // 50 % des nœuds
        for (int i = 0; i < targetConvincers; i++) {
            group0.add(nodes.get(i)); // Ajouter au groupe 0
        }

        // 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.println("Degré moyen du groupe 0 (non immunisé) : " + averageDegreeGroup0);

        // Calcul du degré moyen pour le groupe 1 (immunisé)
        double totalDegreeGroup1 = 0;
        for (Node node : group1) {
            totalDegreeGroup1 += node.getDegree();
        }
        double averageDegreeGroup1 = group1.isEmpty() ? 0 : totalDegreeGroup1 / group1.size();
        System.out.println("Degré moyen du groupe 1 (immunisé) : " + averageDegreeGroup1);
    }


}
