package metric;

import java.io.File;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Locale;
import java.util.function.Function;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.graph.BreadthFirstIterator;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.stream.file.FileSourceEdge;

import metric.utils.GraphUtils;

/**
 * <p>Programme permettant de récupérer quelques mesures sur un réseau d'intéraction.</p>
 * <p>
 * L'execution pouvant prendre jusqu'à plus d'un dizaine de minutes, ci-dessous figure
 * les résultats envoyés dans le terminal.
 * </p>
 * <code>
 * Graph DBLP
 * ----------------------------
 * N         : 317080
 * L         : 1049866
 * <k>       : 6.62208890914917
 * <C>       : 0.6324308280637396
 * Connected : true
 * Write degree distribution...done
 * Write distance distribution...done
 * ----------------------------
 * Graph Random
 * ----------------------------
 * N         : 317088
 * L         : 1110910
 * <k>       : 7.006950855255127
 * <C>       : 2.1839707467845732E-5
 * Connected : false
 * Write degree distribution...done
 * Write distance distribution...done
 * ----------------------------
 * Generate Preferential...done
 * Graph Preferential
 * ----------------------------
 * N         : 317082
 * L         : 1267243
 * <k>       : 7.993156433105469
 * <C>       : 4.884052160020197E-4
 * Connected : true
 * Write degree distribution...done
 * Write distance distribution...done
 * </code>
 * 
 * @author Yoann Eichelberger
 *         M1 - IWOCS
 */
public class Metric {

    private static final int NODE_SAMPLE = 1000;

    /**
     * Lance l'application.
     * 
     * @param args non utilisé.
     */
    public static void main(String[] args) {
        // Graphe DBLP
        Graph graph = GraphUtils.readResource("DBLP", "com-dblp.ungraph.txt", new FileSourceEdge());
        printInfo(graph);
        writeDegreeDistribution(graph, "data/ddegree_dblp.dat");
        writeDistanceDistribution(graph, NODE_SAMPLE, "data/ddistance_dblp.dat");

        int nodeCount = graph.getNodeCount();
        double averageDegree = Toolkit.averageDegree(graph);

        double rdmAvgCoeffClustering = averageDegree / nodeCount;
        boolean rdmIsConnected = averageDegree / Math.log(nodeCount) > 1;
        
        // Graphe aléatoire
        Graph rdmGraph = GraphUtils.randomGen(nodeCount, averageDegree);
        printInfo(rdmGraph);
        writeDegreeDistribution(rdmGraph, "data/ddegree_random.dat");
        writeDistanceDistribution(rdmGraph, NODE_SAMPLE, "data/ddistance_random.dat");

        // Graphe préférentiel
        Graph pGraph = GraphUtils.randomPreferencielGraph(nodeCount, averageDegree);
        printInfo(pGraph);
        writeDegreeDistribution(pGraph, "data/ddegree_preferential.dat");
        writeDistanceDistribution(pGraph, NODE_SAMPLE, "data/ddistance_preferential.dat");
    }

    private static void printInfo(Graph graph) {
        int nodeCount = graph.getNodeCount();
        int edgeCount = graph.getEdgeCount();
        double avgDegree = Toolkit.averageDegree(graph);
        double avgCoeffClustering = Toolkit.averageClusteringCoefficient(graph);
        boolean isConnected = Toolkit.isConnected(graph);
        
        System.out.println("\nGraph " + graph.getId());
        System.out.println("----------------------------");
        System.out.println("N         : " + nodeCount);
        System.out.println("L         : " + edgeCount);
        System.out.println("<k>       : " + avgDegree);
        System.out.println("<C>       : " + avgCoeffClustering);
        System.out.println("Connected : " + isConnected);
    }

    private static int[] degreeDistribution(Graph graph) {
        return Toolkit.degreeDistribution(graph);
    }

    private static double averageDistance(Graph graph, int sample) {
        int[] dd = distanceDistribution(graph, sample);
        int sum = Arrays.stream(dd).reduce(0, Integer::sum);
        double v = 0;
        for (int i = 0; i < dd.length; i++)
            v += i * dd[i];
        return v / (double) sum;
    }

    private static int[] distanceDistribution(Graph graph, int sample) {
        int[] dd = new int[50];
        Toolkit.randomNodeSet(graph, sample).forEach(node -> {
            BreadthFirstIterator<Node> bfi = (BreadthFirstIterator<Node>) node.getBreadthFirstIterator();
            while (bfi.hasNext())
                dd[bfi.getDepthOf(bfi.next())]++;
        });
        return dd;
    }

    private static void writeDegreeDistribution(Graph graph, String fileName) {
        System.out.print("Write degree distribution... ");
        int[] dd = degreeDistribution(graph);
        int nodeCount = graph.getNodeCount();
        writeDistribution(fileName, dd, data -> (double) data / nodeCount);
        System.out.println("done");
    }

    private static void writeDistanceDistribution(Graph graph, int sample, String fileName) {
        System.out.print("Write distance distribution... ");
        int[] dd = distanceDistribution(graph, sample);
        int sum = Arrays.stream(dd).reduce(0, Integer::sum);
        writeDistribution(fileName, dd, data -> (double) data / sum);
        System.out.println("done");
    }

    private static void writeDistribution(String fileName, int[] dd, Function<Integer, Double> function) {
        try (FileWriter writer = new FileWriter(new File(fileName))) {
            for (int i = 0; i < dd.length; i++)
                if (dd[i] > 0)
                    writer.write(String.format(Locale.US, "%6d%20.8f%n", i, function.apply(dd[i])));
            writer.flush();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
/*

*/