/*
 *  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.directory.shared.ldap.subtree;


import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

import org.apache.directory.shared.ldap.entry.StringValue;
import org.apache.directory.shared.ldap.filter.AndNode;
import org.apache.directory.shared.ldap.filter.BranchNode;
import org.apache.directory.shared.ldap.filter.EqualityNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.FilterParser;
import org.apache.directory.shared.ldap.filter.NotNode;
import org.apache.directory.shared.ldap.filter.OrNode;
import org.apache.directory.shared.ldap.filter.SimpleNode;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.subtree.SubtreeSpecification;
import org.apache.directory.shared.ldap.subtree.SubtreeSpecificationParser;
import org.junit.Test;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;


/**
 * Unit tests class for Subtree Specification parser (wrapper).
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 * @version $Rev$
 */
public class SubtreeSpecificationParserTest
{
    /** A valid empty specification with single white space between brackets */
    private static final String EMPTY_SPEC = "{ }";

    /** A valid specification only with base set */
    private static final String SPEC_WITH_BASE = "{ base \"ou=system\" }";

    /** An invalid specification with missing white space and base set */
    private static final String INVALID_SPEC_WITH_BASE_AND_MISSING_WS = "{ base\"ou=system\"}";

    /** A valid specification with some specific exclusions set */
    private static final String SPEC_WITH_SPECIFICEXCLUSIONS = "{ specificExclusions { chopAfter:\"ef=gh\", chopBefore:\"ab=cd\" } }";

    /** A valid specification with empty specific exclusions set */
    private static final String SPEC_WITH_EMPTY_SPECIFICEXCLUSIONS = "{ specificExclusions { } }";

    /** A valid specification with minimum and maximum set */
    private static final String SPEC_WITH_MINIMUM_AND_MAXIMUM = "{ minimum 1, maximum 2 }";

    /** A valid specification with base and minimum and maximum set */
    private static final String SPEC_WITH_BASE_AND_MINIMUM_AND_MAXIMUM = "{ base \"ou=ORGANIZATION UNIT\", minimum  1, maximum   2 }";

    /**
     * A valid specification with base and specific exclusions and minimum and
     * maximum set
     */
    private static final String SPEC_WITH_BASE_AND_SPECIFICEXCLUSIONS_AND_MINIMUM_AND_MAXIMUM = "{ base \"ou=people\", specificExclusions { chopBefore:\"x=y\""
        + ", chopAfter:\"k=l\", chopBefore:\"y=z\", chopAfter:\"l=m\" }, minimum   7, maximum 77 }";

    /** A valid specification with refinement set */
    private static final String SPEC_WITH_REFINEMENT = "{ base \"ou=system\", specificationFilter and:{ and:{ item:1.2.3"
        + ", or:{ item:4.5.6, item:person-7 } }, not:{ item:10.11.12 } } }";

    /** A valid specification with base and an empty refinement set */
    private static final String SPEC_WITH_BASE_AND_EMPTY_REFINEMENT = "{ base \"ou=system\", specificationFilter and:{ } }";

    /** A valid specification with ALL IN ONE */
    private static final String SPEC_WITH_ALL_IN_ONE = "{ base    \"ou=departments\""
        + ", specificExclusions { chopBefore:\"x=y\", chopAfter:\"k=l\", chopBefore:\"y=z\", chopAfter:\"l=m\" }"
        + ", minimum 7, maximum   77"
        + ", specificationFilter     and:{ and:{ item:1.2.3, or:{ item:4.5.6, item:7.8.9 } }, not:{ item:10.11.12 } } }";

    /** An valid specification with unordinary component order */
    private static final String SPEC_ORDER_OF_COMPONENTS_DOES_NOT_MATTER = "{ base \"ou=system\", minimum 3, specificExclusions { chopBefore:\"x=y\" } }";

    /** An invalid specification with completely unrelated content */
    private static final String INVALID_SILLY_THING = "How much wood would a wood chuck chuck if a wood chuck would chuck wood?";
    
    /** A valid specification with filter expression */
    private static final String SPEC_WITH_FILTER = "{ base \"ou=system\", specificationFilter (&(cn=test)(sn=test)) }";

    
    /** the ss parser wrapper */
    SubtreeSpecificationParser parser;

    /** holds multithreaded success value */
    boolean isSuccessMultithreaded = true;


    /**
     * Creates a SubtreeSpecificationParserTest instance.
     */
    public SubtreeSpecificationParserTest()
    {
        super();
        parser = new SubtreeSpecificationParser( null );
    }


    /**
     * Tests the parser with a valid empty specification.
     */
    @Test
    public void testEmptySpec() throws Exception
    {
        SubtreeSpecification ss = parser.parse( EMPTY_SPEC );
        assertNotNull( ss );

        // try a second time
        ss = parser.parse( EMPTY_SPEC );
        assertNotNull( ss );

        // try a third time
        ss = parser.parse( EMPTY_SPEC );
        assertNotNull( ss );
    }


