package com.atlassian.stash.internal.scm.git.command.foreachref;

import com.atlassian.bitbucket.repository.Ref;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;

/**
 * @since 4.6
 */
public class RefNameFilter implements Predicate<Ref> {

    private final Pattern pattern;

    private RefNameFilter(Pattern pattern) {
        this.pattern = pattern;
    }

    @Nonnull
    public static Predicate<Ref> filterBy(@Nullable String filterText) {
        if (StringUtils.isBlank(filterText)) {
            return Objects::nonNull;
        }

        return new RefNameFilter(createPattern(filterText));
    }

    /**
     * Evaluates the provided {@link Ref ref} to determine whether it should be included in the result set.
     * <p>
     * This implementation returns {@code true} if:
     * <ul>
     *     <li>The provided reference is not {@code null}</li>
     *     <li>The ref's {@link Ref#getDisplayId() display ID} starts with the provided filter text, ignoring case</li>
     * </ul>
     *
     * @param ref the reference to evaluate
     * @return {@code true} if the ref should be included in the result set; otherwise, {@code false}
     */
    @Override
    public boolean test(Ref ref) {
        return ref != null && pattern.matcher(ref.getDisplayId()).find();
    }

    /**
     * Creates a {@code Pattern} from the provided {@code filterText}. If a pattern is returned, it will perform
     * case-insensitive matching to make this approach as forgiving as possible for users.
     *
     * @param filterText filter text, or {@code null} to not filter
     * @return a compiled pattern or {@code null} to not filter
     */
    private static Pattern createPattern(@Nonnull String filterText) {
        //Note: The start of a string is considered a word boundary (as is the end, but that doesn't matter here),
        //      but for names that contain lots of (or only) non-alphanumeric things, we need all of these other
        //      bits in the pattern to match correctly.
        //Further Note: This expression is from NameMatchingFilter.NAME_MATCHING_REGEX
        return Pattern.compile("(?:\\b|^|[^\\p{L}\\p{N}])" + Pattern.quote(filterText), Pattern.CASE_INSENSITIVE);
    }
}
