/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */
package org.opends.server.protocols.ldap;

import java.util.ArrayList;

import org.forgerock.opendj.io.ASN1;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.opends.server.TestCaseUtils;
import org.opends.server.types.LDAPException;
import org.opends.server.types.RawFilter;
import org.opends.server.types.SearchFilter;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static org.opends.server.util.CollectionUtils.*;
import static org.testng.Assert.*;

public class TestLDAPFilter extends LdapTestCase
{
  @BeforeTest
  public void setup() throws Exception
  {
    TestCaseUtils.startServer();
  }
  @DataProvider(name="badfilterstrings")
  public Object[][] getBadFilterStrings() throws Exception
  {
    return new Object[][]
    {
      { null, null },
      { "", null },
      { "=", null },
      { "()", null },
      { "(&(objectClass=*)(sn=s*s)", null },
      { "(dob>12221)", null },
      { "(cn=bob\\2 doe)", null },
      { "(cn=\\4j\\w2\\yu)", null },
      { "(cn=ds\\2)", null },
      { "(&(givenname=bob)|(sn=pep)dob=12))", null },
      { "(:=bob)", null },
      { "(=sally)", null },
      { "(cn=billy bob", null },
      { "(|(!(title=sweep*)(l=Paris*)))", null },
      { "(|(!))", null },
      { "((uid=user.0))", null },
      { "(&&(uid=user.0))", null },
      { "!uid=user.0", null },
      { "(:dn:=Sally)", null },

    };
  }
  @DataProvider(name="filterstrings")
  public Object[][] getFilterStrings() throws Exception
  {
    LDAPFilter equal = LDAPFilter.createEqualityFilter("objectClass",
                                        ByteString.valueOfUtf8("\\test*(Value)"));
    LDAPFilter equal2 = LDAPFilter.createEqualityFilter("objectClass",
                                                      ByteString.valueOfUtf8(""));
    LDAPFilter approx = LDAPFilter.createApproximateFilter("sn",
                                        ByteString.valueOfUtf8("\\test*(Value)"));
    LDAPFilter greater = LDAPFilter.createGreaterOrEqualFilter("employeeNumber",
                                        ByteString.valueOfUtf8("\\test*(Value)"));
    LDAPFilter less = LDAPFilter.createLessOrEqualFilter("dob",
                                        ByteString.valueOfUtf8("\\test*(Value)"));
    LDAPFilter presense = LDAPFilter.createPresenceFilter("login");

    ArrayList<ByteString> any = new ArrayList<>(0);
    ArrayList<ByteString> multiAny = newArrayList(ByteString.valueOfUtf8("\\wid*(get)"), ByteString.valueOfUtf8("*"));

    LDAPFilter substring1 = LDAPFilter.createSubstringFilter("givenName",
                                                 ByteString.valueOfUtf8("\\Jo*()"),
                                                      any,
                                                 ByteString.valueOfUtf8("\\n*()"));
    LDAPFilter substring2 = LDAPFilter.createSubstringFilter("givenName",
                                                 ByteString.valueOfUtf8("\\Jo*()"),
                                                      multiAny,
                                                 ByteString.valueOfUtf8("\\n*()"));
    LDAPFilter substring3 = LDAPFilter.createSubstringFilter("givenName",
                                                      ByteString.valueOfUtf8(""),
                                                      any,
                                                 ByteString.valueOfUtf8("\\n*()"));
    LDAPFilter substring4 = LDAPFilter.createSubstringFilter("givenName",
                                                 ByteString.valueOfUtf8("\\Jo*()"),
                                                      any,
                                                      ByteString.valueOfUtf8(""));
    LDAPFilter substring5 = LDAPFilter.createSubstringFilter("givenName",
                                                      ByteString.valueOfUtf8(""),
                                                      multiAny,
                                                      ByteString.valueOfUtf8(""));
    LDAPFilter extensible1 = LDAPFilter.createExtensibleFilter("2.4.6.8.19",
                                                "cn",
                                           ByteString.valueOfUtf8("\\John* (Doe)"),
                                                false);
    LDAPFilter extensible2 = LDAPFilter.createExtensibleFilter("2.4.6.8.19",
                                                "cn",
                                           ByteString.valueOfUtf8("\\John* (Doe)"),
                                                true);
    LDAPFilter extensible3 = LDAPFilter.createExtensibleFilter("2.4.6.8.19",
                                                null,
                                           ByteString.valueOfUtf8("\\John* (Doe)"),
                                                true);
    LDAPFilter extensible4 = LDAPFilter.createExtensibleFilter(null,
                                                "cn",
                                           ByteString.valueOfUtf8("\\John* (Doe)"),
                                                true);
    LDAPFilter extensible5 = LDAPFilter.createExtensibleFilter("2.4.6.8.19",
                                                null,
                                           ByteString.valueOfUtf8("\\John* (Doe)"),
                                                false);

    ArrayList<RawFilter> list1 = new ArrayList<>();
    list1.add(equal);
    list1.add(approx);

    LDAPFilter and = LDAPFilter.createANDFilter(list1);

    ArrayList<RawFilter> list2 = new ArrayList<>();
    list2.add(substring1);
    list2.add(extensible1);
    list2.add(and);

    return new Object[][]
    {
        { "(objectClass=\\5ctest\\2a\\28Value\\29)", equal },

        { "(objectClass=)", equal2 },

        { "(sn~=\\5ctest\\2a\\28Value\\29)", approx },

        { "(employeeNumber>=\\5ctest\\2a\\28Value\\29)", greater },

        { "(dob<=\\5ctest\\2a\\28Value\\29)", less },

        { "(login=*)", presense },

        { "(givenName=\\5cJo\\2a\\28\\29*\\5cn\\2a\\28\\29)", substring1 },

        { "(givenName=\\5cJo\\2a\\28\\29*\\5cwid\\2a\\28get\\29*\\2a*\\5cn\\2a\\28\\29)", substring2 },

        { "(givenName=*\\5cn\\2a\\28\\29)", substring3 },

        { "(givenName=\\5cJo\\2a\\28\\29*)", substring4 },

        { "(givenName=*\\5cwid\\2a\\28get\\29*\\2a*)", substring5 },

        { "(cn:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)", extensible1 },

        { "(cn:dn:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)", extensible2 },

        { "(:dn:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)", extensible3 },

        { "(cn:dn:=\\5cJohn\\2a \\28Doe\\29)", extensible4 },

        { "(:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)", extensible5 },

        { "(&(objectClass=\\5ctest\\2a\\28Value\\29)(sn~=\\5ctest\\2a\\28Value\\29))",
            LDAPFilter.createANDFilter(list1) },

        { "(|(objectClass=\\5ctest\\2a\\28Value\\29)(sn~=\\5ctest\\2a\\28Value\\29))",
            LDAPFilter.createORFilter(list1) },

        { "(!(objectClass=\\5ctest\\2a\\28Value\\29))", LDAPFilter.createNOTFilter(equal) },

        { "(|(givenName=\\5cJo\\2a\\28\\29*\\5cn\\2a\\28\\29)(cn:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)" +
            "(&(objectClass=\\5ctest\\2a\\28Value\\29)(sn~=\\5ctest\\2a\\28Value\\29)))",
            LDAPFilter.createORFilter(list2) },

        // OpenDJ issue 23.
        {
            "(ds-sync-conflict=uid=\\5c+3904211775265,ou=SharedAddressBook,cn=1038372,dc=cab)",
            LDAPFilter.createEqualityFilter("ds-sync-conflict",
                    ByteString.valueOfUtf8("uid=\\+3904211775265,ou=SharedAddressBook,cn=1038372,dc=cab")) },

      // OPENDJ-735
      { "(&)", LDAPFilter.createANDFilter(new ArrayList<RawFilter>()) },

      // OPENDJ-735
      { "(|)", LDAPFilter.createORFilter(new ArrayList<RawFilter>()) }
    };
  }

