/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.io;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import net.ranides.assira.text.StringUtils;

/**
 * Domyślne implementacje interfejsu {@link FileFilter}
 * @author ranides
 */
public final class FileFilters extends ArrayList<FileFilter> {
    
    private static final long serialVersionUID = 3L;

    /**
     * Filtr akceptujący pliki i/lub katalogi na podstawie maski. Maska jest
     * zwykłym tekstem, w którym mogą występować symbole wieloznaczne * oraz ?.
     * Plik/katalog jest akceptowany przez filtr, jeśli pasuje do co najmniej 
     * jednej maski "include" oraz nie pasuje do żadnej maski "exclude".
     */
    public static class Wildcard extends FileFilter {

        private Set<String> include = new HashSet<>();
        private Set<String> exclude = new HashSet<>();

        /**
         * Tworzy nowy filtr z domyślnymi flagami: akceptujący dowolny katalog lub
         * plik spełniający kryteria. 
         */
        public Wildcard() {
            super();
        }

        /**
         * Tworzy nowy filtr z podanymi flagami.
         * @param mode
         */
        public Wildcard(Set<AcceptMode> mode) {
            super(mode);
        }

        /**
         * Tworzy nowy filtr z podanym opisem oraz flagami.
         * @param description
         * @param mode
         */
        public Wildcard(String description, Set<AcceptMode> mode) {
            super(description, mode);
        }

        /**
         * Dodaje do filtru jeden lub więcej masek akceptujących obiekt. Poszczególne 
         * maski są rozdzielane za pomocą średnika. 
         * @param wildcards
         * @return
         */
        public Wildcard include(String wildcards) {
            for(String wildcard : wildcards.split(";")) {
                include.add(w2p(wildcard));
            }
            return this;
        }
        
        /**
         * Dodaje do filtru jeden lub więcej masek odrzucających obiekt. Poszczególne 
         * maski są rozdzielane za pomocą średnika. 
         * @param wildcards
         * @return
         */
        public Wildcard exclude(String wildcards) {
            for(String wildcard : wildcards.split(";")) {
                exclude.add(w2p(wildcard));
            }
            return this;
        }
        
        private static String w2p(String wildcard) {
            return StringUtils.escapeRegex( StringUtils.replace(wildcard, '\\', '/') ).replace("\\*", ".*");
        }

        @Override
        public boolean acceptFile(File file) {
            for(String wildcard : exclude) { 
                if(StringUtils.replace(file.getPath(),'\\','/').matches(wildcard)) { return false; } 
            }
            for(String wildcard : include) { 
                if(StringUtils.replace(file.getPath(),'\\','/').matches(wildcard)) { return true; } 
            }
            return include.isEmpty();
        }
        
        //<editor-fold defaultstate="collapsed" desc=" properties ">

        /**
         * Zwraca zbiór wszystkich masek, które odrzucają obiekt. Modyfikacja 
         * zbioru jest dozwolona i wpływa na działanie filtru. Zwracana kolekcja
         * nie jest domyślnie synchronizowana, chyba, że przekazano synchronizowaną
         * kolekcję za pomocą metody {@link #setExclude}
         * @return
         */
        public Set<String> getExclude() {
            return exclude;
        }

        /**
         * Ustawia zbiór wszystkich masek, które odrzucają obiekt. Filtr nie kopiuje
         * elementów, ale przechowuje referencję do kolekcji. Z tego powodu 
         * modyfikacja przekazanego zbioru wpływa na działanie filtru. 
         * @param exclude
         */
        public void setExclude(Set<String> exclude) {
            this.exclude = exclude;
        }

        /**
         * Zwraca zbiór wszystkich masek, które akceptują obiekt. Modyfikacja 
         * zbioru jest dozwolona i wpływa na działanie filtru. Zwracana kolekcja
         * nie jest domyślnie synchronizowana, chyba, że przekazano synchronizowaną
         * kolekcję za pomocą metody {@link #setInclude}
         * @return
         */
        public Set<String> getInclude() {
            return include;
        }

        /**
         * Ustawia zbiór wszystkich masek, które akceptują obiekt. Filtr nie kopiuje
         * elementów, ale przechowuje referencję do kolekcji. Z tego powodu 
         * modyfikacja przekazanego zbioru wpływa na działanie filtru. 
         * @param include
         */
        public void setInclude(Set<String> include) {
            this.include = include;
        }
        
