/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.brooklyn.util.text;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class StringPredicates {

    /** predicate form of {@link Strings#isBlank(CharSequence)} */
    public static <T extends CharSequence> Predicate<T> isBlank() {
        return new IsBlank<T>();
    }

    private static final class IsBlank<T extends CharSequence> implements Predicate<T> {
        @Override
        public boolean apply(@Nullable CharSequence input) {
            return Strings.isBlank(input);
        }

        @Override
        public String toString() {
            return "isBlank()";
        }
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<CharSequence> isBlankOld() {
        return new Predicate<CharSequence>() {
            @Override
            public boolean apply(@Nullable CharSequence input) {
                return Strings.isBlank(input);
            }
            @Override
            public String toString() {
                return "isBlank";
            }
        };
    }


    /** Tests if object is non-null and not a blank string.
     * <p>
     * Predicate form of {@link Strings#isNonBlank(CharSequence)} also accepting objects non-null, for convenience */
    public static <T> Predicate<T> isNonBlank() {
        return new IsNonBlank<T>();
    }

    private static final class IsNonBlank<T> implements Predicate<T> {
        @Override
        public boolean apply(@Nullable Object input) {
            if (input==null) return false;
            if (!(input instanceof CharSequence)) return true;
            return Strings.isNonBlank((CharSequence)input);
        }

        @Override
        public String toString() {
            return "isNonBlank()";
        }
    }
    
    // -----------------
    
    public static <T extends CharSequence> Predicate<T> containsLiteralIgnoreCase(final String fragment) {
        return new ContainsLiteralIgnoreCase<T>(fragment);
    }

    private static final class ContainsLiteralIgnoreCase<T extends CharSequence> implements Predicate<T> {
        private final String fragment;

        private ContainsLiteralIgnoreCase(String fragment) {
            this.fragment = fragment;
        }

        @Override
        public boolean apply(@Nullable CharSequence input) {
            return Strings.containsLiteralIgnoreCase(input, fragment);
        }

        @Override
        public String toString() {
            return "containsLiteralCaseInsensitive("+JavaStringEscapes.wrapJavaString(fragment)+")";
        }
    }

    public static <T extends CharSequence> Predicate<T> containsLiteral(final String fragment) {
        return new ContainsLiteral<T>(fragment);
    }
    
    private static final class ContainsLiteral<T extends CharSequence> implements Predicate<T> {
        private final String fragment;

        private ContainsLiteral(String fragment) {
            this.fragment = fragment;
        }

        @Override
        public boolean apply(@Nullable CharSequence input) {
            return Strings.containsLiteral(input, fragment);
        }

        @Override
        public String toString() {
            return "containsLiteral("+JavaStringEscapes.wrapJavaString(fragment)+")";
        }
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<CharSequence> containsLiteralCaseInsensitiveOld(final String fragment) {
        return new Predicate<CharSequence>() {
            @Override
            public boolean apply(@Nullable CharSequence input) {
                return Strings.containsLiteralIgnoreCase(input, fragment);
            }
            @Override
            public String toString() {
                return "containsLiteralCaseInsensitive("+fragment+")";
            }
        };
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<CharSequence> containsLiteralOld(final String fragment) {
        return new Predicate<CharSequence>() {
            @Override
            public boolean apply(@Nullable CharSequence input) {
                return Strings.containsLiteral(input, fragment);
            }
            @Override
            public String toString() {
                return "containsLiteral("+fragment+")";
            }
        };
    }
    
    // -----------------
    
    public static <T extends CharSequence> Predicate<T> containsAllLiterals(final String... fragments) {
        List<Predicate<CharSequence>> fragmentPredicates = Lists.newArrayList();
        for (String fragment : fragments) {
            fragmentPredicates.add(containsLiteral(fragment));
        }
        return Predicates.and(fragmentPredicates);
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<CharSequence> containsAllLiteralsOld(final String... fragments) {
        return Predicates.and(Iterables.transform(Arrays.asList(fragments), new Function<String,Predicate<CharSequence>>() {
            @Override
            public Predicate<CharSequence> apply(String input) {
                return containsLiteral(input);
            }
        }));
    }
    
    // -----------------

    public static Predicate<CharSequence> containsRegex(final String regex) {
        // "Pattern" ... what a bad name :)
        return Predicates.containsPattern(regex);
    }

    // -----------------
    
    public static <T extends CharSequence> Predicate<T> startsWith(final String prefix) {
        return new StartsWith<T>(prefix);
    }

    private static final class StartsWith<T extends CharSequence> implements Predicate<T> {
        private final String prefix;
        private StartsWith(String prefix) {
            this.prefix = prefix;
        }
        @Override
        public boolean apply(CharSequence input) {
            return (input != null) && input.toString().startsWith(prefix);
        }
        @Override
        public String toString() {
            return "startsWith("+JavaStringEscapes.wrapJavaString(prefix)+")";
        }
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<CharSequence> startsWithOld(final String prefix) {
        return new Predicate<CharSequence>() {
            @Override
            public boolean apply(CharSequence input) {
                return (input != null) && input.toString().startsWith(prefix);
            }
        };
    }

    // -----------------
    
    /** true if the object *is* a {@link CharSequence} starting with the given prefix */
    public static Predicate<Object> isStringStartingWith(final String prefix) {
        return Predicates.<Object>and(Predicates.instanceOf(CharSequence.class),
            Predicates.compose(startsWith(prefix), StringFunctions.toStringFunction()));
    }

    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
    @SuppressWarnings("unused") @Deprecated 
    private static Predicate<Object> isStringStartingWithOld(final String prefix) {
        return new Predicate<Object>() {
            @Override
            public boolean apply(Object input) {
                return (input instanceof CharSequence) && input.toString().startsWith(prefix);
            }
        };
    }

    // ---------------
    
    public static <T> Predicate<T> equalToAny(Iterable<T> vals) {
        return new EqualToAny<T>(vals);
    }

    private static class EqualToAny<T> implements Predicate<T>, Serializable {
        private static final long serialVersionUID = 6209304291945204422L;
        private final Set<T> vals;
        
        public EqualToAny(Iterable<? extends T> vals) {
            this.vals = MutableSet.copyOf(vals); // so allows nulls
        }
        @Override
        public boolean apply(T input) {
            return vals.contains(input);
        }
        @Override
        public String toString() {
            return "equalToAny("+vals+")";
        }
    }

    // -----------
    
    public static <T extends CharSequence> Predicate<T> matchesRegex(final String regex) {
        return new MatchesRegex<T>(regex);
    }

    protected static class MatchesRegex<T extends CharSequence> implements Predicate<T> {
        protected final String regex;
        protected MatchesRegex(String regex) {
            this.regex = regex;
        }
        @Override
        public boolean apply(CharSequence input) {
            return (input != null) && input.toString().matches(regex);
        }
        @Override
        public String toString() {
            return "matchesRegex("+JavaStringEscapes.wrapJavaString(regex)+")";
        }
    }
    
    public static <T extends CharSequence> Predicate<T> matchesGlob(final String glob) {
        return new MatchesGlob<T>(glob);
    }

    protected static class MatchesGlob<T extends CharSequence> implements Predicate<T> {
        protected final String glob;
        protected MatchesGlob(String glob) {
            this.glob = glob;
        }
        @Override
        public boolean apply(CharSequence input) {
            return (input != null) && WildcardGlobs.isGlobMatched(glob, input.toString());
        }
        @Override
        public String toString() {
            return "matchesGlob("+JavaStringEscapes.wrapJavaString(glob)+")";
        }
    }

}
