package fr.univ.dblp.generators;

import fr.univ.dblp.analysis.BasicMetrics;
import fr.univ.dblp.analysis.DegreeAnalyzer;
import fr.univ.dblp.export.ResultsPrinter;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Générateur de réseau par mécanisme de copie (Question 7 - Bonus).
 *
 * Algorithme:
 * 1. Un nouveau nœud choisit un nœud aléatoire v
 * 2. Il se connecte à chaque voisin de v avec une probabilité p
 * 3. Il se connecte toujours à v lui-même
 *
 * Ce mécanisme crée naturellement des triangles (clustering élevé) car
 * si le nouveau nœud se connecte à deux voisins de v qui sont eux-mêmes
 * connectés, un triangle est formé.
 *
 * @author Hamadou BA
 * @see <a href="https://www-apps.univ-lehavre.fr/forge/bh243413/tp2-ri-mesures-de-reseaux-interaction.git">Dépôt Git</a>
 */
public class CopyGenerator {

    private double connectionProbability;
    private Random random;

    public CopyGenerator(double p) {
        this.connectionProbability = p;
        this.random = new Random();
    }

    /**
     * Génère un graphe en utilisant le mécanisme de copie.
     *
     * Cette méthode implémente l'algorithme de copie qui favorise la formation
     * de triangles et donc un coefficient de clustering élevé.
     *
     * @param targetNodes Nombre de nœuds cible
     * @param p Probabilité de connexion aux voisins
     * @return Le graphe généré
     */
    public static Graph generateGraph(int targetNodes, double p) {
        ResultsPrinter.printInfo(
            String.format("Génération réseau par copie: N=%,d, p=%.2f", targetNodes, p));

        Graph g = new SingleGraph("Copy-Model");
        g.setStrict(false);
        g.setAutoCreate(true);

        // Initialisation avec quelques nœuds connectés (réseau de départ)
        int seedSize = Math.min(10, targetNodes);
        for (int i = 0; i < seedSize; i++) {
            g.addNode("n" + i);
        }

        // Création des connexions initiales (petite clique)
        for (int i = 0; i < seedSize; i++) {
            for (int j = i + 1; j < Math.min(i + 3, seedSize); j++) {
                g.addEdge("e_init_" + i + "_" + j, "n" + i, "n" + j);
            }
        }

        Random rand = new Random();
        int currentNodeCount = seedSize;

        // Génération des nœuds restants avec le mécanisme de copie
        while (currentNodeCount < targetNodes) {
            // Choix d'un nœud aléatoire v PARMI LES NŒUDS EXISTANTS
            // (avant d'ajouter le nouveau nœud, pour éviter l'explosion)
            int randomIndex = rand.nextInt(currentNodeCount);
            Node v = g.getNode("n" + randomIndex);
            if (v == null) {
                v = g.getNode("n0"); // Solution de repli
            }

            String newNodeId = "n" + currentNodeCount;
            g.addNode(newNodeId);
            Node newNode = g.getNode(newNodeId);

            // Connexion aux voisins de v avec probabilité p
            List<Node> neighborsOfV = new ArrayList<>();
            for (Edge e : v.edges().toArray(Edge[]::new)) {
                Node neighbor = e.getOpposite(v);
                if (neighbor != null && !neighbor.getId().equals(newNodeId)) {
                    neighborsOfV.add(neighbor);
                }
            }

            for (Node neighbor : neighborsOfV) {
                if (rand.nextDouble() < p) {
                    String edgeId = "e_" + currentNodeCount + "_" + neighbor.getId();
                    if (!g.getNode(newNodeId).hasEdgeBetween(neighbor.getId())) {
                        g.addEdge(edgeId, newNodeId, neighbor.getId());
                    }
                }
            }

            // Connexion systématique à v
            String edgeToV = "e_" + currentNodeCount + "_to_" + v.getId();
            if (!g.getNode(newNodeId).hasEdgeBetween(v.getId())) {
                g.addEdge(edgeToV, newNodeId, v.getId());
            }

            currentNodeCount++;

            if (currentNodeCount % 10000 == 0) {
                ResultsPrinter.printInfo(
                    String.format("  Progression: %,d/%,d nœuds générés", currentNodeCount, targetNodes));
            }
        }

        ResultsPrinter.printSuccess(
            String.format("Réseau par copie généré: %,d nœuds, %,d arêtes",
                        g.getNodeCount(), g.getEdgeCount()));

        return g;
    }

