/*
 * Copyright (c) 1997-2026 IDRsolutions (https://www.idrsolutions.com)
 */
package org.jpedal.examples;

import org.jpedal.utils.LogWriter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * Helper class for converting Office documents to PDF using LibreOffice.
 */
public final class DocumentToPDFConverter {

    private static final String TEMP_DIR;
    static {
        String tempDir = System.getProperty("java.io.tmpdir");
        if (!tempDir.endsWith("/") && !tempDir.endsWith("\\")) {
            tempDir += FileSystems.getDefault().getSeparator();
        }
        TEMP_DIR = tempDir;
    }

    private DocumentToPDFConverter() {
    }

    /**
     * Uses LibreOffice to convert an office document to PDF using the provided LibreOffice executable path.
     * <p>
     * Creates a file with the same name (with .pdf extension) in the same directory as the source file.
     * <p>
     * If the output file already exists, it will not be overwritten and an IOException will be thrown.
     *
     * @param document                  Office document to convert to PDF
     * @param libreOfficeExecutablePath Absolute path for the LibreOffice executable to use to convert the document
     * @param allowOverwrite            Boolean flag on whether the output file can be overwritten
     * @return The exit code of the LibreOffice process (usually 0 regardless of success or failure)
     * @throws IOException          If the LibreOffice executable was not found, the document does not exist, the document does
     *                              not have a valid file extension, or the output file already exists (and allowOverwrite
     *                              is set to false)
     * @throws InterruptedException If the conversion process gets interrupted
     */
    public static int convert(final File document, final String libreOfficeExecutablePath, final boolean allowOverwrite) throws IOException, InterruptedException {

        if (!document.exists()) {
            throw new FileNotFoundException(document.getAbsolutePath());
        }

        final int idx = document.getAbsolutePath().lastIndexOf('.');
        if (idx < 1) {
            throw new IOException("Document does not have a valid file extension: " + document.getAbsolutePath());
        }

        final String outputDocumentPath = document.getAbsolutePath().substring(0, idx) + ".pdf";
        final File outputDocument = new File(outputDocumentPath);
        if (outputDocument.exists()) {
            if (allowOverwrite) {
                outputDocument.delete();
            } else {
                throw new IOException("Output file already exists: " + outputDocumentPath);
            }
        }

        // Use a unique profile directory to allow multiple conversions simultaneously
        final String uniqueLOProfile = (TEMP_DIR + "LO-" + UUID.randomUUID()).replace('\\', '/'); // Requires a url (not a path)

        final ProcessBuilder pb = new ProcessBuilder(libreOfficeExecutablePath,
                "-env:UserInstallation=file:///" + uniqueLOProfile,
                "--headless", "--convert-to", "pdf", document.getName());

        pb.directory(document.getParentFile());

        try {
            return pb.start().waitFor();
        } finally {
            deleteFolder(Paths.get(uniqueLOProfile));
        }
    }

    /**
     * Uses LibreOffice to convert an office document to PDF using the provided LibreOffice executable path.
     * <p>
     * Creates a file with the same name (with .pdf extension) in the same directory as the source file.
     * <p>
     * If the output file already exists, it will not be overwritten and an IOException will be thrown.
     *
     * @param document                  Office document to convert to PDF
     * @param libreOfficeExecutablePath Absolute path for the LibreOffice executable to use to convert the document
     * @return The exit code of the LibreOffice process (usually 0 regardless of success or failure)
     * @throws IOException          If the LibreOffice executable was not found, the document does not exist, the document does
     *                              not have a valid file extension, or the output file already exists (will not overwrite)
     * @throws InterruptedException If the conversion process gets interrupted
     */
    public static int convert(final File document, final String libreOfficeExecutablePath) throws IOException, InterruptedException {
        return convert(document, libreOfficeExecutablePath, false);
    }

    private static final String[] FILE_TYPES = {
            "ODT", "FODT", "ODS", "FODS", "ODP", "FODP", "ODB", "ODG", "FODG", "ODF", // LibreOffice
            "SXW", "STW", "SXC", "STC", "SXI", "STI", "SXD", "STD", "SXM", // OpenOffice
            "DOCX", "XLSX", "PPTX",
            "DOC", "DOT",
            "PPT", "PPS", "POT",
            "XLS", "XLW", "XLT"
    };

    private static final Set<String> FILE_TYPES_LIST = new HashSet<>(Arrays.asList(FILE_TYPES));

    /**
     * Returns whether the provided file has a convertible file type
     *
     * @param file file to check
     * @return whether provided file is convertible
     */
    public static boolean hasConvertibleFileType(final File file) {
        return hasConvertibleFileType(file.getAbsolutePath());
    }

    /**
     * Returns whether the provided filename has a convertible file type
     *
     * @param filename filename of the file to check
     * @return whether provided file is convertible
     */
    public static boolean hasConvertibleFileType(final String filename) {
        final int idx = filename.lastIndexOf('.');
        return idx > 0 && FILE_TYPES_LIST.contains(filename.substring(idx + 1).toUpperCase());
    }

    private static void deleteFolder(final Path pathToDelete) {

        if (Files.isDirectory(pathToDelete)) {
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToDelete)) {
                for (final Path path : stream) {
                    deleteFolder(path);
                }

                Files.delete(pathToDelete);
            } catch (final IOException e) {
                LogWriter.error(e, "Unable to delete files in directory " + pathToDelete);
            }
        } else {
            try {
                Files.delete(pathToDelete);
            } catch (final IOException e) {
                LogWriter.error(e, "Unable to delete file " + pathToDelete);
            }
        }
    }
}