  @Test(dataProvider = "filterstrings")
  public void testDecode(String filterStr, LDAPFilter filter) throws Exception
  {
    LDAPFilter decoded = LDAPFilter.decode(filterStr);
    assertEquals(decoded.toString(), filter.toString());
    assertEquals(decoded.getAssertionValue(), filter.getAssertionValue());
    assertEquals(decoded.getAttributeType(), filter.getAttributeType());
    assertEquals(decoded.getDNAttributes(), filter.getDNAttributes());
    if(decoded.getFilterComponents() != null || filter.getFilterComponents() != null)
    {
      assertEquals(decoded.getFilterComponents().toString(), filter.getFilterComponents().toString());
    }
    assertEquals(decoded.getFilterType(), filter.getFilterType());
    assertEquals(decoded.getMatchingRuleID(), filter.getMatchingRuleID());
    if(decoded.getNOTComponent() != null || filter.getNOTComponent() != null)
    {
      assertEquals(decoded.getNOTComponent().toString(), filter.getNOTComponent().toString());
    }
    if(isNotEmpty(decoded.getSubAnyElements()) || isNotEmpty(filter.getSubAnyElements()))
    {
      assertEquals(decoded.getSubAnyElements(), filter.getSubAnyElements());
    }
    if(isNotEmpty(decoded.getSubFinalElement()) || isNotEmpty(filter.getSubFinalElement()))
    {
      assertEquals(decoded.getSubFinalElement(), filter.getSubFinalElement());
    }
    if(isNotEmpty(decoded.getSubInitialElement()) || isNotEmpty(filter.getSubInitialElement()))
    {
      assertEquals(decoded.getSubInitialElement(), filter.getSubInitialElement());
    }
  }