    /**
     * Tests the parser with a valid specification with base set.
     */
    @Test
    public void testSpecWithBase() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE );
        assertNotNull( ss );

        assertEquals( "ou=system", ss.getBase().toString() );
    }


    /**
     * Tests the parser with an invalid specification with missing white spaces
     * and base set.
     */
    @Test
    public void testInvalidSpecWithBaseAndMissingWS() throws Exception
    {
        try
        {
            parser.parse( INVALID_SPEC_WITH_BASE_AND_MISSING_WS );
            fail( "testInvalidSpecWithBaseAndMissingWS() should never come here..." );
        }
        catch ( ParseException e )
        {
            assertNotNull( e );
        }
    }


    /**
     * Tests the parser with a valid specification with some specific exclusions
     * set.
     */
    @Test
    public void testSpecWithSpecificExclusions() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
        assertFalse( ss.getChopBeforeExclusions().isEmpty() );
        assertFalse( ss.getChopAfterExclusions().isEmpty() );
        assertTrue( ss.getChopBeforeExclusions().contains( new DN( "ab=cd" ) ) );
        assertTrue( ss.getChopAfterExclusions().contains( new DN( "ef=gh" ) ) );

        // try a second time
        ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
        assertFalse( ss.getChopBeforeExclusions().isEmpty() );
        assertFalse( ss.getChopAfterExclusions().isEmpty() );
        assertTrue( ss.getChopBeforeExclusions().contains( new DN( "ab=cd" ) ) );
        assertTrue( ss.getChopAfterExclusions().contains( new DN( "ef=gh" ) ) );

        // try a third time
        ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
        assertFalse( ss.getChopBeforeExclusions().isEmpty() );
        assertFalse( ss.getChopAfterExclusions().isEmpty() );
        assertTrue( ss.getChopBeforeExclusions().contains( new DN( "ab=cd" ) ) );
        assertTrue( ss.getChopAfterExclusions().contains( new DN( "ef=gh" ) ) );
    }


    /**
     * Tests the parser with a valid specification with an empty specific
     * exclusions set.
     */
    @Test
    public void testSpecWithEmptySpecificExclusions() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_EMPTY_SPECIFICEXCLUSIONS );
        assertNotNull( ss );

        assertTrue( ss.getChopBeforeExclusions().isEmpty() );
    }


    /**
     * Tests the parser with a valid specification with minimum and maximum set.
     */
    @Test
    public void testSpecWithMinimumAndMaximum() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
        assertEquals( 1, ss.getMinBaseDistance() );
        assertEquals( 2, ss.getMaxBaseDistance() );

        // try a second time
        ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
        assertEquals( 1, ss.getMinBaseDistance() );
        assertEquals( 2, ss.getMaxBaseDistance() );

        // try a third time
        ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
        assertEquals( 1, ss.getMinBaseDistance() );
        assertEquals( 2, ss.getMaxBaseDistance() );
    }


    /**
     * Tests the parser with a valid specification with base and minimum and
     * maximum set.
     */
    @Test
    public void testWithBaseAndMinimumAndMaximum() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_MINIMUM_AND_MAXIMUM );

        assertEquals( new DN( "ou=ORGANIZATION UNIT" ), ss.getBase() );
        assertEquals( 1, ss.getMinBaseDistance() );
        assertEquals( 2, ss.getMaxBaseDistance() );
    }


    /**
     * Tests the parser with a valid specification with base and specific
     * exclusions and minimum and maximum set.
     */
    @Test
    public void testSpecWithBaseAndSpecificExclusionsAndMinimumAndMaximum() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_SPECIFICEXCLUSIONS_AND_MINIMUM_AND_MAXIMUM );
        assertNotNull( ss );

        assertEquals( "ou=people", ss.getBase().toString() );
        assertTrue( ss.getChopBeforeExclusions().contains( new DN( "x=y" ) ) );
        assertTrue( ss.getChopBeforeExclusions().contains( new DN( "y=z" ) ) );
        assertTrue( ss.getChopAfterExclusions().contains( new DN( "k=l" ) ) );
        assertTrue( ss.getChopAfterExclusions().contains( new DN( "l=m" ) ) );
        assertEquals( 7, ss.getMinBaseDistance() );
        assertEquals( 77, ss.getMaxBaseDistance() );
    }


    /**
     * Tests the parser with a valid specification with refinement set.
     */
    @Test
    public void testSpecWithRefinement() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_REFINEMENT );

        SimpleNode<String> n1 = new EqualityNode<String>( "objectClass", new StringValue( "1.2.3" ) );
        SimpleNode<String> n2 = new EqualityNode<String>( "objectClass", new StringValue( "4.5.6" ) );
        SimpleNode<String> n3 = new EqualityNode<String>( "objectClass", new StringValue( "person-7" ) );
        BranchNode n4 = new OrNode();
        n4.addNode( n2 );
        n4.addNode( n3 );
        BranchNode n5 = new AndNode();
        n5.addNode( n1 );
        n5.addNode( n4 );
        SimpleNode<String> n6 = new EqualityNode<String>( "objectClass", new StringValue( "10.11.12" ) );
        BranchNode n7 = new NotNode();
        n7.addNode( n6 );
        BranchNode n8 = new AndNode();
        n8.addNode( n5 );
        n8.addNode( n7 );

        assertEquals( n8, ss.getRefinement() );
    }


    /**
     * Tests the parser with a valid specification with base and empty
     * refinement set.
     */
    @Test
    public void testSpecWithBaseAndEmptyRefinement() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_EMPTY_REFINEMENT );

        assertEquals( "ou=system", ss.getBase().toString() );
    }


    /**
     * Tests the parser with a valid specification with all components set.
     */
    @Test
    public void testSpecWithAllInOne() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_ALL_IN_ONE );
        assertNotNull( ss );
    }


    /**
     * Tests the parser with a valid specification with unordinary component
     * order.
     */
    @Test
    public void testSpecOrderOfComponentsDoesNotMatter() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_ORDER_OF_COMPONENTS_DOES_NOT_MATTER );
        assertNotNull( ss );
    }


    /**
     * Tests the parser with an invalid specification with silly things in.
     */
    @Test
    public void testInvalidSillyThing() throws Exception
    {
        try
        {
            parser.parse( INVALID_SILLY_THING );
            fail( "testInvalidSillyThing() should never come here..." );
        }
        catch ( ParseException e )
        {
            assertNotNull( e );
        }
    }
    
    
    /**
     * Tests the parser with a valid specification with refinement set.
     */
    @Test
    public void testSpecWithFilter() throws Exception
    {
        SubtreeSpecification ss = parser.parse( SPEC_WITH_FILTER );

        ExprNode filter = FilterParser.parse( "(&(cn=test)(sn=test))" );

        assertEquals( filter, ss.getRefinement() );
    }


    /**
     * Test reusability, especially if the state is resetted.
     */
    @Test
    public void testReusabiltiy() throws Exception
    {
        DN firstDN = new DN("k=l");
        String firstExclusion = "{ specificExclusions { chopAfter:\"k=l\" } }";
        SubtreeSpecification firstSpec = parser.parse( firstExclusion );
        assertEquals( 1, firstSpec.getChopAfterExclusions().size() );
        assertEquals( firstDN, firstSpec.getChopAfterExclusions().iterator().next() );

        DN secondDN = new DN("x=y");
        String secondExclusion = "{ specificExclusions { chopAfter:\"x=y\" } }";
        SubtreeSpecification secondSpec = parser.parse( secondExclusion );
        assertEquals( 1, secondSpec.getChopAfterExclusions().size() );
        assertEquals( secondDN, secondSpec.getChopAfterExclusions().iterator().next() );

    }


    /**
     * Tests the multithreaded use of a single parser.
     */
    @Test
    public void testMultiThreaded() throws Exception
    {
        // start up and track all threads (40 threads)
        List<Thread> threads = new ArrayList<Thread>();
        for ( int ii = 0; ii < 10; ii++ )
        {
            Thread t0 = new Thread( new ParseSpecification( EMPTY_SPEC ) );
            Thread t1 = new Thread( new ParseSpecification( SPEC_WITH_SPECIFICEXCLUSIONS ) );
            Thread t2 = new Thread( new ParseSpecification( SPEC_WITH_MINIMUM_AND_MAXIMUM ) );
            Thread t3 = new Thread( new ParseSpecification( SPEC_WITH_ALL_IN_ONE ) );
            threads.add( t0 );
            threads.add( t1 );
            threads.add( t2 );
            threads.add( t3 );
            t0.start();
            t1.start();
            t2.start();
            t3.start();
        }

        // wait until all threads have died
        boolean hasLiveThreads = false;
        do
        {
            hasLiveThreads = false;

            for ( int ii = 0; ii < threads.size(); ii++ )
            {
                Thread t = threads.get( ii );
                hasLiveThreads = hasLiveThreads || t.isAlive();
            }
        }
        while ( hasLiveThreads );

        // check that no one thread failed to parse and generate a SS object
        assertTrue( isSuccessMultithreaded );
    }

    /**
     * Used to test multithreaded use of a single parser.
     */
    class ParseSpecification implements Runnable
    {
        private final String specStr;

        SubtreeSpecification result;


        public ParseSpecification(String specStr)
        {
            this.specStr = specStr;
        }


        public void run()
        {
            try
            {
                result = parser.parse( specStr );
            }
            catch ( ParseException e )
            {
                e.printStackTrace();
            }

            isSuccessMultithreaded = isSuccessMultithreaded && ( result != null );
        }
    }
}
