/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.util.filter;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Utility class for working with Filters. Contains builder style methods, apply
 * methods, as well as mechanisms for adapting Filters and FilenameFilters.
 */
public class Filters {

    private Filters() { }

    /**
     * Filter a given Collection.
     *
     * @param <T>
     *            Type of the Collection.
     * @param filter
     *            A Filter upon the Type of objects in the Collection.
     * @param collection
     *            The Collection to filter.
     * @return A List containing only those objects for which the Filter
     *         returned <code>true</code>.
     */
    public static <T> List<T> filter(Filter<T> filter, Collection<T> collection) {
        List<T> list = new ArrayList<>();
        for (T obj : collection) {
            if (filter.filter(obj)) {
                list.add(obj);
            }
        }
        return list;
    }

    /**
     * Get a File Filter for files with the given extensions, ignoring case.
     *
     * @param extensions
     *            The extensions to filter.
     * @return A File Filter.
     */
    public static Filter<File> getFileExtensionFilter(String... extensions) {
        return new FileExtensionFilter(extensions);
    }

    /**
     * Get a File Filter for directories.
     *
     * @return A File Filter.
     */
    public static Filter<File> getDirectoryFilter() {
        return DirectoryFilter.INSTANCE;
    }

    /**
     * Get a File Filter for directories or for files with the given extensions,
     * ignoring case.
     *
     * @param extensions
     *            The extensions to filter.
     * @return A File Filter.
     */
    public static Filter<File> getFileExtensionOrDirectoryFilter(String... extensions) {
        return new OrFilter<>(getFileExtensionFilter(extensions), getDirectoryFilter());
    }

    /**
     * Given a String Filter, expose as a File Filter. The File paths are
     * normalized to a standard pattern using <code>/</code> as a path separator
     * which can be used cross platform easily in a regular expression based
     * String Filter.
     *
     * @param filter
     *            A String Filter.
     * @return A File Filter.
     */
    public static Filter<File> toNormalizedFileFilter(final Filter<String> filter) {
        return new Filter<File>() {
            @Override
            public boolean filter(File file) {
                String path = file.getPath();
                path = path.replace('\\', '/');
                return filter.filter(path);
            }

            @Override
            public String toString() {
                return filter.toString();
            }
        };
    }

    /**
     * Given a String Filter, expose as a Filter on another type. The
     * <code>toString()</code> method is called on the objects of the other type
     * and delegated to the String Filter.
     *
     * @param <T>
     *            The desired type.
     * @param filter
     *            The existing String Filter.
     * @return A Filter on the desired type.
     */
    public static <T> Filter<T> fromStringFilter(final Filter<String> filter) {
        return new Filter<T>() {
            @Override
            public boolean filter(T obj) {
                return filter.filter(obj.toString());
            }

            @Override
            public String toString() {
                return filter.toString();
            }
        };
    }

    /**
     * Given a File Filter, expose as a FilenameFilter.
     *
     * @param filter
     *            The File Filter.
     * @return A FilenameFilter.
     */
    public static FilenameFilter toFilenameFilter(final Filter<File> filter) {
        return new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return filter.filter(new File(dir, name));
            }

            @Override
            public String toString() {
                return filter.toString();
            }
        };
    }

    /**
     * Given a FilenameFilter, expose as a File Filter.
     *
     * @param filter
     *            The FilenameFilter.
     * @return A File Filter.
     */
    public static Filter<File> toFileFilter(final FilenameFilter filter) {
        return new Filter<File>() {
            @Override
            public boolean filter(File file) {
                return filter.accept(file.getParentFile(), file.getName());
            }

            @Override
            public String toString() {
                return filter.toString();
            }
        };
    }

    /**
     * Construct a String Filter using set of include and exclude regular
     * expressions. If there are no include regular expressions provide, then a
     * regular expression is added which matches every String by default. A
     * String is included as long as it matches an include regular expression
     * and does not match an exclude regular expression.
     * <p>
     * In other words, exclude patterns override include patterns.
     *
     * @param includeRegexes
     *            The include regular expressions. May be <code>null</code>.
     * @param excludeRegexes
     *            The exclude regular expressions. May be <code>null</code>.
     * @return A String Filter.
     */
    public static Filter<String> buildRegexFilterExcludeOverInclude(List<String> includeRegexes,
            List<String> excludeRegexes) {
        OrFilter<String> includeFilter = new OrFilter<>();
        if (includeRegexes == null || includeRegexes.isEmpty()) {
            includeFilter.addFilter(new RegexStringFilter(".*"));
        } else {
            for (String includeRegex : includeRegexes) {
                includeFilter.addFilter(new RegexStringFilter(includeRegex));
            }
        }

        OrFilter<String> excludeFilter = new OrFilter<>();
        if (excludeRegexes != null) {
            for (String excludeRegex : excludeRegexes) {
                excludeFilter.addFilter(new RegexStringFilter(excludeRegex));
            }
        }

        return new AndFilter<>(includeFilter, new NotFilter<>(excludeFilter));
    }

    /**
     * Construct a String Filter using set of include and exclude regular
     * expressions. If there are no include regular expressions provide, then a
     * regular expression is added which matches every String by default. A
     * String is included as long as the case that there is an include which
     * matches or there is not an exclude which matches.
     * <p>
     * In other words, include patterns override exclude patterns.
     *
     * @param includeRegexes
     *            The include regular expressions. May be <code>null</code>.
     * @param excludeRegexes
     *            The exclude regular expressions. May be <code>null</code>.
     * @return A String Filter.
     */
    public static Filter<String> buildRegexFilterIncludeOverExclude(List<String> includeRegexes,
            List<String> excludeRegexes) {
        OrFilter<String> includeFilter = new OrFilter<>();
        if (includeRegexes != null) {
            for (String includeRegex : includeRegexes) {
                includeFilter.addFilter(new RegexStringFilter(includeRegex));
            }
        }

        OrFilter<String> excludeFilter = new OrFilter<>();
        if (excludeRegexes != null) {
            for (String excludeRegex : excludeRegexes) {
                excludeFilter.addFilter(new RegexStringFilter(excludeRegex));
            }
        }

        return new OrFilter<>(includeFilter, new NotFilter<>(excludeFilter));
    }
}
