/*
 *  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.common.filtereditor;


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.LdapNotFilterComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapOrFilterComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.parser.LdapFilterParser;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;


/**
 * The FilterAutoEditStrategy implements the IAutoEditStrategy for the filter editor widget.
 * It provides smart parentesis handling when typing the filter.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class FilterAutoEditStrategy extends DefaultIndentLineAutoEditStrategy implements IAutoEditStrategy
{

    /** The Constant INDENT_STRING. */
    public static final String INDENT_STRING = "    "; //$NON-NLS-1$

    /** The filter parser. */
    private LdapFilterParser parser;


    /**
     * Creates a new instance of FilterAutoEditStrategy.
     * 
     * @param parser the filter parser
     */
    public FilterAutoEditStrategy( LdapFilterParser parser )
    {
        this.parser = parser;
    }


    /**
     * @see org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy#customizeDocumentCommand(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.DocumentCommand)
     */
    public void customizeDocumentCommand( IDocument d, DocumentCommand c )
    {
        super.customizeDocumentCommand( d, c );
        AutoEditParameters aep = new AutoEditParameters( c.text, c.offset, c.length, c.caretOffset, c.shiftsCaret );
        customizeAutoEditParameters( d.get(), aep );
        c.offset = aep.offset;
        c.length = aep.length;
        c.text = aep.text;
        c.caretOffset = aep.caretOffset;
        c.shiftsCaret = aep.shiftsCaret;
    }


    /**
     * Customizes auto edit parameters.
     * 
     * @param currentFilter the current filter
     * @param aep the auto edit parameters
     */
    public void customizeAutoEditParameters( String currentFilter, AutoEditParameters aep )
    {
        parser.parse( currentFilter );
        LdapFilter filter = parser.getModel().getFilter( aep.offset );
        if ( filter == null )
        {
            return;
        }

        // check balanced parenthesis
        int balanced = 0;
        for ( int i = 0; i < currentFilter.length(); i++ )
        {
            if ( currentFilter.charAt( i ) == '(' )
            {
                balanced++;
            }
            else if ( currentFilter.charAt( i ) == ')' )
            {
                balanced--;
            }
        }

        if ( aep.length > 0 && ( aep.text == null || "".equals( aep.text ) ) ) //$NON-NLS-1$
        {
            // delete surrounding parenthesis after deleting the last character
            if ( filter.toString().length() - aep.length == 2 && filter.getStartToken() != null
                && filter.getStopToken() != null
                && aep.offset >= filter.getStartToken().getOffset() + filter.getStartToken().getLength()
                && aep.offset + aep.length <= filter.getStopToken().getOffset() )
            {
                if ( filter.toString().length() - aep.length == 2 )
                {
                    aep.offset -= 1;
                    aep.length += 2;
                    aep.caretOffset = aep.offset;
                    aep.shiftsCaret = false;
                }
            }

            // delete closing parenthesis after deleting the opening parenthesis
            if ( filter.toString().length() - aep.length == 1 && filter.getStartToken() != null
                && filter.getStopToken() != null && aep.offset == filter.getStartToken().getOffset() )
            {
                aep.length += 1;
                aep.caretOffset = aep.offset;
                aep.shiftsCaret = false;
            }

        }

        if ( ( aep.length == 0 || aep.length == currentFilter.length() ) && aep.text != null && !"".equals( aep.text ) ) //$NON-NLS-1$
        {
            boolean isNewFilter = aep.text.equals( "(" ); //$NON-NLS-1$
            boolean isNewNestedFilter = aep.text.equals( "&" ) || aep.text.equals( "|" ) || aep.text.equals( "!" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            boolean isSurroundNew = false;
            boolean isSurroundNested = false;
            boolean isSurroundBeforeOtherFilter = false;
            boolean isSurroundAfterOtherFilter = false;
            if ( !Character.isWhitespace( aep.text.charAt( 0 ) )
                && !aep.text.startsWith( "(" ) && !aep.text.endsWith( ")" ) ) //$NON-NLS-1$ //$NON-NLS-2$
            {
                // isSurroundNew
                isSurroundNew = aep.offset == 0;

                // isSurroundNested
                if ( filter.getStartToken() != null
                    && ( filter.getFilterComponent() instanceof LdapAndFilterComponent
                        || filter.getFilterComponent() instanceof LdapOrFilterComponent || filter.getFilterComponent() instanceof LdapNotFilterComponent ) )
                {
                    LdapFilterComponent fc = filter.getFilterComponent();
                    LdapFilter[] filters = fc.getFilters();

                    if ( filters.length == 0 && aep.offset > fc.getStartToken().getOffset() )
                    {
                        // no nested filter yet
                        isSurroundNested = true;
                    }

                    if ( filters.length > 0 && aep.offset > fc.getStartToken().getOffset()
                        && aep.offset < filters[0].getStartToken().getOffset() )
                    {
                        // before first nested filter
                        isSurroundNested = true;
                    }

                    if ( filters.length > 0 && aep.offset > filters[filters.length - 1].getStopToken().getOffset()
                        && aep.offset <= filter.getStopToken().getOffset() )
                    {
                        // after last nested filter
                        isSurroundNested = true;
                    }

                    for ( int i = 0; i < filters.length; i++ )
                    {
                        if ( filters.length > i + 1 )
                        {
                            if ( aep.offset > filters[i].getStopToken().getOffset()
                                && aep.offset <= filters[i + 1].getStopToken().getOffset() )
                            {
                                // between nested filter
                                isSurroundNested = true;
                            }
                        }
                    }
                }

                // isSurroundBeforeOtherFilter
                isSurroundBeforeOtherFilter = filter.getStartToken() != null
                    && aep.offset == filter.getStartToken().getOffset();

                // isSurroundAfterOtherFilter
                isSurroundAfterOtherFilter = filter.getStopToken() != null
                    && aep.offset == filter.getStopToken().getOffset()
                    && ( filter.getFilterComponent() instanceof LdapAndFilterComponent
                        || filter.getFilterComponent() instanceof LdapOrFilterComponent || filter.getFilterComponent() instanceof LdapNotFilterComponent );
            }

            // add opening parenthesis '('
            if ( isSurroundNew || isSurroundNested || isSurroundAfterOtherFilter || isSurroundBeforeOtherFilter )
            {
                aep.text = "(" + aep.text; //$NON-NLS-1$
                aep.caretOffset = aep.offset + aep.text.length();
                aep.shiftsCaret = false;
            }

            // add parenthesis for nested filters
            if ( isNewNestedFilter )
            {
                aep.text = aep.text + "()"; //$NON-NLS-1$
                aep.caretOffset = aep.offset + aep.text.length() - 1;
                aep.shiftsCaret = false;
            }

            // add closing parenthesis ')'
            if ( isNewFilter || isSurroundNew || isSurroundNested || isSurroundAfterOtherFilter
                || isSurroundBeforeOtherFilter )
            {
                if ( balanced == 0 )
                {
                    aep.text = aep.text + ")"; //$NON-NLS-1$
                    if ( aep.caretOffset == -1 )
                    {
                        aep.caretOffset = aep.offset + aep.text.length() - 1;
                        aep.shiftsCaret = false;
                    }
                }
            }

            // translate tab to IDENT_STRING
            if ( aep.text.equals( "\t" ) ) //$NON-NLS-1$
            {
                aep.text = INDENT_STRING;
            }
        }
    }

    /**
     * Helper class.
     *
     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
     */
    public static class AutoEditParameters
    {

        /** The text. */
        public String text;

        /** The offset. */
        public int offset;

        /** The length. */
        public int length;

        /** The caret offset. */
        public int caretOffset;

        /** The shifts caret flag. */
        public boolean shiftsCaret;


        /**
         * Creates a new instance of AutoEditParameters.
         * 
         * @param text the text
         * @param offset the offset
         * @param length the length
         * @param caretOffset the caret offset
         * @param shiftsCaret the shifts caret flag
         */
        public AutoEditParameters( String text, int offset, int length, int caretOffset, boolean shiftsCaret )
        {
            this.text = text;
            this.offset = offset;
            this.length = length;
            this.caretOffset = caretOffset;
            this.shiftsCaret = shiftsCaret;
        }
    }

}
