package io.quarkus.deployment.builditem;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import io.quarkus.bootstrap.model.PathsCollection;
import io.quarkus.builder.item.SimpleBuildItem;

public final class ArchiveRootBuildItem extends SimpleBuildItem {

    public static class Builder {

        private List<Path> archiveRoots = new ArrayList<>();
        private Collection<Path> excludedFromIndexing;

        private Builder() {
        }

        public Builder addArchiveRoot(Path root) {
            this.archiveRoots.add(root);
            return this;
        }

        public Builder addArchiveRoots(PathsCollection paths) {
            paths.forEach(archiveRoots::add);
            return this;
        }

        public Builder setExcludedFromIndexing(Collection<Path> excludedFromIndexing) {
            this.excludedFromIndexing = excludedFromIndexing;
            return this;
        }

        @Deprecated
        public Builder setArchiveLocation(Path archiveLocation) {
            this.archiveRoots.clear();
            this.archiveRoots.add(archiveLocation);
            return this;
        }

        public ArchiveRootBuildItem build(QuarkusBuildCloseablesBuildItem buildCloseables) throws IOException {
            return new ArchiveRootBuildItem(this, buildCloseables);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    private final Path archiveRoot;
    private final Collection<Path> excludedFromIndexing;
    private final PathsCollection rootDirs;
    private final PathsCollection paths;

    public ArchiveRootBuildItem(Path appClassesDir) {
        this(appClassesDir, appClassesDir);
    }

    @Deprecated
    public ArchiveRootBuildItem(Path archiveLocation, Path archiveRoot) {
        this(archiveLocation, archiveRoot, Collections.emptySet());
    }

    private ArchiveRootBuildItem(Path archiveLocation, Path archiveRoot, Collection<Path> excludedFromIndexing) {
        if (!Files.isDirectory(archiveRoot)) {
            throw new IllegalArgumentException(archiveRoot + " does not point to the application output directory");
        }
        this.rootDirs = PathsCollection.of(archiveRoot);
        this.paths = PathsCollection.of(archiveLocation);
        this.archiveRoot = archiveRoot;
        this.excludedFromIndexing = excludedFromIndexing;
    }

    private ArchiveRootBuildItem(Builder builder, QuarkusBuildCloseablesBuildItem buildCloseables) throws IOException {
        this.excludedFromIndexing = builder.excludedFromIndexing;
        final PathsCollection.Builder rootDirs = PathsCollection.builder();
        final PathsCollection.Builder paths = PathsCollection.builder();
        for (Path root : builder.archiveRoots) {
            paths.add(root);
            if (Files.isDirectory(root)) {
                rootDirs.add(root);
            } else {
                final FileSystem fs = buildCloseables.add(FileSystems.newFileSystem(root, null));
                fs.getRootDirectories().forEach(rootDirs::add);
            }
        }
        this.rootDirs = rootDirs.build();
        this.paths = paths.build();
        this.archiveRoot = this.rootDirs.iterator().next();
    }

    /**
     * If this archive is a jar file it will return the path to the jar file on the file system,
     * otherwise it will return the directory that this corresponds to.
     *
     * @deprecated in favor of {@link #getPaths()}
     */
    @Deprecated
    public Path getArchiveLocation() {
        final Iterator<Path> i = paths.iterator();
        Path last = i.next();
        while (i.hasNext()) {
            last = i.next();
        }
        return last;
    }

    /**
     *
     * Returns a path representing the archive root. Note that if this is a jar archive this is not the path to the
     * jar, but rather a path to the root of the mounted {@link com.sun.nio.zipfs.ZipFileSystem}
     *
     * @return The archive root.
     * @deprecated in favor of {@link #getRootDirs()}
     */
    @Deprecated
    public Path getArchiveRoot() {
        return archiveRoot;
    }

    /**
     * Collection of path representing the archive's root directories. If there is a JAR among the paths
     * (returned by {@link #getPaths()} this method will return the path to the root of the mounted
     * {@link java.nio.file.ZipFileSystem}
     * instead.
     *
     * @return Collection of path representing the archive's root directories.
     */
    public PathsCollection getRootDirs() {
        return rootDirs;
    }

    /**
     * Collection of paths that collectively constitute the application archive's content.
     *
     * @return collection of paths that collectively constitute the application archive content.
     */
    public PathsCollection getPaths() {
        return paths;
    }

    public boolean isExcludedFromIndexing(Path p) {
        return excludedFromIndexing.contains(p);
    }
}

@@ -2761,32 +2761,83 @@
 edFromIndexing;%0A
+        if (!builder.archiveRoots.isEmpty()) %7B%0A    
         final Pa
@@ -2896,32 +2896,36 @@
 lder();%0A        
+    
 final PathsColle
@@ -2981,16 +2981,20 @@
         
+   
+ 
 for (Pat
@@ -3034,24 +3034,28 @@
             
+    
 paths.add(ro
@@ -3067,24 +3067,28 @@
             
+   
+ 
 if (Files.is
@@ -3122,16 +3122,20 @@
         
+    
 rootDirs
@@ -3158,16 +3158,20 @@
         
+  
+  
 %7D else %7B
@@ -3171,16 +3171,20 @@
  else %7B%0A
+    
         
@@ -3289,16 +3289,20 @@
         
+    
 fs.getRo
@@ -3333,32 +3333,36 @@
 rootDirs::add);%0A
+    
             %7D%0A  
@@ -3359,34 +3359,42 @@
       %7D%0A        
-%7D%0A
+    %7D%0A    
         this.roo
@@ -3419,32 +3419,36 @@
 uild();%0A        
+    
 this.paths = pat
@@ -3459,32 +3459,36 @@
 uild();%0A        
+  
+  
 this.archiveRoot
@@ -3519,24 +3519,151 @@
 r().next();%0A
+        %7D else %7B%0A            this.paths = this.rootDirs = PathsCollection.of();%0A            this.archiveRoot = null;%0A        %7D%0A
     %7D%0A%0A    /

Allow no source paths in ArchiveRootBuildItem