package fr.univ.dblp.export;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Map;

/**
 * Classe utilitaire pour l'export de données vers des fichiers (pour gnuplot).
 *
 * Cette classe fournit des méthodes pour exporter différents types de données
 * (distributions de degrés, distributions de distances, etc.) dans des formats
 * compatibles avec gnuplot pour la visualisation.
 *
 * @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 DataExporter {

    /**
     * Exporte des données à deux colonnes vers un fichier.
     *
     * Cette méthode générique permet d'exporter n'importe quelle paire de données
     * (x, y) dans un format lisible par gnuplot.
     *
     * @param x Valeurs de la première colonne
     * @param y Valeurs de la deuxième colonne
     * @param filePath Chemin du fichier de sortie
     * @param header En-tête optionnel (commentaire)
     */
    public static void exportTwoColumns(double[] x, double[] y, String filePath, String header) {
        try {
            createDirectoryIfNeeded(filePath);

            try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
                if (header != null && !header.isEmpty()) {
                    writer.println("# " + header);
                }

                for (int i = 0; i < Math.min(x.length, y.length); i++) {
                    writer.printf(Locale.US, "%.10f  %.10f%n", x[i], y[i]);
                }
            }

            ResultsPrinter.printSuccess("Données exportées vers: " + filePath);
        } catch (IOException e) {
            ResultsPrinter.printError("Erreur lors de l'export vers " + filePath + ": " + e.getMessage());
        }
    }

    /**
     * Exporte la distribution des degrés vers un fichier pour gnuplot.
     *
     * Le fichier contient deux colonnes: le degré k et la probabilité p_k
     * qu'un nœud ait ce degré.
     *
     * @param degreeDistribution Tableau où l'indice = degré, valeur = nombre de nœuds
     * @param nodeCount Nombre total de nœuds
     * @param filePath Chemin du fichier de sortie
     */
    public static void exportDegreeDistribution(int[] degreeDistribution, int nodeCount, String filePath) {
        try {
            createDirectoryIfNeeded(filePath);

            try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
                writer.println("# k  p_k");

                for (int k = 0; k < degreeDistribution.length; k++) {
                    if (degreeDistribution[k] > 0) {
                        double pk = (double) degreeDistribution[k] / nodeCount;
                        writer.printf(Locale.US, "%d  %.10f%n", k, pk);
                    }
                }
            }

            ResultsPrinter.printSuccess("Distribution des degrés exportée vers: " + filePath);
        } catch (IOException e) {
            ResultsPrinter.printError("Erreur lors de l'export: " + e.getMessage());
        }
    }

    /**
     * Exporte la distribution des degrés avec comparaison à une distribution de Poisson.
     *
     * Le fichier contient trois colonnes: le degré k, la probabilité observée p_k
     * et la probabilité théorique selon une distribution de Poisson.
     *
     * @param degreeDistribution Tableau où l'indice = degré, valeur = nombre de nœuds
     * @param nodeCount Nombre total de nœuds
     * @param avgDegree Degré moyen (paramètre lambda pour Poisson)
     * @param filePath Chemin du fichier de sortie
     */
    public static void exportDegreeDistributionWithPoisson(
            int[] degreeDistribution, int nodeCount, double avgDegree, String filePath) {

        try {
            createDirectoryIfNeeded(filePath);

            try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
                writer.println("# k  p_k  poisson_pk");

                for (int k = 0; k < degreeDistribution.length; k++) {
                    if (degreeDistribution[k] > 0) {
                        double pk = (double) degreeDistribution[k] / nodeCount;
                        double poissonPk = poissonProbability(k, avgDegree);
                        writer.printf(Locale.US, "%d  %.10f  %.10f%n", k, pk, poissonPk);
                    }
                }
            }

            ResultsPrinter.printSuccess("Distribution avec Poisson exportée vers: " + filePath);
        } catch (IOException e) {
            ResultsPrinter.printError("Erreur lors de l'export: " + e.getMessage());
        }
    }

    /**
     * Exporte la distribution des distances vers un fichier.
     *
     * Le fichier contient deux colonnes: la distance et la fréquence
     * (nombre de paires de nœuds à cette distance).
     *
     * @param distanceDistribution Map associant distance -> fréquence
     * @param filePath Chemin du fichier de sortie
     */
    public static void exportDistanceDistribution(Map<Integer, Integer> distanceDistribution, String filePath) {
        try {
            createDirectoryIfNeeded(filePath);

            try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
                writer.println("# distance  frequency");

                distanceDistribution.entrySet().stream()
                        .sorted(Map.Entry.comparingByKey())
                        .forEach(entry -> {
                            writer.printf(Locale.US, "%d  %d%n", entry.getKey(), entry.getValue());
                        });
            }

            ResultsPrinter.printSuccess("Distribution des distances exportée vers: " + filePath);
        } catch (IOException e) {
            ResultsPrinter.printError("Erreur lors de l'export: " + e.getMessage());
        }
    }

    /**
     * Calcule la probabilité selon une distribution de Poisson.
     *
     * Formule: P(k) = (lambda^k * e^(-lambda)) / k!
     * Utilise les logarithmes pour éviter les débordements numériques.
     *
     * @param k Valeur pour laquelle calculer la probabilité
     * @param lambda Paramètre de la distribution (moyenne)
     * @return Probabilité P(k)
     */
    private static double poissonProbability(int k, double lambda) {
        // Utilisation des logarithmes pour éviter les débordements
        double logProb = k * Math.log(lambda) - lambda - logFactorial(k);
        return Math.exp(logProb);
    }

    /**
     * Calcule le logarithme de la factorielle.
     *
     * Méthode utilisée pour éviter les débordements lors du calcul
     * de la probabilité de Poisson pour de grandes valeurs de k.
     *
     * @param n Nombre dont on calcule log(n!)
     * @return log(n!)
     */
    private static double logFactorial(int n) {
        if (n <= 1) return 0.0;
        double result = 0.0;
        for (int i = 2; i <= n; i++) {
            result += Math.log(i);
        }
        return result;
    }

    /**
     * Crée le répertoire parent si nécessaire.
     *
     * Cette méthode garantit que le répertoire de destination existe
     * avant d'écrire un fichier.
     *
     * @param filePath Chemin du fichier (le répertoire parent sera créé)
     */
    private static void createDirectoryIfNeeded(String filePath) {
        File file = new File(filePath);
        File parentDir = file.getParentFile();
        if (parentDir != null && !parentDir.exists()) {
            parentDir.mkdirs();
        }
    }
}