    /**
     * Effectue l'analyse complète de la Question 7 (Bonus).
     *
     * Cette méthode teste différentes valeurs du paramètre p, génère les
     * réseaux correspondants, et compare leur clustering avec DBLP et
     * Barabási-Albert pour démontrer l'amélioration apportée par le
     * mécanisme de copie.
     *
     * @param dblpGraph Le graphe DBLP de référence
     * @param baGraph Le graphe Barabási-Albert pour comparaison
     */
    public static void analyze(Graph dblpGraph, Graph baGraph) {
        long startTime = System.currentTimeMillis();
        ResultsPrinter.printHeader("QUESTION 7 (BONUS): Générateur par copie");

        ResultsPrinter.printInfo("Objectif: Améliorer le coefficient de clustering par rapport à Barabási-Albert");

        // Test de différentes valeurs de p
        double[] pValues = {0.3, 0.5, 0.7};
        int generatedSize = Math.min(dblpGraph.getNodeCount(), 50000);

        ResultsPrinter.printSubHeader("Tests avec différentes valeurs de p");

        Graph bestGraph = null;
        double bestP = 0.0;
        double bestClustering = 0.0;

        for (double p : pValues) {
            ResultsPrinter.printInfo(String.format("\n=== Test avec p = %.1f ===", p));

            Graph copyGraph = generateGraph(generatedSize, p);

            double clustering = BasicMetrics.getClusteringCoefficient(copyGraph);
            double avgDegree = BasicMetrics.getAverageDegree(copyGraph);

            ResultsPrinter.printMetric("Degré moyen", avgDegree);
            ResultsPrinter.printMetric("Coefficient de clustering", clustering);

            if (clustering > bestClustering) {
                bestClustering = clustering;
                bestP = p;
                bestGraph = copyGraph;
            }
        }

        // Analyse détaillée de la meilleure configuration
        ResultsPrinter.printSubHeader("Meilleure configuration: p = " + bestP);

        BasicMetrics.analyze(bestGraph, "Copie (p=" + bestP + ")");
        DegreeAnalyzer.analyze(bestGraph, "Copie", "copy");

        // Tableau de comparaison
        ResultsPrinter.printHeader("COMPARAISON CLUSTERING");

        double dblpClustering = BasicMetrics.getClusteringCoefficient(dblpGraph);
        double baClustering = BasicMetrics.getClusteringCoefficient(baGraph);

        ResultsPrinter.printComparisonHeader("DBLP", "Barabási-Albert", "Copie (p=" + bestP + ")");
        ResultsPrinter.printComparisonRow("Clustering",
            dblpClustering,
            baClustering,
            bestClustering);

        ResultsPrinter.printSeparator();

        double improvementVsBA = bestClustering / baClustering;
        ResultsPrinter.printMetric("Amélioration vs BA", improvementVsBA + "x");

        if (bestClustering > baClustering * 10) {
            ResultsPrinter.printSuccess(
                "Le générateur par copie reproduit BEAUCOUP MIEUX le clustering!");
        } else if (bestClustering > baClustering) {
            ResultsPrinter.printSuccess(
                "Le générateur par copie améliore le clustering!");
        }

        ResultsPrinter.printInfo("\nCONCLUSIONS:");
        ResultsPrinter.printInfo("  - La méthode de copie crée naturellement des triangles (clustering)");
        ResultsPrinter.printInfo("  - Compromis à trouver avec la valeur de p");
        ResultsPrinter.printInfo("  - Meilleure modélisation que BA pour les réseaux sociaux!");

        ResultsPrinter.printElapsedTime(startTime);
    }
}
