package ch.bfh.lpdg;

import ch.bfh.lpdg.datastructure.Dependency;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public class GraphHelper {

    public final static GraphHelper INSTANCE = new GraphHelper();
    private final InteractionHandler interactionHandler = InteractionHandler.getInstance();

    private GraphHelper() {
    }

    public static GraphHelper getInstance() {
        return INSTANCE;
    }

    /**
     * This method creates a new .tex file containing a list of the found dependencies and the dependeccy graph created with Graphviz
     *
     * @param dependency for which the .tex file should be created
     * @param outputPath in which the created file should be located.
     * @return the path of the newly created .tex file.
     */
    public Path createFileForDependency(final Dependency dependency, final String outputPath) {
        final String dependencyFileName = "dependencies_" + dependency.getName() + ".tex";
        final String dependencyFilePath = outputPath + dependencyFileName;
        var dependencyFile = new File(outputPath + dependencyFileName);

        var latexHelper = new LatexHelper();
        this.writeDependencyToFile(dependencyFile, dependency);

        var result = latexHelper.compileTempDocument(outputPath, dependencyFilePath);
        interactionHandler.printDebugMessage(String.valueOf(result));

        if (!generatePdfFromDotFile(outputPath + "dependencyGraph.dot")) {
            interactionHandler.printDebugMessage("Something went wrong trying to generate a pdf from the dot file.");
        }

        //Second compile cycle to include the generated graph in the file.
        var result2 = latexHelper.compileTempDocument(outputPath, dependencyFilePath);
        interactionHandler.printDebugMessage(String.valueOf(result2));

        return dependencyFile.toPath();
    }

    /**
     * This method created the .tex file containing a list of the dependencies and its graph.
     *
     * @param file       in which should be written
     * @param dependency the dependency for which the graph should be created
     */
    private void writeDependencyToFile(final File file, final Dependency dependency) {
        try {
            final String dependencyList = dependency.toLaTeXString();
            final String graphString = dependency.toGraphString();
            final String truncatedGraphString = this.truncateGraphString(graphString);
            final String reorganizedGraphString = this.reorganizeGraphString(truncatedGraphString);

            BufferedWriter writer = Files.newBufferedWriter(file.toPath());
            writer.write("""
                    \\documentclass{article}
                    \\usepackage[pdf]{graphviz}
                    \\usepackage{pdflscape}
                    \\begin{document}
                    \\begin{verbatim}
                    """);
            writer.write(dependencyList);
            writer.write("\\end{verbatim}\n");

            if (dependency.getDependencyList().size() != 0) {
                writer.write("\\pagebreak\n");
                writer.write("\\begin{landscape}\n");
                writer.write("\\digraph{dependencyGraph}{\n");
                writer.write("\tnewrank=true;\n\t");
                writer.write("\trank=\"same\";\n\t");
                writer.write("\trankdir=TB;\n\t");
                writer.write("\tratio=\"fill\";\n\t");
                writer.write("\tsize=\"8.3,11.7!\";\n\t");
                writer.write("\tmargin=0;\n\t");
                writer.write(reorganizedGraphString);
                writer.write("}\n");
                writer.write("\\end{landscape}\n");
            }

            writer.write("\\end{document}\n");
            writer.close();
        } catch (IOException e) {
            System.err.println("Error in the temporary file: " + e);
        }
    }

    /**
     * Removes duplicated lines from graphString
     *
     * @param graphString for which duplicated lines should be removed
     * @return a truncated string
     */
    private String truncateGraphString(final String graphString) {
        StringBuilder builder = new StringBuilder();
        for (String line : new LinkedHashSet<>(Arrays.asList(graphString.split("\n")))) {
            builder.append(line).append("\n");
        }
        return builder.toString();
    }

    private String reorganizeGraphString(String graphString) {
        StringBuilder result = new StringBuilder();
        String[] lines = graphString.split("\n");

        Map<String, Integer> lastOccurrences = new LinkedHashMap<>();

        // Iterate to find the last occurrence index for each node
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].trim();
            if (!line.isEmpty()) {
                String[] parts = line.split(" -> ");
                String rightNode = parts[1].substring(0, parts[1].indexOf(';')).trim();
                lastOccurrences.put(rightNode, i);
            }
        }

        // Append lines with the last occurrences
        for (int i : lastOccurrences.values()) {
            result.append(lines[i]).append("\n");
        }

        // Append lines with other occurrences
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].trim();
            if (!line.isEmpty()) {
                String[] parts = line.split(" -> ");
                String rightNode = parts[1].substring(0, parts[1].indexOf(';')).trim();

                // Skip if it's the last occurrence
                if (i != lastOccurrences.get(rightNode)) {
                    result.append(lines[i]).append("\n");
                }
            }
        }

        return result.toString();
    }

    /**
     * This method generated a pdf document for the .dot document.
     * This is required to include it to the .tex document given to the user.
     *
     * @param dependencyGraphPath of the generated .dot file
     * @return if the generation was successful or not
     */
    private boolean generatePdfFromDotFile(final String dependencyGraphPath) {
        try {
            List<String> cmd = Arrays.asList(
                    "dot",
                    "-Tpdf",
                    "-o", dependencyGraphPath.replace("dot", "pdf"),
                    dependencyGraphPath);

            ProcessBuilder processBuilder = new ProcessBuilder(cmd);

            // Redirect the error stream to the output stream
            processBuilder.redirectErrorStream(true);

            Process process = processBuilder.start();

            var res = process.waitFor();

            return res == 0;
        } catch (Exception e) {
            System.err.println("Error while compiling the dot file to pdf: " + e);
            return false;
        }
    }
}