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.Random;

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

        // --- Paramètres ---
        int daysToSimulate = 90; // 3 months
        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
        calculateAverageDegreeSelectiveImmunization(graph);
        epidemicThresholdAnalysis(graph,infectionProbability,recoveryProbability);
    }



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

        // Reset states
        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
                for (Node node : graph) {
                    if (random.nextDouble() < immunizationFraction && node.getDegree() > 0) {
                        Node neighbor = node.neighborNodes().toList().get(0); // Prendre un 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");
                        }
                    }
                }
            }

            for (Node node : graph) {
                if (node.hasAttribute("nextState")) {
                    node.setAttribute("state", node.getAttribute("nextState"));
                    node.removeAttribute("nextState");
                }
            }

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

        return infectedFractions;
    }




    private static void calculateAverageDegreeSelectiveImmunization(Graph graph) {
        double totalDegreeGroup0 = 0; // Somme des degrés des nœuds du groupe 0
        double totalDegreeGroup1 = 0; // Somme des degrés des nœuds du groupe 1
        int countGroup0 = 0; // Nombre de nœuds dans le groupe 0
        int countGroup1 = 0; // Nombre de nœuds dans le groupe 1

        // Parcourir tous les nœuds pour identifier les groupes
        for (Node node : graph) {
            if (node.hasAttribute("selected")) { // Groupe 0 : nœuds choisis aléatoirement
                totalDegreeGroup0 += node.getDegree();
                countGroup0++;

                // Groupe 1 : voisins immunisés de ces nœuds
                for (Node neighbor : node.neighborNodes().toList()) {
                    if (neighbor.hasAttribute("immune")) {
                        totalDegreeGroup1 += neighbor.getDegree();
                        countGroup1++;
                    }
                }
            }
        }

        // Calcul des degrés moyens
        double avgDegreeGroup0 = countGroup0 > 0 ? totalDegreeGroup0 / countGroup0 : 0;
        double avgDegreeGroup1 = countGroup1 > 0 ? totalDegreeGroup1 / countGroup1 : 0;

        // Afficher les résultats
        System.out.printf("Degré moyen (Groupe 0 - Nœuds sélectionnés aléatoirement) : %.9f%n", avgDegreeGroup0);
        System.out.printf("Degré moyen (Groupe 1 - Voisins immunisés) : %.9f%n", avgDegreeGroup1);
    }
}
