/*
 *  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.studio.ldapbrowser.core.model.filter.parser;


import java.util.Stack;

import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapAndFilterComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilter;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilterComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilterExtensibleComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilterItemComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapNotFilterComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapOrFilterComponent;


/**
 * The LdapFilterParser implements a parser for LDAP filters.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class LdapFilterParser
{

    /** The scanner. */
    private LdapFilterScanner scanner;

    /** The filter stack. */
    private Stack<LdapFilter> filterStack;

    /** The parsed LDAP filter model. */
    private LdapFilter model;


    /**
     * Creates a new instance of LdapFilterParser.
     */
    public LdapFilterParser()
    {
        this.scanner = new LdapFilterScanner();
        this.model = new LdapFilter();
    }


    /**
     * Gets the parsed LDAP filter model.
     * 
     * @return the parsed model
     */
    public LdapFilter getModel()
    {
        return model;
    }


    /**
     * Parses the given LDAP filter.
     * 
     * @param ldapFilter the LDAP filter
     */
    public void parse( String ldapFilter )
    {
        // reset state
        filterStack = new Stack<LdapFilter>();
        scanner.reset( ldapFilter );
        model = new LdapFilter();

        // handle error tokens before filter
        LdapFilterToken token = scanner.nextToken();
        while ( token.getType() != LdapFilterToken.LPAR && token.getType() != LdapFilterToken.EOF )
        {
            handleError( false, token, model );
            token = scanner.nextToken();
        }

        // check filter start
        if ( token.getType() == LdapFilterToken.LPAR )
        {
            // start top level filter
            model.setStartToken( token );
            filterStack.push( model );

            // loop till filter end or EOF
            do
            {
                // next token
                token = scanner.nextToken();

                switch ( token.getType() )
                {
                    case LdapFilterToken.LPAR:
                    {
                        LdapFilter newFilter = new LdapFilter();
                        newFilter.setStartToken( token );

                        LdapFilter currentFilter = filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        if ( filterComponent != null && filterComponent.addFilter( newFilter ) )
                        {
                            filterStack.push( newFilter );
                        }
                        else
                        {
                            currentFilter.addOtherToken( token );
                        }

                        break;
                    }
                    case LdapFilterToken.RPAR:
                    {
                        LdapFilter currentFilter = filterStack.pop();
                        handleError( currentFilter.setStopToken( token ), token, currentFilter );
                        /*
                         * if(!filterStack.isEmpty()) { LdapFilter parentFilter =
                         * (LdapFilter) filterStack.peek(); LdapFilterComponent
                         * filterComponent = parentFilter.getFilterComponent();
                         * filterComponent.addFilter(currentFilter); }
                         */
                        break;
                    }
                    case LdapFilterToken.AND:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapAndFilterComponent filterComponent = new LdapAndFilterComponent( currentFilter );
                        filterComponent.setStartToken( token );
                        handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.OR:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapOrFilterComponent filterComponent = new LdapOrFilterComponent( currentFilter );
                        filterComponent.setStartToken( token );
                        handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.NOT:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapNotFilterComponent filterComponent = new LdapNotFilterComponent( currentFilter );
                        filterComponent.setStartToken( token );
                        handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.ATTRIBUTE:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapFilterItemComponent filterComponent = new LdapFilterItemComponent( currentFilter );
                        filterComponent.setAttributeToken( token );
                        handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.VALUE:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        if ( filterComponent instanceof LdapFilterItemComponent )
                        {
                            handleError( ( filterComponent instanceof LdapFilterItemComponent )
                                && ( ( LdapFilterItemComponent ) filterComponent ).setValueToken( token ), token,
                                currentFilter );
                        }
                        else if ( filterComponent instanceof LdapFilterExtensibleComponent )
                        {
                            handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                                && ( ( LdapFilterExtensibleComponent ) filterComponent ).setValueToken( token ), token,
                                currentFilter );
                        }
                        else
                        {
                            handleError( false, token, currentFilter );
                        }
                        break;
                    }
                    case LdapFilterToken.EQUAL:
                    case LdapFilterToken.GREATER:
                    case LdapFilterToken.LESS:
                    case LdapFilterToken.APROX:
                    case LdapFilterToken.PRESENT:
                    case LdapFilterToken.SUBSTRING:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        if ( filterComponent instanceof LdapFilterItemComponent )
                        {
                            handleError( ( filterComponent instanceof LdapFilterItemComponent )
                                && ( ( LdapFilterItemComponent ) filterComponent ).setFiltertypeToken( token ), token,
                                currentFilter );
                        }
                        else if ( filterComponent instanceof LdapFilterExtensibleComponent )
                        {
                            handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                                && ( ( LdapFilterExtensibleComponent ) filterComponent ).setEqualsToken( token ),
                                token, currentFilter );
                        }
                        else
                        {
                            handleError( false, token, currentFilter );
                        }
                        break;
                    }
                    case LdapFilterToken.WHITESPACE:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        currentFilter.addOtherToken( token );
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_ATTRIBUTE:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterExtensibleComponent filterComponent = new LdapFilterExtensibleComponent(
                            currentFilter );
                        filterComponent.setAttributeToken( token );
                        handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_DNATTR_COLON:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        if ( filterComponent == null )
                        {
                            filterComponent = new LdapFilterExtensibleComponent( currentFilter );
                            ( ( LdapFilterExtensibleComponent ) filterComponent ).setDnAttrColonToken( token );
                            handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        }
                        else
                        {
                            handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                                && ( ( LdapFilterExtensibleComponent ) filterComponent ).setDnAttrColonToken( token ),
                                token, currentFilter );
                        }
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_DNATTR:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                            && ( ( LdapFilterExtensibleComponent ) filterComponent ).setDnAttrToken( token ), token,
                            currentFilter );
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_MATCHINGRULEOID_COLON:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        if ( filterComponent == null )
                        {
                            filterComponent = new LdapFilterExtensibleComponent( currentFilter );
                            ( ( LdapFilterExtensibleComponent ) filterComponent ).setMatchingRuleColonToken( token );
                            handleError( currentFilter.setFilterComponent( filterComponent ), token, currentFilter );
                        }
                        else
                        {
                            handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                                && ( ( LdapFilterExtensibleComponent ) filterComponent )
                                    .setMatchingRuleColonToken( token ), token, currentFilter );
                        }
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_MATCHINGRULEOID:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                            && ( ( LdapFilterExtensibleComponent ) filterComponent ).setMatchingRuleToken( token ),
                            token, currentFilter );
                        break;
                    }
                    case LdapFilterToken.EXTENSIBLE_EQUALS_COLON:
                    {
                        LdapFilter currentFilter = ( LdapFilter ) filterStack.peek();
                        LdapFilterComponent filterComponent = currentFilter.getFilterComponent();
                        handleError( ( filterComponent instanceof LdapFilterExtensibleComponent )
                            && ( ( LdapFilterExtensibleComponent ) filterComponent ).setEqualsColonToken( token ),
                            token, currentFilter );
                        break;
                    }

                    case LdapFilterToken.EOF:
                    {
                        model.addOtherToken( token );
                        break;
                    }
                    default:
                    {
                        LdapFilter currentFilter = filterStack.peek();
                        handleError( false, token, currentFilter );
                    }
                }
            }
            while ( !filterStack.isEmpty() && token.getType() != LdapFilterToken.EOF );
        }

        // handle error token after filter
        token = scanner.nextToken();
        while ( token.getType() != LdapFilterToken.EOF )
        {
            handleError( false, token, model );
            token = scanner.nextToken();
        }
    }


    /**
     * Helper method to handle parse errors.
     * 
     * @param success the success flag
     * @param filter the filter
     * @param token the token
     */
    private void handleError( boolean success, LdapFilterToken token, LdapFilter filter )
    {
        if ( !success )
        {
            filter.addOtherToken( new LdapFilterToken( LdapFilterToken.ERROR, token.getValue(), token.getOffset() ) );
        }
    }

}
