/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.core;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import static org.jboss.installer.core.LoggerUtils.taskLog;

public class FileUtils {

    public static String replaceLines(HashMap<String, String> replacements, BufferedReader reader) throws IOException {
        StringBuilder output = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            for (String key : replacements.keySet()) {
                if (line.contains(key)) {
                    line = line.replace(key, replacements.get(key));
                }
            }
            output.append(line);
            output.append(System.lineSeparator());
        }
        return output.toString();
    }

    public static void writeToFile(File file, String input) throws IOException {
        try (PrintWriter writer = new PrintWriter(new FileOutputStream(file, false))) {
            writer.write(input);
        }
    }

    public static boolean isFileEmpty(File file) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            if (br.readLine() == null) {
                return true;
            }
        }
        return false;
    }

    public static Document loadXmlFromString(String xmlString) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder;

        builder = factory.newDocumentBuilder();
        InputSource inputSource = new InputSource(new StringReader(xmlString));
        return builder.parse(inputSource);

    }

    public static Document loadXmlFromFile(File file) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(file);
    }

    public static JarFile getJarFromPath(String jarPath) throws IOException {

        JarFile jar;
        if (isUrl(jarPath)) {
            URL urlToJar = new URL(jarPath);
            URL url = new URL(urlToJar, "jar:" + urlToJar + "!/");
            JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
            jar = jarConnection.getJarFile();
        } else {
            jar = new JarFile(jarPath);
        }
        return jar;
    }

    /**
     * checks if the {@code text} is a URL
     * @param text
     * @return
     */
    public static boolean isUrl(String text) {

        try {
            new URL(text);
            return true;
        } catch (MalformedURLException e) {
            return false;
        }
    }

    /**
     * converts text containing either valid URL or local path into an URL.
     *
     * @param textValue
     * @return
     * @throws InstallerRuntimeException if the {@code textValue} is neither an URL or a path.
     */
    public static URL asUrl(String textValue) {
        try {
            return new URL(textValue);
        } catch (MalformedURLException e) {
            try {
                return Path.of(textValue).toAbsolutePath().toUri().toURL();
            } catch (MalformedURLException ex) {
                throw new InstallerRuntimeException("The remote repository value is not a URL", e);
            }
        }
    }

    public static void unzip(File file, File
            outputDirectory, UnarchiveProgressCallback unarchiveProgressCallback) {

        try (ZipFile zipFile = new ZipFile(file)) {
            unarchiveProgressCallback.unarchiveStarted();
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            int nextSize = 0;
            int zipFileSize = zipFile.size();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                File entryDestination = newFile(outputDirectory, entry.getName());
                if (entry.isDirectory()) {
                    if (!entryDestination.isDirectory() && !entryDestination.mkdirs()) {
                        throw new IOException("Failed to create directory " + entryDestination);
                    }
                } else {
                    // fix for Windows-created archives
                    File parent = entryDestination.getParentFile();
                    if (!parent.isDirectory() && !parent.mkdirs()) {
                        throw new IOException("Failed to create directory " + parent);
                    }
                    try (OutputStream out = new FileOutputStream(entryDestination)) {
                        zipFile.getInputStream(entry).transferTo(out);
                    }
                }
                nextSize++;
                unarchiveProgressCallback.unarchiveUpdate(nextSize,zipFileSize);
            }
            unarchiveProgressCallback.unarchiveFinished();
        } catch (IOException e) {
            unarchiveProgressCallback.unarchiveFinished();
            throw new InstallerRuntimeException("Unable to unzip file " + file.getName() + " to " + outputDirectory.getName(), e);
        }
    }

    private static File newFile(File destinationDir, String name) throws IOException {
        File destFile = new File(destinationDir, name);

        String destDirPath = destinationDir.getCanonicalPath();
        String destFilePath = destFile.getCanonicalPath();

        if (!destFilePath.startsWith(destDirPath + File.separator)) {
            throw new IOException("Entry is outside the target dir: " + name);
        }

        return destFile;
    }

    public static boolean isZipFile(File file) {
        if (file != null && file.exists() && file.isFile()) {
            try (ZipFile zipFile = new ZipFile(file)) {
                return true;
            } catch (ZipException zipCorrupted) {
            } catch (IOException anyIOError) {
            }
        }
        return false;
    }

    public static Path createTempDirectory(String namePrefix) {
        try {
            final Path tempDir = Files.createTempDirectory(namePrefix);
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                taskLog.debug("Deleting the temporary directory " + tempDir);
                try {
                    org.apache.commons.io.FileUtils.deleteDirectory(tempDir.toFile());
                } catch (IOException e) {
                    taskLog.debug("Failed to delete temporary directory " + tempDir, e);
                }
            }));
            return tempDir;
        } catch (IOException e) {
            throw new InstallerRuntimeException("Unable to create temporary directory.", e);
        }
    }

    public static Path createTempFile(String namePrefix, String nameSuffix) {
        try {
            Path tempFile = Files.createTempFile(namePrefix, nameSuffix);
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                taskLog.debug("Deleting the temporary file " + tempFile);
                try {
                    org.apache.commons.io.FileUtils.deleteDirectory(tempFile.toFile());
                } catch (IOException e) {
                    taskLog.debug("Failed to delete temporary file " + tempFile, e);
                }
            }));
            return tempFile;
        } catch (IOException e) {
            throw new InstallerRuntimeException("Unable to create temporary file " + namePrefix + ".", e);
        }
    }
}