  private boolean isNotEmpty(ByteString bs)
  {
    return bs != null && !bs.toString().equals("");
  }

  private boolean isNotEmpty(ArrayList<ByteString> col)
  {
    return col != null && !col.isEmpty();
  }

  @Test(dataProvider = "badfilterstrings", expectedExceptions = LDAPException.class)
  public void testDecodeException (String filterStr, LDAPFilter filter) throws Exception
  {
    LDAPFilter.decode(filterStr);
  }

  @Test
  public void testToSearchFilter() throws Exception
  {
    LDAPFilter filter = LDAPFilter.decode(
        "(&" +
          "(cn>=*)" +
          "(:2.5.13.2:=Bob)" +
          "(cn:=Jane)" +
          "(|" +
            "(sn<=gh*sh*sl)" +
            "(!(cn:dn:2.5.13.5:=Sally))" +
            "(cn~=blvd)" +
            "(cn=*)" +
          ")" +
          "(cn=*n)" +
          "(cn=n*)" +
          "(cn=n*n)" +
          "(:dn:1.3.6.1.4.1.1466.109.114.1:=Doe)" +
          "(cn:2.5.13.2:=)" +
        ")");

    SearchFilter searchFilter = filter.toSearchFilter();
    LDAPFilter newFilter = new LDAPFilter(searchFilter);
    assertEquals(filter.toString(), newFilter.toString());
  }

  @Test(dataProvider = "filterstrings")
  public void testEncodeDecode(String filterStr, LDAPFilter filter) throws Exception
  {
    ByteStringBuilder builder = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(builder);
    filter.write(writer);

    ASN1Reader reader = ASN1.getReader(builder.toByteString());
    assertEquals(LDAPFilter.decode(reader).toString(), filter.toString());
  }

  @Test
  public void testEncodeDecodeComplex() throws Exception
  {
    LDAPFilter filter = LDAPFilter.decode(
        "(&" +
          "(cn>=*)" +
          "(:1.2.3.4:=Bob)" +
          "(cn:=Jane)" +
          "(|" +
            "(sn<=gh*sh*sl)" +
            "(!(cn:dn:2.4.6.8.19:=Sally))" +
            "(cn~=blvd)" +
            "(cn=*)" +
          ")" +
          "(cn=*n)" +
          "(cn=n*)" +
          "(cn=n*n)" +
          "(:dn:1.2.3.4:=Doe)" +
          "(cn:2.4.6.8.10:=)" +
        ")");

    ByteStringBuilder builder = new ByteStringBuilder();
    ASN1Writer writer = ASN1.getWriter(builder);
    filter.write(writer);

    ASN1Reader reader = ASN1.getReader(builder.toByteString());
    assertEquals(LDAPFilter.decode(reader).toString(), filter.toString());
  }
}
