package org.metaborg.util.resource;

import java.util.Collection;
import java.util.regex.Pattern;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSelectInfo;
import org.apache.commons.vfs2.FileSelector;
import org.apache.commons.vfs2.FileSystemException;
import org.metaborg.util.iterators.Iterables2;

import com.google.common.collect.Lists;

public class FileSelectorUtils {
    public static FileSelector and(Iterable<FileSelector> selectors) {
        return new AndFileSelector(Iterables2.from(selectors));
    }

    public static FileSelector and(FileSelector... selectors) {
        return and(Iterables2.from(selectors));
    }


    public static FileSelector or(Iterable<FileSelector> selectors) {
        return new OrFileSelector(selectors);
    }

    public static FileSelector or(FileSelector... selectors) {
        return or(Iterables2.from(selectors));
    }


    public static FileSelector not(FileSelector selector) {
        return new NotFileSelector(selector);
    }


    public static FileSelector contains(String contains) {
        return new ContainsFileSelector(contains);
    }

    public static FileSelector extensions(Iterable<String> extensions) {
        return new ExtensionFileSelector(extensions);
    }

    public static FileSelector extensions(String... extensions) {
        return new ExtensionFileSelector(Iterables2.from(extensions));
    }

    public static FileSelector extension(String extension) {
        return new ExtensionFileSelector(extension);
    }


    public static FileSelector regex(String regex) {
        return new PatternFileSelector(regex);
    }

    public static FileSelector regex(String regex, int flags) {
        return new PatternFileSelector(regex, flags);
    }

    public static FileSelector regex(Pattern pattern) {
        return new PatternFileSelector(pattern);
    }


    public static FileSelectInfo info(FileObject file, FileObject base, int depth) {
        return new DefaultFileSelectInfo(base, file, depth);
    }

    public static FileSelectInfo info(FileObject file, FileObject base) {
        return info(base, file, -1);
    }

    public static FileSelectInfo info(FileObject file) throws FileSystemException {
        return info(file, file.getParent());
    }


    public static boolean include(FileSelector selector, FileObject resource, FileObject base)
        throws FileSystemException {
        int depth = depth(resource, base);
        FileObject current = resource;
        FileSelectInfo info = info(current, base, depth);

        try {
            if(!selector.includeFile(info)) {
                return false;
            }

            do {
                switch(current.getType()) {
                    case FILE:
                        if(!selector.includeFile(info)) {
                            return false;
                        }
                        break;
                    case FOLDER:
                        if(!selector.traverseDescendents(info)) {
                            return false;
                        }
                        break;
                    case FILE_OR_FOLDER:
                    case IMAGINARY:
                        if(!selector.includeFile(info) || !selector.traverseDescendents(info)) {
                            return false;
                        }
                        break;
                }

                --depth;
                current = current.getParent();
                if(current == null) {
                    throw new FileSystemException("vfs.provider/find-files.error", resource);
                }
                info = info(current, base, depth);
            } while(!current.getName().equals(base.getName()));
        } catch(Exception e) {
            throw new FileSystemException("vfs.provider/find-files.error", resource, e);
        }

        return true;
    }

    public static Iterable<FileObject> filter(FileSelector selector, Iterable<FileObject> resources, FileObject base)
        throws FileSystemException {
        final Collection<FileObject> filteredResources = Lists.newLinkedList();
        for(FileObject resource : resources) {
            if(include(selector, resource, base)) {
                filteredResources.add(resource);
            }
        }
        return filteredResources;
    }

    private static int depth(FileObject resource, FileObject base) {
        int depth = 0;
        FileObject current = resource;
        while(!current.getName().equals(base.getName())) {
            ++depth;
            try {
                current = current.getParent();
            } catch(FileSystemException e) {
                throw new RuntimeException("Cannot calculate depth", e);
            }
            if(current == null) {
                throw new RuntimeException("Cannot calculate depth, resource is not part of base");
            }
        }
        return depth;
    }
}

@@ -2591,281 +2591,145 @@
     
-if(!selector.includeFile(info)) %7B%0A                return false;%0A            %7D%0A%0A            do %7B%0A                switch(current.getType()) %7B%0A                    case FILE:%0A                        if(!selector.includeFile(info)) %7B%0A                            return false;%0A  
+// Check only once if resource equals the base%0A            if(depth == 0) %7B%0A                return includeOne(selector, resource, info);%0A
     
@@ -2732,35 +2732,26 @@
             
-          
 %7D
+%0A
 %0A           
@@ -2755,72 +2755,60 @@
     
-            break;%0A                    case FOLDER:%0A            
+// If file is excluded beforehand, stop immediately%0A
     
@@ -2828,35 +2828,27 @@
 elector.
-traverseDescendents
+includeFile
 (info)) 
@@ -2861,36 +2861,24 @@
             
-            
 return false
@@ -2895,218 +2895,177 @@
     
-            %7D%0A                        break;%0A                    case FILE_OR_FOLDER:%0A                    case IMAGINARY:%0A                        if(!selector.includeFile(info) %7C%7C !selector.traverseDescendents(
+%7D%0A%0A            // Go over the file and its ancestors to check if it should be excluded%0A            while(depth %3E= 0) %7B%0A                if(!includeOne(selector, current, 
 info
@@ -3085,32 +3085,24 @@
             
-        
 return false
@@ -3123,92 +3123,10 @@
     
-        %7D%0A                        break;%0A                %7D%0A%0A                --depth;
+%7D%0A
 %0A   
@@ -3311,32 +3311,57 @@
               %7D%0A
+                --depth;%0A
                 
@@ -3412,58 +3412,8 @@
    %7D
- while(!current.getName().equals(base.getName()));
 %0A   
@@ -3560,32 +3560,788 @@
 rn true;%0A    %7D%0A%0A
+    public static boolean includeOne(FileSelector selector, FileObject resource, FileSelectInfo info)%0A        throws FileSystemException, Exception %7B%0A        switch(resource.getType()) %7B%0A            case FILE:%0A                if(!selector.includeFile(info)) %7B%0A                    return false;%0A                %7D%0A                break;%0A            case FOLDER:%0A                if(!selector.traverseDescendents(info)) %7B%0A                    return false;%0A                %7D%0A                break;%0A            case FILE_OR_FOLDER:%0A            case IMAGINARY:%0A                if(!selector.includeFile(info) %7C%7C !selector.traverseDescendents(info)) %7B%0A                    return false;%0A                %7D%0A                break;%0A        %7D%0A        return true;%0A    %7D%0A%0A
     public stati

Fix error when checking to include root directory