package jenkins;

import hudson.Extension;
import hudson.FilePath;
import hudson.remoting.ChannelProperty;

import java.io.File;
import java.util.Collections;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Maintains a bundle of {@link FilePathFilter} and implement a hook that broadcasts to all the filters.
 *
 * Accessible as channel property.
 *
 * @author Kohsuke Kawaguchi
 * @see FilePath
 * @since 1.587 / 1.580.1
 */
class FilePathFilterAggregator extends FilePathFilter {
    private final CopyOnWriteArrayList<Entry> all = new CopyOnWriteArrayList<>();

    private static class Entry implements Comparable<Entry> {
        final FilePathFilter filter;
        final double ordinal;

        private Entry(FilePathFilter filter, double ordinal) {
            this.filter = filter;
            this.ordinal = ordinal;
        }

        @Override
        public int compareTo(Entry that) {
            double result = Double.compare(this.ordinal, that.ordinal);

            if (result < 0) return -1;
            if (result > 0) return 1;

            // to create predictable order that doesn't depend on the insertion order, use class name
            // to break a tie
            return this.filter.getClass().getName().compareTo(that.filter.getClass().getName());
        }
    }

    public final void add(FilePathFilter f) {
        add(f, DEFAULT_ORDINAL);
    }

    /**
     *
     * @param ordinal
     *      Crude ordering control among {@link FilePathFilter} ala {@link Extension#ordinal()}.
     *      A filter with a bigger value will get precedence. Defaults to 0.
     */
    public void add(FilePathFilter f, double ordinal) {
        Entry e = new Entry(f, ordinal);
        int i = Collections.binarySearch(all, e, Collections.reverseOrder());
        if (i>=0)   all.add(i,e);
        else        all.add(-i-1,e);
    }

    public void remove(FilePathFilter f) {
        all.removeIf(e -> e.filter == f);
    }

    /**
     * If no filter cares, what to do?
     */
    protected boolean defaultAction() throws SecurityException {
        return false;
    }

    @Override
    public boolean read(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.read(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean mkdirs(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.mkdirs(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean write(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.write(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean symlink(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.symlink(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean create(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.create(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean delete(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.delete(f))
                return true;
        }
        return defaultAction();
    }

    @Override
    public boolean stat(File f) throws SecurityException {
        for (Entry e : all) {
            if (e.filter.stat(f))
                return true;
        }
        return defaultAction();
    }

    @Override public String toString() {
        return "FilePathFilterAggregator" + all;
    }

    static final ChannelProperty<FilePathFilterAggregator> KEY = new ChannelProperty<>(FilePathFilterAggregator.class, "FilePathFilters");

    public static final int DEFAULT_ORDINAL = 0;
}
