package fxlauncher;

import com.sun.javafx.application.ParametersImpl;

import javax.xml.bind.JAXB;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CreateManifest {
    private static ArrayList<String> includeExtensions = new ArrayList<>();

    static {
        includeExtensions.addAll(Arrays.asList("jar", "war"));
    }

    public static void main(String[] args) throws IOException, URISyntaxException {
        URI baseURI = URI.create(args[0]);
        String launchClass = args[1];
        Path appPath = Paths.get(args[2]);

        String cacheDir = null;
        Boolean acceptDowngrade = null;
        Boolean stopOnUpdateErrors = null;
        String parameters = null;
        String whatsNew = null;
        String preloadNativeLibraries = null;
        Boolean lingeringUpdateScreen = false;
        Boolean stopOnUpdateErrorsDeprecated = null;

        if (args.length > 3) {
            // Parse named parameters
            List<String> rawParams = new ArrayList<>();
            rawParams.addAll(Arrays.asList(args).subList(3, args.length));
            ParametersImpl params = new ParametersImpl(rawParams);
            Map<String, String> named = params.getNamed();

            if (named != null) {
                // Configure cacheDir
                if (named.containsKey("cache-dir"))
                    cacheDir = named.get("cache-dir");

                // Configure acceptDowngrade
                if (named.containsKey("accept-downgrade"))
                    acceptDowngrade = Boolean.valueOf(named.get("accept-downgrade"));

                // Configure stopOnUpdateErrors
                if (named.containsKey("stop-on-update-errors"))
                    stopOnUpdateErrors = Boolean.valueOf(named.get("stop-on-update-errors"));

                // Configure preload native libraries
                if (named.containsKey("preload-native-libraries"))
                    preloadNativeLibraries = named.get("preload-native-libraries");

                // Should the update screen stay until the primary stage is shown?
                if (named.containsKey("lingering-update-screen"))
                    lingeringUpdateScreen = Boolean.valueOf(named.get("lingering-update-screen"));

                // Configure the whats-new option
                if (named.containsKey("whats-new"))
                    whatsNew = named.get("whats-new");

                // Add additional files with these extensions to manifest
                if (named.containsKey("include-extensions"))
                    includeExtensions.addAll(
                            Arrays.stream(named.get("include-extensions").split(","))
                                    .filter(s -> s != null && !s.isEmpty())
                                    .collect(Collectors.toList())
                    );
            }

            // Append the rest as manifest parameters
            StringBuilder rest = new StringBuilder();
            for (String raw : params.getRaw()) {
                // Special case for deprecated parameter.
                if (raw.equals("--stopOnUpdateErrors")) {
                    stopOnUpdateErrorsDeprecated = true;
                    continue;
                }
                if (raw.startsWith("--cache-dir=")) continue;
                if (raw.startsWith("--accept-downgrade=")) continue;
                if (raw.startsWith("--stop-on-update-errors=")) continue;
                if (raw.startsWith("--include-extensions=")) continue;
                if (raw.startsWith("--preload-native-libraries=")) continue;
                if (raw.startsWith("--whats-new")) continue;
                if (raw.startsWith("--lingering-update-screen")) continue;
                if (rest.length() > 0) rest.append(" ");
                rest.append(raw);
            }

            // Add the raw parameter string to the manifest
            if (rest.length() > 0)
                parameters = rest.toString();
        }

        FXManifest manifest = create(baseURI, launchClass, appPath);
        if (cacheDir != null) manifest.cacheDir = cacheDir;
        if (acceptDowngrade != null) manifest.acceptDowngrade = acceptDowngrade;
        if (parameters != null) manifest.parameters = parameters;
        if (preloadNativeLibraries != null) manifest.preloadNativeLibraries = preloadNativeLibraries;
        if (whatsNew != null) manifest.whatsNewPage = whatsNew;
        manifest.lingeringUpdateScreen = lingeringUpdateScreen;

        // Use --stop-on-update-errors if it was specified.
        if (stopOnUpdateErrors != null) {
            manifest.stopOnUpdateErrors = stopOnUpdateErrors;
            // If --stopOnUpdateErrors is also present, display warning.
            if (stopOnUpdateErrorsDeprecated != null) {
                System.out.println("Warning: --stopOnUpdateErrors is deprecated. "
                        + "Overriding with --stop-on-update-errors.");
            }
        }
        // If --stop-on-update-errors was not specified,
        // use --stopOnUpdateError if it was specified.
        else if (stopOnUpdateErrorsDeprecated != null){
            manifest.stopOnUpdateErrors = stopOnUpdateErrorsDeprecated;
            System.out.println("Warning: --stopOnUpdateErrors is deprecated. "
                    + "Use --stop-on-update-errors instead.");
        }
        JAXB.marshal(manifest, appPath.resolve("app.xml").toFile());
    }

    public static FXManifest create(URI baseURI, String launchClass, Path appPath) throws IOException, URISyntaxException {
        FXManifest manifest = new FXManifest();
        manifest.ts = System.currentTimeMillis();
        manifest.uri = baseURI;
        manifest.launchClass = launchClass;

        if(!manifest.uri.getPath().endsWith("/")) {
            manifest.uri = new URI(String.format("%s/", baseURI.toString()));
        }
        Files.walkFileTree(appPath, new SimpleFileVisitor<Path>() {
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (!Files.isDirectory(file) && shouldIncludeInManifest(file) && !file.getFileName().toString().startsWith("fxlauncher"))
                    manifest.files.add(new LibraryFile(appPath, file));
                return FileVisitResult.CONTINUE;
            }
        });

        return manifest;
    }

    /**
     * Add the includeExtensions to the default list of "war" and "jar".
     *
     * Allthough the method is called setIncludeExtensions, it actually does an addAll.
     * @param includeExtensions
     */
    public static void setIncludeExtensions(List<String> includeExtensions) {
        CreateManifest.includeExtensions.addAll(includeExtensions);
    }

    private static boolean shouldIncludeInManifest(Path file) {
        String filename = file.getFileName().toString();
        for (String ext : includeExtensions) {
            if (filename.toLowerCase().endsWith(String.format(".%s", ext.toLowerCase()))) return true;
        }
        return false;
    }

}

@@ -812,32 +812,66 @@
 acheDir = null;%0A
+        String updateText = null;%0A
         Boolean 
@@ -886,32 +886,32 @@
 wngrade = null;%0A
-
         Boolean 
@@ -2050,24 +2050,186 @@
 errors%22));%0A%0A
+                // Configure stopOnUpdateErrors%0A                if (named.containsKey(%22update-text%22))%0A                    updateText = named.get(%22update-text%22);%0A%0A
             
@@ -4060,32 +4060,95 @@
 ew%22)) continue;%0A
+                if (raw.startsWith(%22--update-text%22)) continue;%0A
                 
@@ -4906,16 +4906,82 @@
 atsNew;%0A
+        if (updateText != null) manifest.updateText = updateText;%0A
         
@@ -5675,16 +5675,17 @@
 != null)
+ 
 %7B%0A      
@@ -6287,16 +6287,17 @@
       if
+ 
 (!manife
@@ -6995,16 +6995,20 @@
 .%0A     *
+ %3Cp%3E
 %0A     * 
@@ -7088,16 +7088,23 @@
 addAll.%0A
+     *%0A
      * @

Implement --update-text option.