package ch.bfh.lpdg;

import ch.bfh.lpdg.datastructure.Dependency;
import ch.bfh.lpdg.datastructure.DependencyType;
import lombok.NoArgsConstructor;
import org.springframework.util.FileSystemUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@NoArgsConstructor
public class LatexHelper {

    private final InteractionHandler interactionHandler = InteractionHandler.getInstance();
    private Path tempFile;
    private Path sourceFile;
    private Path tempFolder;
    private Dependency dependencies;

    public LatexHelper(File file, Dependency dependencies) throws IOException {
        this.dependencies = dependencies;
        this.tempFile = createTempFile(file, ".temp");
        this.sourceFile = file.toPath();
        this.tempFolder = createTempFolder(Paths.get(file.getParent()));
        interactionHandler.printDebugMessage("Temp-File has been created. " + tempFile);
    }

    private static String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf(".");
        return (lastDotIndex != -1) ? fileName.substring(lastDotIndex) : "";
    }

    public void findUnnecessaryDependencies(Boolean overwrite, Boolean minimize) throws IOException {
        this.interactionHandler.printDebugMessage("Finding unnecessary Dependencies.");
        List<Dependency> unnecessaryDependencies = new ArrayList<>();
        for (var dep : dependencies.getDependencyList()) {
            if(dep.getType() != DependencyType.REQUIRE_PACKAGE && dep.getType() != DependencyType.USE_PACKAGE)
                continue;
            this.interactionHandler.printDebugMessage("Trying without " + dep.toString());
            this.replaceDependency(tempFile, dep.getSource(), "%" + dep.getSource());
            if (this.compileTempDocument(tempFolder.toAbsolutePath().toString(), tempFile.toAbsolutePath().toString())) {
                unnecessaryDependencies.add(dep);
                dep.IsUnused = true;
            }
            this.replaceDependency(tempFile, "%" + dep.getSource(), dep.getSource());
        }
        this.deleteTempFiles();

        if(unnecessaryDependencies.size() > 0) {
            System.out.println("\nThe following Packages are not needed:");
            for (var dep : unnecessaryDependencies)
                System.out.println("\t" + dep.getName());
        }

        if (minimize || overwrite) {
            Path fileToMinimize = overwrite ? this.sourceFile : createTempFile(sourceFile.toFile(), ".minimized");
            this.writeMinimizedFile(fileToMinimize);
        }
    }

    public boolean compileTempDocument(final String outputDirectory, final String documentToCompile) {
        try {
            List<String> cmd = Arrays.asList(
                    "pdflatex", //We use pdflatex to compile the documents.
                    "-output-directory", outputDirectory, //Set the output directory to the created tempFolder directory of the input
                    "-interaction", "nonstopmode",
                    "-halt-on-error",
                    "-file-line-error",
                    documentToCompile);
            this.interactionHandler.printDebugMessage("Running: " + cmd);
            ProcessBuilder processBuilder = new ProcessBuilder(cmd);

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

            Process process = processBuilder.start();
            InputStream is = process.getInputStream();
            int readBytes;
            byte[] buffer = new byte[1024];
            StringBuilder outputBuilder = new StringBuilder();

            while ((readBytes = is.read(buffer)) != -1) {
                String chunk = new String(buffer, 0, readBytes, StandardCharsets.UTF_8);
                outputBuilder.append(chunk);
            }
            this.interactionHandler.printDebugMessage(outputBuilder.toString());

            var res = process.waitFor();
            this.interactionHandler.printDebugMessage("ReturnCode: " + res);
            return res == 0;
        } catch (Exception e) {
            System.err.println("Error while compiling the temporary file: " + e);
            return false;
        }
    }

    private void writeMinimizedFile(Path minimizedFile) {
        for (var dep : dependencies.getDependencyList()) {
            if (!dep.IsUnused)
                continue;
            replaceDependency(minimizedFile, dep.getSource(), "%" + dep.getSource());
        }
    }

    private void replaceDependency(Path file, String dependency, String replaceWith) {
        this.interactionHandler.printDebugMessage("Replacing " + dependency + " with " + replaceWith);
        try {
            BufferedReader reader = Files.newBufferedReader(file);
            StringBuilder content = new StringBuilder();

            String row;
            while ((row = reader.readLine()) != null) {
                content.append(row).append(System.lineSeparator());
            }

            String newContent = content.toString().replace(dependency, replaceWith);

            BufferedWriter writer = Files.newBufferedWriter(file);
            writer.write(newContent);

            reader.close();
            writer.close();
        } catch (IOException e) {
            System.err.println("Error in the temporary file: " + e);
        }
    }

    private Path createTempFile(File file, String identifier) throws IOException {
        var extension = getFileExtension(file.getName());
        return Files.copy(Paths.get(file.getAbsolutePath()), Paths.get(file.getPath().replace(extension, identifier + extension)), StandardCopyOption.REPLACE_EXISTING);
    }

    private Path createTempFolder(Path parentDirectory) throws IOException {
        return Files.createTempDirectory(parentDirectory, "LatexDependencyTemp");
    }

    private void deleteTempFiles() throws IOException {
        FileSystemUtils.deleteRecursively(tempFolder.toFile());
        Files.delete(tempFile);
    }
}