        //</editor-fold>
        
    }
    
    /**
     *  Filtr akceptujący pliki i/lub katalogi na podstawie wyrażenia regularnego.
     */
    public static class Regex extends FileFilter {
    
        private Set<Pattern> include = new HashSet<>();
        private Set<Pattern> exclude = new HashSet<>();
        
        /**
         * Tworzy nowy filtr z domyślnymi flagami: akceptujący dowolny katalog lub
         * plik spełniający kryteria.
         */
        public Regex() {
            super();
        }

        /**
         * Tworzy nowy filtr z podanymi flagami.
         * @param mode
         */
        public Regex(Set<AcceptMode> mode) {
            super(mode);
        }

        /**
         * Tworzy nowy filtr z podanym opisem oraz flagami.
         * @param description
         * @param mode
         */
        public Regex(String description, Set<AcceptMode> mode) {
            super(description, mode);
        }

        /**
         * Dodaje do filtru wyrażenie regularne akceptujące obiekt
         * @param pattern
         * @return
         */
        public Regex include(Pattern pattern) {
            include.add(pattern);
            return this;
        }
        
        /**
         * Dodaje do filtru wyrażenie regularne odrzucające obiekt
         * @param pattern
         * @return
         */
        public Regex exclude(Pattern pattern) {
            exclude.add(pattern);
            return this;
        }
        
        /**
         * Kompiluje podany tekst i dodaje do filtru skompilowane wyrażenie regularne akceptujące
         * @param pattern
         * @return
         */
        public Regex include(String pattern) {
            include.add(Pattern.compile(pattern));
            return this;
        }
        
        /**
         * Kompiluje podany tekst i dodaje do filtru skompilowane wyrażenie regularne odrzucające
         * @param pattern
         * @return
         */
        public Regex exclude(String pattern) {
            exclude.add(Pattern.compile(pattern));
            return this;
        }
        
        @Override
        public boolean acceptFile(File file) {
            for(Pattern pattern : exclude) {
                if( pattern.matcher(StringUtils.replace(file.getPath(),'\\','/')).find() ) { return false; }
            }
            for(Pattern pattern : include) {
                if( pattern.matcher(StringUtils.replace(file.getPath(),'\\','/')).find() ) { return true; }
            }
            return include.isEmpty();
        }
        
        //<editor-fold defaultstate="collapsed" desc=" properties ">
        
        /**
         * Zwraca zbiór wszystkich wyrażeń, które odrzucają obiekt. Modyfikacja 
         * zbioru jest dozwolona i wpływa na działanie filtru. Zwracana kolekcja
         * nie jest domyślnie synchronizowana, chyba, że przekazano synchronizowaną
         * kolekcję za pomocą metody {@link #setExclude}
         * @return
         */
        public Set<Pattern> getExclude() {
            return exclude;
        }

        /**
         * Ustawia zbiór wszystkich wyrażeń, które odrzucają obiekt. Filtr nie kopiuje
         * elementów, ale przechowuje referencję do kolekcji. Z tego powodu 
         * modyfikacja przekazanego zbioru wpływa na działanie filtru. 
         * @param exclude
         */
        public void setExclude(Set<Pattern> exclude) {
            this.exclude = exclude;
        }

        /**
         * Zwraca zbiór wszystkich wyrażeń, które akceptują obiekt. Modyfikacja 
         * zbioru jest dozwolona i wpływa na działanie filtru. Zwracana kolekcja
         * nie jest domyślnie synchronizowana, chyba, że przekazano synchronizowaną
         * kolekcję za pomocą metody {@link #setInclude}
         * @return
         */
        public Set<Pattern> getInclude() {
            return include;
        }

        /**
         * Ustawia zbiór wszystkich wyrażeń, które akceptują obiekt. Filtr nie kopiuje
         * elementów, ale przechowuje referencję do kolekcji. Z tego powodu 
         * modyfikacja przekazanego zbioru wpływa na działanie filtru. 
         * @param include
         */
        public void setInclude(Set<Pattern> include) {
            this.include = include;
        }
        
        //</editor-fold>

        
    }
    
    
}
