package fr.univ.dblp.analysis;

import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;

import java.util.Set;

/**
 * Analyse épidémiologique des réseaux.
 * Calcule le seuil épidémique et autres métriques liées à la propagation virale.
 */
public class EpidemicAnalyzer {

    /**
     * Calcule le seuil épidémique du réseau.
     * Le seuil épidémique τ est donné par : τ = <k²>/<k> - 1
     * où <k> est le degré moyen et <k²> est le second moment de la distribution des degrés.
     *
     * Pour qu'une épidémie se propage, le taux de reproduction R0 = β/γ doit être > τ
     *
     * @param graph Le graphe à analyser
     * @return Le seuil épidémique
     */
    public static double calculateEpidemicThreshold(Graph graph) {
        int nodeCount = graph.getNodeCount();
        if (nodeCount == 0) {
            return 0.0;
        }

        // Calculer <k> (degré moyen)
        long sumDegrees = 0;
        for (Node node : graph) {
            sumDegrees += node.getDegree();
        }
        double avgDegree = (double) sumDegrees / nodeCount;

        // Calculer <k²> (second moment)
        long sumSquaredDegrees = 0;
        for (Node node : graph) {
            int degree = node.getDegree();
            sumSquaredDegrees += (long) degree * degree;
        }
        double secondMoment = (double) sumSquaredDegrees / nodeCount;

        // τ = <k²>/<k> - 1
        return (secondMoment / avgDegree) - 1.0;
    }

    /**
     * Calcule le seuil épidémique théorique pour un réseau aléatoire (Erdős-Rényi).
     * Pour un réseau aléatoire : τ = <k>
     *
     * @param avgDegree Degré moyen du réseau
     * @return Le seuil épidémique théorique
     */
    public static double theoreticalRandomThreshold(double avgDegree) {
        return avgDegree;
    }

    /**
     * Calcule le taux de reproduction de base R0 = β/γ
     *
     * @param transmissionRate Taux de transmission β (probabilité de transmission par contact)
     * @param recoveryRate Taux de récupération γ (probabilité de récupération par unité de temps)
     * @return Le taux de reproduction R0
     */
    public static double calculateR0(double transmissionRate, double recoveryRate) {
        return transmissionRate / recoveryRate;
    }

    /**
     * Vérifie si les conditions sont favorables à une épidémie.
     * L'épidémie se propage si R0 > τ
     *
     * @param r0 Taux de reproduction de base
     * @param threshold Seuil épidémique du réseau
     * @return true si l'épidémie peut se propager
     */
    public static boolean isEpidemicPossible(double r0, double threshold) {
        return r0 > threshold;
    }

    /**
     * Calcule le seuil épidémique d'un réseau après immunisation.
     * Les nœuds immunisés sont considérés comme supprimés du réseau.
     *
     * @param graph Le graphe
     * @param immuneNodeIds IDs des nœuds immunisés
     * @return Le seuil épidémique du réseau modifié
     */
    public static double calculateThresholdAfterImmunization(Graph graph, Set<String> immuneNodeIds) {
        if (immuneNodeIds.isEmpty()) {
            return calculateEpidemicThreshold(graph);
        }

        // Calculer les statistiques en excluant les nœuds immunisés
        int activeNodeCount = 0;
        long sumDegrees = 0;
        long sumSquaredDegrees = 0;

        for (Node node : graph) {
            if (!immuneNodeIds.contains(node.getId())) {
                // Compter seulement les arêtes vers des nœuds non immunisés
                final int[] effectiveDegree = {0}; // Utiliser un tableau pour contourner la restriction "effectively final"
                node.edges().forEach(edge -> {
                    Node neighbor = edge.getOpposite(node);
                    if (!immuneNodeIds.contains(neighbor.getId())) {
                        effectiveDegree[0]++;
                    }
                });

                activeNodeCount++;
                sumDegrees += effectiveDegree[0];
                sumSquaredDegrees += (long) effectiveDegree[0] * effectiveDegree[0];
            }
        }

        if (activeNodeCount == 0) {
            return Double.POSITIVE_INFINITY; // Pas d'épidémie possible
        }

        double avgDegree = (double) sumDegrees / activeNodeCount;
        double secondMoment = (double) sumSquaredDegrees / activeNodeCount;

        if (avgDegree == 0) {
            return Double.POSITIVE_INFINITY;
        }

        return (secondMoment / avgDegree) - 1.0;
    }

    /**
     * Calcule la variance de la distribution des degrés.
     * Utile pour comprendre l'hétérogénéité du réseau.
     *
     * @param graph Le graphe
     * @return La variance des degrés
     */
    public static double getDegreeVariance(Graph graph) {
        int nodeCount = graph.getNodeCount();
        if (nodeCount == 0) {
            return 0.0;
        }

        // Degré moyen
        long sumDegrees = 0;
        for (Node node : graph) {
            sumDegrees += node.getDegree();
        }
        double avgDegree = (double) sumDegrees / nodeCount;

        // Variance
        double sumSquaredDiff = 0.0;
        for (Node node : graph) {
            double diff = node.getDegree() - avgDegree;
            sumSquaredDiff += diff * diff;
        }

        return sumSquaredDiff / nodeCount;
    }
}
