/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.server.deployment.scanner;

import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * An extensible filter for VFS files.  Three arrays are maintained for checking: a prefix, suffix, and match array.  If
 * the filename starts with any of the prefixes, ends with any of the suffixes, or exactly matches any of the matches,
 * then the accepts method will return false.
 * <p/>
 * NOTE: the arrays *must* be sorted for the string matching to work, and suffixes use the 'reverseComparator'
 *
 * @author Scott.Stark@jboss.org
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 */
class ExtensibleFilter implements DirectoryStream.Filter<Path> {

    /**
     * Compare the strings backwards.  This assists in suffix comparisons.
     */
    private static final Comparator<String> reverseComparator = new Comparator<String>() {
        public int compare(String o1, String o2) {
            int idx1 = o1.length();
            int idx2 = o2.length();
            int comp = 0;
            while (comp == 0 && idx1 > 0 && idx2 > 0)
                comp = o1.charAt(--idx1) - o2.charAt(--idx2);
            return (comp == 0) ? (idx1 - idx2) : comp;
        }
    };

    /**
     * the default prefix list
     */
    private static final String[] DEFAULT_PREFIXES =
            { "#", "%", ",", ".", "_$" };

    /**
     * the default suffix list
     */
    private static final String[] DEFAULT_SUFFIXES =
            { "#", "$", "%", "~", ",v", ".BAK", ".bak", ".old", ".orig", ".tmp", ".rej", ".sh", ".txt" };

    /**
     * the default matches list
     */
    private static final String[] DEFAULT_MATCHES =
            { ".make.state", ".nse_depinfo", "CVS", "CVS.admin", "RCS", "RCSLOG", "SCCS", "TAGS", "core", "tags" };

    /**
     * The list of disallowed suffixes, sorted using reverse values
     */
    private final List<String> suffixes;

    /**
     * The sorted list of disallowed prefixes
     */
    private final List<String> prefixes;

    /**
     * The sorted list of disallowed values
     */
    private final List<String> matches;

    /**
     * Use the default values for suffixes, prefixes, and matches
     */
    public ExtensibleFilter() {
        this(DEFAULT_MATCHES, DEFAULT_PREFIXES, DEFAULT_SUFFIXES);
    }

    /**
     * Create using a custom set of matches, prefixes, and suffixes.  If any of these arrays are null, then the
     * corresponding default will be substituted.
     *
     * @param matches the matches
     * @param prefixes the prefixes
     * @param suffixes the suffixes
     */
    public ExtensibleFilter(String[] matches, String[] prefixes, String[] suffixes) {
        if (matches == null) {
            matches = DEFAULT_MATCHES;
        }
        Arrays.sort(matches);
        this.matches = new ArrayList<>(Arrays.asList(matches));
        if (prefixes == null) {
            prefixes = DEFAULT_PREFIXES;
        }
        Arrays.sort(prefixes);
        this.prefixes = new ArrayList<>(Arrays.asList(prefixes));
        if (suffixes == null) {
            suffixes = DEFAULT_SUFFIXES;
        }
        Arrays.sort(suffixes, reverseComparator);
        this.suffixes = new ArrayList<>(Arrays.asList(suffixes));
    }

    public void addPrefix(String prefix) {
        prefixes.add(prefix);
        Collections.sort(prefixes);
    }

    public void addPrefixes(String[] prefixes) {
        this.prefixes.addAll(Arrays.asList(prefixes));
        Collections.sort(this.prefixes);
    }

    public void delPrefix(String prefix) {
        prefixes.remove(prefix);
    }

    public void delPrefixes(String[] prefixes) {
        this.prefixes.removeAll(Arrays.asList(prefixes));
        Collections.sort(this.prefixes);
    }

    public void addSuffix(String suffix) {
        suffixes.add(suffix);
        Collections.sort(suffixes, reverseComparator);
    }

    public void addSuffixes(String[] suffixes) {
        this.suffixes.addAll(Arrays.asList(suffixes));
        Collections.sort(this.suffixes, reverseComparator);
    }

    public void delSuffix(String suffix) {
        suffixes.remove(suffix);
    }

    public void delSuffixes(String[] suffixes) {
        this.suffixes.removeAll(Arrays.asList(suffixes));
        Collections.sort(this.suffixes, reverseComparator);
    }

    public String[] getSuffixes() {
        String[] tmp = new String[suffixes.size()];
        suffixes.toArray(tmp);
        return tmp;
    }

    public void setSuffixes(String[] suffixes) {
        Arrays.sort(suffixes, reverseComparator);
        this.suffixes.clear();
        this.suffixes.addAll(Arrays.asList(suffixes));
    }

    public String[] getPrefixes() {
        String[] tmp = new String[prefixes.size()];
        prefixes.toArray(tmp);
        return tmp;
    }

    public void setPrefixes(String[] prefixes) {
        Arrays.sort(prefixes);
        this.prefixes.clear();
        this.prefixes.addAll(Arrays.asList(prefixes));
    }

    public String[] getMatches() {
        String[] tmp = new String[matches.size()];
        matches.toArray(tmp);
        return tmp;
    }

    public void setMatches(String[] matches) {
        Arrays.sort(matches);
        this.matches.clear();
        this.matches.addAll(Arrays.asList(matches));
    }

    /**
     * If the filename matches any string in the prefix, suffix, or matches array, return false.  Perhaps a bit of
     * overkill, but this method operates in log(n) time, where n is the size of the arrays.
     *
     * @param file The file to be tested
     *
     * @return <code>false</code> if the filename matches any of the prefixes, suffixes, or matches.
     */
    @Override
    public boolean accept(Path file) {
        String name = file.getFileName().toString();
        // check exact match
        int index = Collections.binarySearch(matches, name);
        if (index >= 0) {
            return false;
        }
        // check prefix
        index = Collections.binarySearch(prefixes, name);
        if (index >= 0) {
            return false;
        }
        if (index < -1) {
            // The < 0 index gives the first index greater than name
            int firstLessIndex = -2 - index;
            String prefix = prefixes.get(firstLessIndex);
            // If name starts with an ignored prefix ignore name
            if (name.startsWith(prefix)) {
                return false;
            }
        }
        // check suffix
        index = Collections.binarySearch(suffixes, name, reverseComparator);
        if (index >= 0) {
            return false;
        }
        if (index < -1) {
            // The < 0 index gives the first index greater than name
            int firstLessIndex = -2 - index;
            String suffix = suffixes.get(firstLessIndex);
            // If name ends with an ignored suffix ignore name
            if (name.endsWith(suffix)) {
                return false;
            }
        }
        // everything checks out.
        return true;
    }
}
