package fr.univ.dblp.simulation;

import fr.univ.dblp.utils.RandomSampler;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;

import java.util.*;

/**
 * Stratégies d'immunisation pour contrôler la propagation virale.
 */
public class ImmunizationStrategy {

    /**
     * Aucune immunisation - tous les nœuds sont susceptibles
     */
    public static Set<String> noImmunization(Graph graph) {
        return new HashSet<>();
    }

    /**
     * Immunisation aléatoire : sélectionne une fraction aléatoire de nœuds
     *
     * @param graph Le graphe
     * @param fraction Fraction de nœuds à immuniser (entre 0 et 1)
     * @return Ensemble des IDs de nœuds immunisés
     */
    public static Set<String> randomImmunization(Graph graph, double fraction) {
        int numToImmunize = (int) Math.round(graph.getNodeCount() * fraction);
        List<Node> immunizedNodes = RandomSampler.sampleNodes(graph, numToImmunize);

        Set<String> immuneIds = new HashSet<>();
        for (Node node : immunizedNodes) {
            immuneIds.add(node.getId());
        }

        return immuneIds;
    }

    /**
     * Immunisation sélective (acquaintance immunization) :
     * Chaque nœud sélectionné aléatoirement convainc un de ses voisins de s'immuniser.
     * Cette stratégie cible préférentiellement les nœuds à haut degré.
     *
     * @param graph Le graphe
     * @param fraction Fraction de nœuds qui participent à la campagne (chacun immunise un voisin)
     * @return Ensemble des IDs de nœuds immunisés
     */
    public static Set<String> acquaintanceImmunization(Graph graph, double fraction) {
        int numParticipants = (int) Math.round(graph.getNodeCount() * fraction);
        List<Node> participants = RandomSampler.sampleNodes(graph, numParticipants);

        Set<String> immuneIds = new HashSet<>();
        Random random = new Random();

        for (Node participant : participants) {
            // Obtenir un voisin aléatoire
            List<Node> neighbors = new ArrayList<>();
            participant.edges().forEach(edge -> {
                Node neighbor = edge.getOpposite(participant);
                neighbors.add(neighbor);
            });

            if (!neighbors.isEmpty()) {
                Node randomNeighbor = neighbors.get(random.nextInt(neighbors.size()));
                immuneIds.add(randomNeighbor.getId());
            }
        }

        return immuneIds;
    }

    /**
     * Immunisation ciblée des hubs : immunise les nœuds avec le plus haut degré
     *
     * @param graph Le graphe
     * @param fraction Fraction de nœuds à immuniser
     * @return Ensemble des IDs de nœuds immunisés
     */
    public static Set<String> hubImmunization(Graph graph, double fraction) {
        int numToImmunize = (int) Math.round(graph.getNodeCount() * fraction);

        // Trier les nœuds par degré décroissant
        List<Node> sortedNodes = new ArrayList<>();
        graph.nodes().forEach(sortedNodes::add);
        sortedNodes.sort((n1, n2) -> Integer.compare(n2.getDegree(), n1.getDegree()));

        Set<String> immuneIds = new HashSet<>();
        for (int i = 0; i < Math.min(numToImmunize, sortedNodes.size()); i++) {
            immuneIds.add(sortedNodes.get(i).getId());
        }

        return immuneIds;
    }

    /**
     * Calcule le degré moyen d'un ensemble de nœuds
     *
     * @param graph Le graphe
     * @param nodeIds IDs des nœuds à analyser
     * @return Degré moyen
     */
    public static double getAverageDegree(Graph graph, Set<String> nodeIds) {
        if (nodeIds.isEmpty()) {
            return 0.0;
        }

        int totalDegree = 0;
        for (String nodeId : nodeIds) {
            Node node = graph.getNode(nodeId);
            if (node != null) {
                totalDegree += node.getDegree();
            }
        }

        return (double) totalDegree / nodeIds.size();
    }

    /**
     * Calcule le degré moyen des nœuds NON immunisés
     *
     * @param graph Le graphe
     * @param immuneIds IDs des nœuds immunisés
     * @return Degré moyen des non-immunisés
     */
    public static double getAverageDegreeNonImmune(Graph graph, Set<String> immuneIds) {
        Set<String> nonImmuneIds = new HashSet<>();
        graph.nodes().forEach(node -> {
            if (!immuneIds.contains(node.getId())) {
                nonImmuneIds.add(node.getId());
            }
        });

        return getAverageDegree(graph, nonImmuneIds);
    }
}
