/*
 *  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.widgets.browser;


import java.util.Arrays;
import java.util.Collection;

import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.studio.common.ui.HistoryUtils;
import org.apache.directory.studio.common.ui.widgets.BaseWidgetUtils;
import org.apache.directory.studio.connection.ui.widgets.ExtendedContentAssistCommandAdapter;
import org.apache.directory.studio.ldapbrowser.common.BrowserCommonActivator;
import org.apache.directory.studio.ldapbrowser.common.BrowserCommonConstants;
import org.apache.directory.studio.ldapbrowser.common.actions.BrowserSelectionUtils;
import org.apache.directory.studio.ldapbrowser.common.widgets.ListContentProposalProvider;
import org.apache.directory.studio.ldapbrowser.core.jobs.SearchRunnable;
import org.apache.directory.studio.ldapbrowser.core.jobs.StudioBrowserJob;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
import org.apache.directory.studio.ldapbrowser.core.model.IQuickSearch;
import org.apache.directory.studio.ldapbrowser.core.model.ISearch;
import org.apache.directory.studio.ldapbrowser.core.model.impl.QuickSearch;
import org.apache.directory.studio.ldapbrowser.core.model.schema.SchemaUtils;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;


/**
 * The BrowserQuickSearchWidget implements an instant search 
 * for the browser widget.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class BrowserQuickSearchWidget
{

    /** The Constant VALUE_HISTORY_DIALOGSETTING_KEY. */
    public static final String VALUE_HISTORY_DIALOGSETTING_KEY = BrowserQuickSearchWidget.class.getName()
        + ".valueHistory"; //$NON-NLS-1$

    /** The Constant ATTRIBUTE_HISTORY_DIALOGSETTING_KEY. */
    public static final String ATTRIBUTE_HISTORY_DIALOGSETTING_KEY = BrowserQuickSearchWidget.class.getName()
        + ".attributeHistory"; //$NON-NLS-1$

    /** An empty string array */
    private static final String[] EMPTY = new String[0];

    /** The browser widget. */
    private BrowserWidget browserWidget;

    /** The parent, used to create the composite. */
    private Composite parent;

    /** The outer composite. */
    private Composite composite;

    /** The inner composite, it is created/destroyed when showing/hiding the quick search. */
    private Composite innerComposite;

    /** The quick search attribute combo. */
    private Combo quickSearchAttributeCombo;

    /** The quick search attribute proposal provider. */
    private ListContentProposalProvider quickSearchAttributePP;

    /** The quick search operator combo. */
    private Combo quickSearchOperatorCombo;

    /** The quick search value combo. */
    private Combo quickSearchValueCombo;

    /** The quick search value proposal provider. */
    private ListContentProposalProvider quickSearchValuePP;

    /** The quick search scope button. */
    private Button quickSearchScopeButton;

    /** The quick search run button. */
    private Button quickSearchRunButton;

    /** Listener that listens for selections of connections */
    private ISelectionChangedListener selectionListener = new ISelectionChangedListener()
    {
        /**
         * {@inheritDoc}
         *
         * This implementation sets the input when another connection was selected.
         */
        public void selectionChanged( SelectionChangedEvent event )
        {
            setEnabled( getSelectedEntry() != null );
        }
    };


    /**
     * Creates a new instance of BrowserQuickSearchWidget.
     * 
     * @param browserWidget the browser widget
     */
    public BrowserQuickSearchWidget( BrowserWidget browserWidget )
    {
        this.browserWidget = browserWidget;

        if ( HistoryUtils.load( BrowserCommonActivator.getDefault().getDialogSettings(),
            ATTRIBUTE_HISTORY_DIALOGSETTING_KEY ).length == 0 )
        {
            BrowserCommonActivator.getDefault().getDialogSettings().put( ATTRIBUTE_HISTORY_DIALOGSETTING_KEY,
                new String[]
                    { "cn", //$NON-NLS-1$
                        "sn", //$NON-NLS-1$
                        "givenName", //$NON-NLS-1$
                        "mail", //$NON-NLS-1$
                        "uid", //$NON-NLS-1$
                        "description", //$NON-NLS-1$
                        "o", //$NON-NLS-1$
                        "ou", //$NON-NLS-1$
                        "member" //$NON-NLS-1$
                    } );
        }
    }


    /**
     * Creates the outer composite.
     * 
     * @param parent the parent
     */
    public void createComposite( Composite parent )
    {
        this.parent = parent;

        composite = BaseWidgetUtils.createColumnContainer( parent, 1, 1 );
        GridLayout gl = new GridLayout();
        gl.marginHeight = 2;
        gl.marginWidth = 2;
        composite.setLayout( gl );
        // Setting the default width and height of the composite to 0
        GridData compositeGridData = new GridData( SWT.NONE, SWT.NONE, false, false );
        compositeGridData.heightHint = 0;
        compositeGridData.widthHint = 0;
        composite.setLayoutData( compositeGridData );

        innerComposite = null;
    }


    /**
     * Creates the inner composite with its input fields.
     */
    private void create()
    {
        this.browserWidget.getViewer().addPostSelectionChangedListener( selectionListener );

        IDialogSettings dialogSettings = BrowserCommonActivator.getDefault().getDialogSettings();

        // Reseting the layout of the composite to be displayed correctly
        GridData compositeGridData = new GridData( SWT.FILL, SWT.NONE, true, false );
        composite.setLayoutData( compositeGridData );

        innerComposite = BaseWidgetUtils.createColumnContainer( composite, 5, 1 );

        String[] attributes = HistoryUtils.load( dialogSettings, ATTRIBUTE_HISTORY_DIALOGSETTING_KEY );
        quickSearchAttributeCombo = BaseWidgetUtils.createCombo( innerComposite, attributes, -1, 1 );
        quickSearchAttributePP = new ListContentProposalProvider( attributes );
        new ExtendedContentAssistCommandAdapter( quickSearchAttributeCombo, new ComboContentAdapter(),
            quickSearchAttributePP, null, null, true );
        quickSearchAttributeCombo.addModifyListener( new ModifyListener()
        {
            public void modifyText( ModifyEvent e )
            {
                quickSearchRunButton.setEnabled( !"".equals( quickSearchAttributeCombo.getText() ) ); //$NON-NLS-1$
            }
        } );
        quickSearchAttributeCombo.addSelectionListener( new SelectionAdapter()
        {
            public void widgetDefaultSelected( SelectionEvent e )
            {
                performSearch();
            }
        } );
        GridData gd = new GridData( GridData.FILL_HORIZONTAL );
        gd.widthHint = 50;
        quickSearchAttributeCombo.setLayoutData( gd );

        String[] operators = new String[]
            { "=", "!=", "<=", ">=", "~=" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
        quickSearchOperatorCombo = BaseWidgetUtils.createReadonlyCombo( innerComposite, operators, 0, 1 );
        GridData data = new GridData();
        quickSearchOperatorCombo.setLayoutData( data );

        String[] values = HistoryUtils.load( dialogSettings, VALUE_HISTORY_DIALOGSETTING_KEY );
        quickSearchValueCombo = BaseWidgetUtils.createCombo( innerComposite, values, -1, 1 );
        quickSearchValuePP = new ListContentProposalProvider( values );
        new ExtendedContentAssistCommandAdapter( quickSearchValueCombo, new ComboContentAdapter(), quickSearchValuePP,
            null, null, true );
        quickSearchValueCombo.addSelectionListener( new SelectionAdapter()
        {
            public void widgetDefaultSelected( SelectionEvent e )
            {
                performSearch();
            }
        } );
        gd = new GridData( GridData.FILL_HORIZONTAL );
        gd.widthHint = 50;
        quickSearchValueCombo.setLayoutData( gd );

        quickSearchScopeButton = new Button( innerComposite, SWT.TOGGLE );
        quickSearchScopeButton.setToolTipText( Messages.getString( "BrowserQuickSearchWidget.ScopeOneLevelToolTip" ) ); //$NON-NLS-1$
        quickSearchScopeButton.setImage( BrowserCommonActivator.getDefault().getImage(
            BrowserCommonConstants.IMG_SUBTREE ) );
        quickSearchScopeButton.setSelection( BrowserCommonActivator.getDefault().getPreferenceStore()
            .getBoolean( BrowserCommonConstants.PREFERENCE_BROWSER_QUICK_SEARCH_SUBTREE_SCOPE ) );
        quickSearchScopeButton.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                String one = Messages.getString( "BrowserQuickSearchWidget.ScopeOneLevelToolTip" ); //$NON-NLS-1$
                String sub = Messages.getString( "BrowserQuickSearchWidget.ScopeSubtreeToolTip" ); //$NON-NLS-1$
                boolean selected = quickSearchScopeButton.getSelection();
                quickSearchScopeButton.setToolTipText( selected ? sub : one );
                BrowserCommonActivator.getDefault().getPreferenceStore()
                    .setValue( BrowserCommonConstants.PREFERENCE_BROWSER_QUICK_SEARCH_SUBTREE_SCOPE, selected );
            }
        } );

        quickSearchRunButton = new Button( innerComposite, SWT.PUSH );
        quickSearchRunButton.setToolTipText( Messages.getString( "BrowserQuickSearchWidget.RunQuickSearch" ) ); //$NON-NLS-1$
        quickSearchRunButton.setImage( BrowserCommonActivator.getDefault().getImage(
            BrowserCommonConstants.IMG_QUICKSEARCH ) );
        quickSearchRunButton.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                performSearch();
            }
        } );

        setEnabled( getSelectedEntry() != null );

        composite.layout( true, true );
        parent.layout( true, true );
    }


    private void performSearch()
    {
        if ( !quickSearchRunButton.isEnabled() )
        {
            return;
        }

        IEntry entry = getSelectedEntry();
        if ( entry == null )
        {
            return;
        }

        IDialogSettings dialogSettings = BrowserCommonActivator.getDefault().getDialogSettings();

        HistoryUtils.save( dialogSettings, ATTRIBUTE_HISTORY_DIALOGSETTING_KEY, quickSearchAttributeCombo.getText() );
        String[] attributes = HistoryUtils.load( dialogSettings, ATTRIBUTE_HISTORY_DIALOGSETTING_KEY );
        quickSearchAttributeCombo.setItems( attributes );
        quickSearchAttributeCombo.select( 0 );
        HistoryUtils.save( dialogSettings, VALUE_HISTORY_DIALOGSETTING_KEY, quickSearchValueCombo.getText() );
        String[] values = HistoryUtils.load( dialogSettings, VALUE_HISTORY_DIALOGSETTING_KEY );
        quickSearchValueCombo.setItems( values );
        quickSearchValueCombo.select( 0 );
        quickSearchValuePP.setProposals( Arrays.asList( values ) );

        IBrowserConnection conn = entry.getBrowserConnection();

        QuickSearch quickSearch = new QuickSearch( entry, conn );
        quickSearch.getSearchParameter().setScope(
            quickSearchScopeButton.getSelection() ? SearchScope.SUBTREE : SearchScope.ONELEVEL );

        StringBuffer filter = new StringBuffer();
        filter.append( "(" ); //$NON-NLS-1$
        if ( "!=".equals( quickSearchOperatorCombo.getText() ) ) //$NON-NLS-1$
        {
            filter.append( "!(" ); //$NON-NLS-1$
        }
        filter.append( quickSearchAttributeCombo.getText() );
        filter
            .append( Messages.getString( "BrowserQuickSearchWidget.9" ).equals( quickSearchOperatorCombo.getText() ) ? "=" : quickSearchOperatorCombo.getText() ); //$NON-NLS-1$ //$NON-NLS-2$

        // only escape '\', '(', ')', and '\u0000'
        // don't escape '*' to allow substring search
        String value = quickSearchValueCombo.getText();
        value = value.replaceAll( "\\\\", "\\\\5c" ); //$NON-NLS-1$ //$NON-NLS-2$
        value = value.replaceAll( "\u0000", "\\\\00" ); //$NON-NLS-1$ //$NON-NLS-2$
        value = value.replaceAll( "\\(", "\\\\28" ); //$NON-NLS-1$ //$NON-NLS-2$
        value = value.replaceAll( "\\)", "\\\\29" ); //$NON-NLS-1$ //$NON-NLS-2$
        filter.append( value );
        if ( "!=".equals( quickSearchOperatorCombo.getText() ) ) //$NON-NLS-1$
        {
            filter.append( ")" ); //$NON-NLS-1$
        }
        filter.append( ")" ); //$NON-NLS-1$
        quickSearch.getSearchParameter().setFilter( filter.toString() );

        // set new quick search
        conn.setQuickSearch( quickSearch );

        // execute quick search
        new StudioBrowserJob( new SearchRunnable( new ISearch[]
            { quickSearch } ) ).execute();
    }


    private IEntry getSelectedEntry()
    {
        ISelection selection = browserWidget.getViewer().getSelection();
        IEntry[] entries = BrowserSelectionUtils.getEntries( selection );
        ISearch[] searches = BrowserSelectionUtils.getSearches( selection );
        if ( entries != null && entries.length == 1 )
        {
            IEntry entry = entries[0];
            return entry;
        }
        else if ( searches != null && searches.length == 1 && ( searches[0] instanceof IQuickSearch ) )
        {
            IQuickSearch quickSearch = ( IQuickSearch ) searches[0];
            IEntry entry = quickSearch.getSearchBaseEntry();
            return entry;
        }
        else
        {
            return null;
        }
    }


    /**
     * Destroys the inner widget.
     */
    private void destroy()
    {
        browserWidget.getViewer().removePostSelectionChangedListener( selectionListener );

        // Reseting the layout of the composite with a width and height set to 0
        GridData compositeGridData = new GridData( SWT.NONE, SWT.NONE, false, false );
        compositeGridData.heightHint = 0;
        compositeGridData.widthHint = 0;
        composite.setLayoutData( compositeGridData );

        innerComposite.dispose();
        innerComposite = null;

        composite.layout( true, true );
        parent.layout( true, true );
    }


    /**
     * Disposes this widget.
     */
    public void dispose()
    {
        if ( browserWidget != null )
        {
            quickSearchAttributeCombo = null;
            quickSearchOperatorCombo = null;
            quickSearchValueCombo = null;
            quickSearchRunButton = null;
            innerComposite = null;
            composite.dispose();
            composite = null;
            parent = null;
            browserWidget = null;
        }
    }


    /**
     * Enables or disables this quick search widget.
     * 
     * @param enabled true to enable this quick search widget, false to disable it
     */
    private void setEnabled( boolean enabled )
    {
        if ( composite != null && !composite.isDisposed() )
        {
            composite.setEnabled( enabled );
        }
        if ( innerComposite != null && !innerComposite.isDisposed() )
        {
            innerComposite.setEnabled( enabled );
            quickSearchAttributeCombo.setEnabled( enabled );
            quickSearchOperatorCombo.setEnabled( enabled );
            quickSearchValueCombo.setEnabled( enabled );
            quickSearchScopeButton.setEnabled( enabled );
            quickSearchRunButton.setEnabled( enabled && !"".equals( quickSearchAttributeCombo.getText() ) ); //$NON-NLS-1$

            if ( !enabled )
            {
                quickSearchAttributeCombo.setToolTipText( null );
                quickSearchOperatorCombo.setToolTipText( null );
                quickSearchValueCombo.setToolTipText( null );
                parent.setToolTipText( Messages.getString( "BrowserQuickSearchWidget.DisabledToolTipText" ) ); //$NON-NLS-1$
            }
            else
            {
                quickSearchAttributeCombo.setToolTipText( Messages
                    .getString( "BrowserQuickSearchWidget.SearchAttribute" ) ); //$NON-NLS-1$
                quickSearchOperatorCombo
                    .setToolTipText( Messages.getString( "BrowserQuickSearchWidget.SearchOperator" ) ); //$NON-NLS-1$
                quickSearchValueCombo.setToolTipText( Messages.getString( "BrowserQuickSearchWidget.SearchValue" ) ); //$NON-NLS-1$
                parent.setToolTipText( null );
            }
        }
    }


    /**
     * Activates or deactivates this quick search widget.
     *
     * @param visible true to create this quick search widget, false to destroy it
     */
    public void setActive( boolean visible )
    {
        if ( visible && innerComposite == null && composite != null )
        {
            create();
            Object input = browserWidget.getViewer().getInput();
            if ( input instanceof IBrowserConnection )
            {
                setInput( ( IBrowserConnection ) input );
            }
            else if ( input instanceof IEntry[] )
            {
                setInput( ( ( IEntry[] ) input )[0].getBrowserConnection() );
            }
            quickSearchAttributeCombo.setFocus();
        }
        else if ( !visible && innerComposite != null && composite != null )
        {
            destroy();
            browserWidget.getViewer().getTree().setFocus();
        }
    }


    /**
     * Sets the input.
     * 
     * @param connection the new input
     */
    public void setInput( IBrowserConnection connection )
    {
        if ( innerComposite != null && !innerComposite.isDisposed() )
        {
            String[] atdNames;
            if ( connection != null )
            {
                Collection<AttributeType> atds = connection.getSchema().getAttributeTypeDescriptions();
                atdNames = SchemaUtils.getNames( atds ).toArray( EMPTY );
            }
            else
            {
                atdNames = EMPTY;
                setEnabled( false );
            }
            quickSearchAttributePP.setProposals( Arrays.asList( atdNames ) );
        }
    }

}
