/**
 * Authors: Frederik Leyvraz, David Degenhardt
 * License: GNU General Public License v3.0 only
 * Version: 1.0.0
 */

package ch.bfh.ti.latexindexer;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class Plotter {

    private static int fileNameCounter;
    private final int DEFAULT_NUMBER_OF_WORDS = 20;
    private List<Word> words;

    /**
     * Constructor
     * @param words A list of the words parsed from a document.
     */
    public Plotter(List<Word> words) {
        fileNameCounter = 1;
        this.words = new LinkedList<>(words);
    }

    /**
     * Creates a list of words according to the filters and rules specified by the user.
     * @param args An array containing the arguments passed to this command by the user.
     * @return The list of words as a single formatted string.
     */
    public String print(UI.Argument[] args) {
        List<Word> list = getSortedList(args);
        int maxLength = 0;
        for (int i = 0; i < list.size(); i++) {
            maxLength = Math.max(list.get(i).getValue().length(), maxLength);
        }
        StringBuilder distribution = new StringBuilder();
        for (Word word : list) {
            distribution.append(String.format("%-" + (maxLength + 10) + "s %d%n", word.getValue(), word.getFrequency()).replace(' ', '.'));
        }
        return distribution.toString();
    }

    /**
     * Generates a plot of the words specified by the user
     * @param workingDirectory The working directory path where the program is being run.
     * @throws IOException If generatePlotTex() throws it.
     * @throws InterruptedException If renderPlot() throws it.
     */
    public void generatePlot(String workingDirectory, UI.Argument[] args) throws IOException, InterruptedException {
        String filePath = workingDirectory + "/";
        String fileName = null;
        for (int i = 0; i < args.length; i++){
            if (args[i].getName().equals("-f")){
                fileName = args[i].getValue();
                args[i] = new UI.Argument(null, null);
            }
        }
        fileName = fileName == null ? "word-frequency-plot-" + fileNameCounter++ : fileName;
        filePath += fileName + ".tex";
        List<Word> list = getSortedList(args);
        generatePlotTex(filePath, list);
        renderPlot(workingDirectory, fileName);
    }

    /**
     * Generates the .tex file to create a plot using the PGFplots package.
     * @param filePath The full qualified path to the .tex file that will contain the code to generate the plot.
     * @throws IOException If the file cannot be written to.
     */
    private void generatePlotTex(String filePath, List<Word> list) throws IOException {
        try (FileWriter writer = new FileWriter(filePath)) {
            writer.write("\\documentclass{standalone}\n");
            writer.write("\\usepackage{pgfplots}\n");
            writer.write("\\pgfplotsset{compat=1.18}\n");
            writer.write("\\begin{document}\n");
            writer.write("\\begin{tikzpicture}\n");
            writer.write("\\begin{axis}[ybar,\n");
            writer.write("    bar width=1,\n");
            writer.write("    ymin=0,\n");
            writer.write("    xtick=data,\n");
            writer.write("    xtick pos=left,\n");
            writer.write("    xticklabel style={rotate=90},");
            writer.write("    nodes near coords,\n");
            writer.write("    xticklabels={");

            for (int i = 0; i < list.size() - 1; i++) {
                writer.write(list.get(i).getValue() + ",");
            }
            writer.write(list.get(list.size()-1).getValue());

            writer.write("},\n");
            writer.write("    xlabel={Words},\n");
            writer.write("    ylabel={Frequency},\n");
            writer.write("    title={Frequency of parsed words},\n");
            writer.write("    ymajorgrids=true,\n");
            writer.write("    grid style=dashed]\n");
            writer.write("\\addplot [\n");
            writer.write("    color=red,\n");
            writer.write("    fill=red!30\n");
            writer.write("    ]\n");
            writer.write("    coordinates {\n");

            // Write data points
            for (int i = 0; i < list.size(); i++) {
                writer.write("    (" + i + "," + list.get(i).getFrequency() + ")\n");
            }

            writer.write("};\n");
            writer.write("\\end{axis}\n");
            writer.write("\\end{tikzpicture}\n");
            writer.write("\\end{document}\n");
        }
    }

    /**
     * Calls pdflatex on the .tex file containing the PGFplots code.
     * @param fileName The full qualified path to the .tex file containing the PGFplots code.
     * @throws IOException If the file cannot be read from.
     * @throws InterruptedException If pdflatex cannot be run.
     */
    private void renderPlot(String workingDirectory, String fileName) throws IOException, InterruptedException {
        ProcessBuilder pb = new ProcessBuilder("pdflatex", fileName);
        pb.directory(new File(workingDirectory));
        Process process = pb.start();
        int exitCode = process.waitFor();

        if (exitCode == 0) {
            System.out.println("Successfully compiled LaTeX to PDF.");
        } else {
            System.err.println("LaTeX compilation failed.");
        }
    }

    /**
     * A helper method returning a sorted and filtered list of words according to the user's specifications.
     * @param args An array of arguments passed to the command by the user.
     * @return A list of sorted and filtered words.
     */
    private List<Word> getSortedList(UI.Argument[] args){
        List<Word> list = new LinkedList<>(this.words);
        int n = Math.min(DEFAULT_NUMBER_OF_WORDS, list.size());
        Comparator<Word> c = new Word.FrequencyComparator();
        boolean reverse = false;
        for (int i = 0; i < args.length; i++) {
            switch (args[i].getName()) {
                case "-n" -> {
                    try {
                        int m = Integer.parseInt(args[i].getValue());
                        if (m < 0) {
                            System.out.println(UI.WARNING + "'-n' must be followed by a positive integer." + UI.RESET);
                        } else {
                            n = Math.min(m, list.size());
                        }
                    } catch (NumberFormatException e) {
                        System.out.println(UI.WARNING + "'-n' must be followed by a number." + UI.RESET);
                    }
                }
                case "-c" -> {
                    if (args[i].getValue().equalsIgnoreCase("a")) {
                        c = new Word.AlphabeticalComparator();
                    } else if (!args[i].getValue().equalsIgnoreCase("f")) {
                        System.out.println(UI.WARNING + "'-c' must be followed by either a or f" + UI.RESET);
                    }
                }
                case "-p" -> {
                    String prefix = args[i].getValue().toLowerCase();
                    list = list.stream().filter(word -> word.getValue().toLowerCase().startsWith(prefix)).collect(Collectors.toList());
                }
                case "-r" -> {
                    reverse = args[i].getValue().equalsIgnoreCase("true");
                    if (!args[i].getValue().equalsIgnoreCase("true") && !args[i].getValue().equalsIgnoreCase("false")){
                        System.out.println(UI.WARNING + "'-r' must be followed by either 'true' or 'false'" + UI.RESET);
                    }
                }
                case null -> {}
                default -> System.out.println(UI.WARNING + "Dropped parameter '" + args[i].getName() + "' as it is unknown." + UI.RESET);
            }
        }
        list.sort(c);
        list = list.subList(0,Math.min(n, list.size()));
        if (reverse) {
            Collections.reverse(list);
        }
        return list;
    }

}
