// Auteur :     Gloumouth1
// Oeuvre :     http://gloumouth1.free.fr/Marabout/java"
// A utilisé :  https://fr.wiktionary.org
// Licence :    https://creativecommons.org/licenses/by-sa/4.0/deed.fr
// Date :       2024-06-24

import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class Main {

    private static void removeDeadEnds(Graph<Integer, String> graph) {
        System.out.println("Remove dead ends in the graph. ");
        List<Integer> verticesToRemove = new ArrayList<>();
        DepthFirstIterator<Integer, String> dfsIterator = new DepthFirstIterator<>(graph);
        while (dfsIterator.hasNext()) {
            Integer vertex = dfsIterator.next();
            if (graph.outDegreeOf(vertex) == 0 || graph.inDegreeOf(vertex) == 0)
                // here is a dead end, i.e. no edge from or to this vertex
                verticesToRemove.add(vertex);

            if (graph.outDegreeOf(vertex) == 1) {
                String outEdge = graph.outgoingEdgesOf(vertex).stream().toArray(String[]::new)[0];
                Integer targetVertex = graph.getEdgeTarget(outEdge);
                if (vertex.equals(targetVertex))
                    // here is vertex which loops on itself
                    verticesToRemove.add(vertex);
            }
        }
        for (Integer vertex : verticesToRemove)
            // remove the vertices, the connected edges will be excluded as well
            graph.removeVertex(vertex);

    }

    public static void simplifyGraph(Graph<Integer, String> graph) {
        printGraphSize(graph);
        System.out.println("Iterative removal of graph dead ends");
        long newEdgesNumber = -1;
        long edgesNumber = graph.edgeSet().stream().count();
        while (newEdgesNumber < edgesNumber) {
            edgesNumber = graph.edgeSet().stream().count();
            removeDeadEnds(graph);
            newEdgesNumber = graph.edgeSet().stream().count();
            printGraphSize(graph);
        }

    }

    public static void printGraphSize(Graph<Integer, String> graph) {
        System.out.println("Graph size = (" + graph.vertexSet().stream().count() + " vertices, "
                + graph.edgeSet().stream().count() + " edges)");
    }

    public static void writeGraphFile(Graph<Integer, String> graph, String graphFileName, String dictionaryFileName, String targetLanguage) {
        try {
            FileWriter w = new FileWriter(graphFileName);

            w.write("// Author     : Gloumouth1\n");
            w.write("// URL        : http://gloumouth1.free.fr/Marabout\n");
            w.write("// Used       : https://fr.wiktionary.org\n");
            w.write("// License    : https://creativecommons.org/licenses/by-sa/4.0/deed.fr\n");
            w.write("// Date       : 2024-06-24\n");
            w.write("// Dictionary     : " + dictionaryFileName + "\n");
            w.write("// Language       : " + targetLanguage + "\n");

            // convert the graph in a javascript array of arrays of couples
            w.write("var marabout_graph = [\n");
            Integer[] verticies = graph.vertexSet().stream().toArray(Integer[]::new);

            Map<Integer, Integer> verticiesOrder = new HashMap<>();
            for (int v = 0; v < verticies.length; v++)
                verticiesOrder.put(verticies[v], v);

            for (int v = 0; v < verticies.length; v++) {
                w.write("/*" + v + "*/[");
                String[] outcomingEdges = graph.outgoingEdgesOf(verticies[v]).stream().toArray(String[]::new);
                for (int e = 0; e < outcomingEdges.length; e++) {
                    w.write("[\"" + outcomingEdges[e] + "\",");
                    Integer targetVertex = graph.getEdgeTarget(outcomingEdges[e]);
                    w.write(verticiesOrder.get(targetVertex) + "]");
                    if (e < outcomingEdges.length - 1)
                        w.write(",");
                }
                w.write("]");
                if (v < verticies.length - 1)
                    w.write(",");
                w.write("\n");
            }
            w.write("];");

            w.close();
            System.out.println("End of output file writing");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void readDictionary(String dictionaryFileName, String dictionaryLanguage, String targetLanguage, int syllablesMin, int syllablesMax, Graph<Integer, String> graph) {
        System.out.print("\nStart of XML file parsing");
        try {
            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
            MyContentHandler handler = new MyContentHandler(dictionaryLanguage, targetLanguage, syllablesMin, syllablesMax, graph);
            XMLReader xmlReader = parser.getXMLReader();
            xmlReader.setContentHandler(handler);
            InputSource source = new InputSource(dictionaryFileName);
            source.setEncoding(StandardCharsets.UTF_8.displayName());
            xmlReader.parse(source);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (MySAXTerminatorException e) {
            System.out.println("XML elements max reached, stop of XML parsing.");
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("End of XML file parsing");
    }

    //private static String DICTIONARY_FILE_NAME = "resources\\frwiktionary-20240601-pages-articles-multistream.xml";
    private static String   DICTIONARY_FILE_NAME = "resources\\enwiktionary-20240620-pages-articles-multistream.xml";
    private static String   DICTIONARY_LANGUAGE = "EN";
    private static String   TARGET_LANGUAGE = "en";
    private static int      SYLLABLES_MIN = 2;
    private static int      SYLLABLES_MAX = 20;

    public static void main(String[] args) {
        Graph<Integer, String> graph = new DefaultDirectedGraph<>(String.class);
        readDictionary(DICTIONARY_FILE_NAME, DICTIONARY_LANGUAGE, TARGET_LANGUAGE, SYLLABLES_MIN, SYLLABLES_MAX, graph);
        simplifyGraph(graph);
        writeGraphFile(graph, "marabout_graph-" + DICTIONARY_LANGUAGE + "-" + TARGET_LANGUAGE + "-" + SYLLABLES_MIN + "-" + SYLLABLES_MAX + ".js", 
            DICTIONARY_FILE_NAME, TARGET_LANGUAGE);
    }
}
