/*
 *   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
 *
 *     https://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.directory.ldap.client.api.search;

import org.apache.directory.api.i18n.I18n;

/**
 * A builder for constructing well formed search filters according to
 * <a href="https://tools.ietf.org/html/rfc4515.html">RFC 4515</a>.  This 
 * builder is most convenient when you use static imports.  For example:
 * <pre>
 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;
 * 
 * ...
 * 
 *         String filter = 
 *                 or(
 *                     and( 
 *                         equal( "givenName", "kermit" ), 
 *                         equal( "sn", "the frog" ) ),
 *                     and( 
 *                         equal( "givenName", "miss" ), 
 *                         equal( "sn", "piggy" ) ) )
 *                 .toString()
 * </pre>
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class FilterBuilder
{
    /** The built filter */
    /* No qualifier */ Filter filter;


    /**
     * A private constructor that creates a new instance of a FilterBuilder
     * containing a given filter.
     * 
     * @param filter The filter
     */
    /* No qualifier*/ FilterBuilder( Filter filter )
    {
        this.filter = filter;
    }


    /**
     * Returns a new FilterBuilder that will <code>&amp;</code> together all of the 
     * supplied filters.  For example:
     * 
     * <pre>
     * and( equal( "givenName", "kermit" ), equal( "sn", "the frog" ) ).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (&amp;(givenName=kermit)(sn=the frog))
     * </pre>
     * 
     * Which would match all entries with a given name of <code>kermit</code>
     * and a surname <code>the frog</code>.
     *
     * @param filters The filters to and together
     * @return A new FilterBuilder
     */
    public static FilterBuilder and( FilterBuilder... filters )
    {
        SetOfFiltersFilter filter = SetOfFiltersFilter.and();

        for ( FilterBuilder builder : filters )
        {
            filter.add( builder.filter );
        }

        return new FilterBuilder( filter );
    }


    /**
     * Returns a new FilterBuilder for testing the approximate equality of an 
     * attribute. For example:
     * 
     * <pre>
     * approximatelyEqual( "l", "san fransico" ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (l~=san fransico)
     * </pre>
     * 
     * Which <i>MIGHT</i> match results whose locality is 
     * <code>San Francisco</code>.  The matching rule used to apply this filter
     * is dependent on the server implementation.
     *
     * @param attribute The attribute 
     * @param value The value
     * @return A new FilterBuilder
     */
    public static FilterBuilder approximatelyEqual( String attribute, String value )
    {
        return new FilterBuilder( AttributeValueAssertionFilter.approximatelyEqual( attribute, value ) );
    }


    /**
     * Returns a new FilterBuilder for testing equality of an attribute. For 
     * example:
     * 
     * <pre>
     * equal( "cn", "Kermit The Frog" ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (cn&gt;=Kermit The Frog)
     * </pre>
     * 
     * Which would match entries with the common name 
     * <code>Kermit The Frog</code>.
     *
     * @param attribute The attribute 
     * @param value The value
     * @return A new FilterBuilder
     */
    public static FilterBuilder equal( String attribute, String value )
    {
        return new FilterBuilder( AttributeValueAssertionFilter.equal( attribute, value ) );
    }


    /**
     * Creates an extensible match filter by calling 
     * {@link #extensible(String, String) extensible(null, value)}.
     *
     * @param value The value to test for
     * @return A new MatchingRuleAssertionFilterBuilder
     */
    public static MatchingRuleAssertionFilterBuilder extensible( String value )
    {
        return new MatchingRuleAssertionFilterBuilder( null, value );
    }


    /**
     * Creates an extensible match filter.  This filter can be used to specify
     * that dn attributes should be included in the match, which matcher to 
     * use, or that all attributes that support a specific matcher will be
     * checked.  For example:
     * 
     * <pre>
     * extensible( "sn", "Barney Rubble" )
     *     .useDnAttributes()
     *     .setMatchingRule( "2.4.6.8.10" )
     *     .toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (sn:dn:2.4.6.8.10:=Barney Rubble)
     * </pre>
     * 
     * Not that the specialized filter builder that is returned <b>IS</b> a 
     * FilterBuilder so it can be chained with other filters.  For example:
     * 
     * <pre>
     * and(
     *     extensible( "sn", "Rubble" )
     *         .useDnAttributes()
     *         .setMatchingRule( "2.4.6.8.10" ),
     *     equal( "givenName", "Barney" ) )
     *     .toString();
     * </pre>
     *
     * @param attribute The attribute to test
     * @param value The value to test for
     * @return A new MatchingRuleAssertionFilterBuilder
     */
    public static MatchingRuleAssertionFilterBuilder extensible( String attribute, String value )
    {
        return new MatchingRuleAssertionFilterBuilder( attribute, value );
    }
    
    
    /**
     * Returns a new FilterBuilder for testing lexicographical greater than.  
     * For example:
     * 
     * <pre>
     * greaterThanOrEqual( "sn", "n" ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (sn&gt;=n)
     * </pre>
     * 
     * which would match results whose surname starts with the second half of
     * the alphabet.  
     *
     * @param attribute The attribute 
     * @param value The value
     * @return A new FilterBuilder
     */
    public static FilterBuilder greaterThanOrEqual( String attribute, String value )
    {
        return new FilterBuilder( AttributeValueAssertionFilter.greaterThanOrEqual( attribute, value ) );
    }


    /**
     * Returns a new FilterBuilder for testing lexicographical less than.  For
     * example:
     * 
     * <pre>
     * lessThanOrEqual( "sn", "mzzzzzz" ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (sn&lt;=mzzzzzz)
     * </pre>
     * 
     * which would match results whose surname starts with the first half of
     * the alphabet.  <i>Note, this is not perfect, but if you know anybody with
     * a last name that starts with an <code>m</code> followed by six
     * <code>z</code>'s...</i>
     *
     * @param attribute The attribute 
     * @param value The value
     * @return A new FilterBuilder
     */
    public static FilterBuilder lessThanOrEqual( String attribute, String value )
    {
        return new FilterBuilder( AttributeValueAssertionFilter.lessThanOrEqual( attribute, value ) );
    }


    /**
     * Returns a new FilterBuilder for negating another filter.  For example:
     * 
     * <pre>
     * not( present( "givenName" ) ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (!(givenName=*))
     * </pre>
     *
     * @param builder The filter to negate
     * @return A new FilterBuilder
     */
    public static FilterBuilder not( FilterBuilder builder )
    {
        return new FilterBuilder( UnaryFilter.not( builder.filter ) );
    }


    /**
     * Returns a new FilterBuilder that will <code>|</code> together all of the 
     * supplied filters.  For example:
     * 
     * <pre>
     * or( equal( "givenName", "kermit" ), equal( "givenName", "walter" ) ).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (|(givenName=kermit)(givenName=walter))
     * </pre>
     * 
     * Which would match any entry with the <code>givenName</code> of either
     * <code>kermit</code> or <code>walter</code>.
     *
     * @param builders The filters to or together
     * @return A new FilterBuilder
     */
    public static FilterBuilder or( FilterBuilder... builders )
    {
        SetOfFiltersFilter filter = SetOfFiltersFilter.or();

        for ( FilterBuilder builder : builders )
        {
            filter.add( builder.filter );
        }

        return new FilterBuilder( filter );
    }


    /**
     * Returns a new FilterBuilder for testing the presence of an attributes.  
     * For example:
     * 
     * <pre>
     * present( "givenName" ).toString();
     * </pre>
     * would result in the string:
     * <pre>
     * (givenName=*)
     * </pre>
     * 
     * Which would match any entry that has a <code>givenName</code> attribute.
     *
     * @param attribute The attribute to test the presence of
     * @return A new FilterBuilder
     */
    public static FilterBuilder present( String attribute )
    {
        return new FilterBuilder( AttributeDescriptionFilter.present( attribute ) );
    }


    /**
     * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
     * and zero to N <em>any</em> part, but no <em>final</em> part.  
     * 
     * For instance:
     * 
     * <pre>
     * startswith( "sn", "Th", "Soft", "Foun" )).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (sn=Th*Soft*Foun*)
     * </pre>
     * 
     * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
     * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
     * 'The Apache Software Foundation'.
     *
     * @param attribute The attribute to use in the filter
     * @param parts The sub elements to use in the filter
     * @return A new FilterBuilder
     */
    public static FilterBuilder startsWith( String attribute, String... parts )
    {
        if ( ( parts == null ) || ( parts.length == 0 ) )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_04162_INITIAL_PART_NEEDED ) );
        }

        return new FilterBuilder( SubstringFilter.startsWith( attribute, parts ) );
    }


    /**
     * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
     * and zero to N <em>any</em> parts, but no <em>final</em> part.  
     * 
     * For instance:
     * 
     * <pre>
     * startswith( "sn", "Th", "Soft", "Foun" ).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (sn=Th*Soft*Foun*)
     * </pre>
     * 
     * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
     * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
     * 'The Apache Software Foundation'.
     *
     * @param attribute The attribute to use in the filter
     * @param parts The sub elements to use in the filter
     * @return A new FilterBuilder
     */
    public static FilterBuilder endsWith( String attribute, String... parts )
    {
        if ( ( parts == null ) || ( parts.length == 0 ) )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_04163_FINAL_PART_NEEDED ) );
        }

        return new FilterBuilder( SubstringFilter.endsWith( attribute, parts ) );
    }


    /**
     * Returns a new FilterBuilder that will construct a SubString filter, with zero to N <em>any</em> parts, 
     * but no <em>initial</em> or <em>final</em> parts.  
     * 
     * For instance:
     * 
     * <pre>
     * contains( "sn", "Soft", "Foun" )).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (sn=*Soft*Foun*)
     * </pre>
     * 
     * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
     * and <code>Foun</code> strings in the middle, like 
     * 'The Apache Software Foundation'.
     *
     * @param attribute The attribute to use in the filter
     * @param parts The sub elements to use in the filter
     * @return A new FilterBuilder
     */
    public static FilterBuilder contains( String attribute, String... parts )
    {
        if ( ( parts == null ) || ( parts.length == 0 ) )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_04164_ANY_PART_NEEDED ) );
        }

        return new FilterBuilder( SubstringFilter.contains( attribute, parts ) );
    }


    /**
     * Returns a new FilterBuilder that will construct a SubString filter, with a <em>initial</em> part, 
     * zero to N <em>any</em> parts, and a <em>final</em> part.
     * 
     * For instance:
     * 
     * <pre>
     * substring( "sn", "The", "Soft", "Foun", "ion" )).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (sn=The*Soft*Foun*ion)
     * </pre>
     * 
     * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
     * and <code>Foun</code> strings in the middle, starts with <code>The</code> and ends with <code>ion</code> like 
     * 'The Apache Software Foundation'.
     * <p>
     * Note that if we have only two strings in the parts, they will be the <em>initial</em> and <em>final</em> ones :
     * 
     * <pre>
     * substring( "sn", "The", "ion" )).toString()
     * </pre>
     * would result in the string:
     * <pre>
     * (sn=The*ion)
     * </pre>
     * 
     * @param attribute The attribute to use in the filter
     * @param parts The sub elements to use in the filter
     * @return A new FilterBuilder
     */
    public static FilterBuilder substring( String attribute, String... parts )
    {
        if ( ( parts == null ) || ( parts.length == 0 ) )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_04165_INITIAL_ANY_FINAL_PART_NEEDED ) );
        }

        return new FilterBuilder( SubstringFilter.substring( attribute, parts ) );
    }


    /**
     * Returns the string version of the filter represented by this FilterBuilder.
     * 
     * @return The string representation of the filter
     */
    @Override
    public String toString()
    {
        return filter.build().toString();
    }
}
