/*
 *  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.schemaeditor.view.dialogs;


import java.util.ArrayList;
import java.util.List;

import org.apache.directory.api.util.Strings;
import org.apache.directory.studio.schemaeditor.Activator;
import org.apache.directory.studio.schemaeditor.PluginUtils;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
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.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;


/**
 * This class implements dialog to manage aliases.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public abstract class AbstractAliasesDialog extends Dialog
{
    /** The aliases List */
    private List<String> initialLowerCasedAliases = new ArrayList<String>();
    private List<String> aliases = new ArrayList<String>();
    private List<String> lowerCasedAliases = new ArrayList<String>();

    /** The listener used to override the listerner on the RETURN key */
    private Listener returnKeyListener = new Listener()
    {
        public void handleEvent( Event event )
        {
            if ( event.detail == SWT.TRAVERSE_RETURN )
            {
                event.detail = SWT.TRAVERSE_TAB_NEXT;
                closeTableEditor();
            }
        }
    };

    // UI Fields
    private Table aliasesTable;
    private TableEditor tableEditor;
    private Button addButton;
    private Button editButton;
    private Button removeButton;
    private Composite errorComposite;
    private Image errorImage;
    private Label errorLabel;


    /**
     * Creates a new instance of AbstractAliasesDialog.
     *
     * @param aliases an array of aliases
     */
    public AbstractAliasesDialog( List<String> aliases )
    {
        super( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell() );
        if ( aliases != null )
        {
            for ( String alias : aliases )
            {
                initialLowerCasedAliases.add( Strings.toLowerCase( alias ) );
                this.aliases.add( alias );
                lowerCasedAliases.add( Strings.toLowerCase( alias ) );
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    protected Control createDialogArea( Composite parent )
    {
        // Creating the composite
        Composite composite = new Composite( parent, SWT.NONE );
        composite.setLayout( new GridLayout( 2, false ) );
        composite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );

        // Aliases Label
        Label aliasesLabel = new Label( composite, SWT.NONE );
        aliasesLabel.setText( Messages.getString( "AbstractAliasesDialog.Aliases" ) ); //$NON-NLS-1$
        aliasesLabel.setLayoutData( new GridData( SWT.FILL, SWT.NONE, true, true, 2, 1 ) );

        // Aliases Table
        aliasesTable = new Table( composite, SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION
            | SWT.HIDE_SELECTION );
        GridData gridData = new GridData( SWT.FILL, SWT.FILL, true, true, 1, 3 );
        gridData.heightHint = 90;
        gridData.minimumHeight = 90;
        gridData.widthHint = 200;
        gridData.minimumWidth = 200;
        aliasesTable.setLayoutData( gridData );

        // Aliases Table Editor
        tableEditor = new TableEditor( aliasesTable );
        tableEditor.horizontalAlignment = SWT.LEFT;
        tableEditor.grabHorizontal = true;
        tableEditor.minimumWidth = 200;

        // Add Button
        addButton = new Button( composite, SWT.PUSH );
        addButton.setText( Messages.getString( "AbstractAliasesDialog.Add" ) ); //$NON-NLS-1$
        addButton.setLayoutData( new GridData( SWT.FILL, SWT.NONE, false, false ) );

        // Edit Button
        editButton = new Button( composite, SWT.PUSH );
        editButton.setText( Messages.getString( "AbstractAliasesDialog.Edit" ) ); //$NON-NLS-1$
        editButton.setLayoutData( new GridData( SWT.FILL, SWT.NONE, false, false ) );
        editButton.setEnabled( false );

        // Remove Button
        removeButton = new Button( composite, SWT.PUSH );
        removeButton.setText( Messages.getString( "AbstractAliasesDialog.Remove" ) ); //$NON-NLS-1$
        removeButton.setLayoutData( new GridData( SWT.FILL, SWT.NONE, false, false ) );
        removeButton.setEnabled( false );

        // Error Composite
        errorComposite = new Composite( composite, SWT.NONE );
        errorComposite.setLayout( new GridLayout( 2, false ) );
        errorComposite.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true, 2, 1 ) );
        errorComposite.setVisible( false );

        // Error Image
        errorImage = PlatformUI.getWorkbench().getSharedImages().getImage( ISharedImages.IMG_OBJS_ERROR_TSK );
        Label label = new Label( errorComposite, SWT.NONE );
        label.setImage( errorImage );
        label.setSize( 16, 16 );

        // Error Label
        errorLabel = new Label( errorComposite, SWT.NONE );
        errorLabel.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
        errorLabel.setText( getAliasAlreadyExistsErrorMessage() );

        // Filling the Table with the given aliases
        fillAliasesTable();

        // Listeners initialization
        initListeners();

        // Checking the aliases
        checkAliases();

        return composite;
    }


    /**
     * Fills in the Aliases Table from the aliases list 
     */
    private void fillAliasesTable()
    {
        aliasesTable.removeAll();
        aliasesTable.setItemCount( 0 );
        for ( String alias : aliases )
        {
            TableItem newItem = new TableItem( aliasesTable, SWT.NONE );
            newItem.setText( alias );
        }
    }


    /**
     * Initializes the Listeners.
     */
    private void initListeners()
    {
        aliasesTable.addKeyListener( new KeyAdapter()
        {
            public void keyPressed( KeyEvent e )
            {
                if ( ( e.keyCode == SWT.DEL ) || ( e.keyCode == Action.findKeyCode( "BACKSPACE" ) ) ) //$NON-NLS-1$
                {
                    removeSelectedAliases();
                    fillAliasesTable();
                    updateButtonsState();
                    checkAliases();
                }
            }
        } );
        aliasesTable.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                closeTableEditor();
                updateButtonsState();
            }
        } );
        aliasesTable.addListener( SWT.MouseDoubleClick, new Listener()
        {
            public void handleEvent( Event event )
            {
                openTableEditor( aliasesTable.getItem( aliasesTable.getSelectionIndex() ) );
            }
        } );

        // Aliases Table's Popup Menu
        Menu menu = new Menu( getShell(), SWT.POP_UP );
        aliasesTable.setMenu( menu );
        MenuItem removeMenuItem = new MenuItem( menu, SWT.PUSH );
        removeMenuItem.setText( Messages.getString( "AbstractAliasesDialog.Remove" ) ); //$NON-NLS-1$
        removeMenuItem.setImage( PlatformUI.getWorkbench().getSharedImages().getImage( ISharedImages.IMG_TOOL_DELETE ) );
        removeMenuItem.addListener( SWT.Selection, new Listener()
        {
            public void handleEvent( Event event )
            {
                removeSelectedAliases();
                fillAliasesTable();
                updateButtonsState();
                checkAliases();
            }
        } );

        // Add Button
        addButton.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                addANewAlias();
            }
        } );

        // Edit Button
        editButton.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                openTableEditor( aliasesTable.getItem( aliasesTable.getSelectionIndex() ) );
            }
        } );

        // Remove Button
        removeButton.addSelectionListener( new SelectionAdapter()
        {
            public void widgetSelected( SelectionEvent e )
            {
                removeSelectedAliases();
                fillAliasesTable();
                updateButtonsState();
                checkAliases();
            }
        } );
    }


    /**
     * Updates the state of the buttons.
     */
    private void updateButtonsState()
    {
        if ( aliasesTable.getSelectionCount() >= 1 )
        {
            editButton.setEnabled( true );
            removeButton.setEnabled( true );
        }
        else
        {
            editButton.setEnabled( false );
            removeButton.setEnabled( false );
        }
    }


    /**
     * Removes the selected aliases.
     */
    private void removeSelectedAliases()
    {
        TableItem[] selectedItems = aliasesTable.getSelection();
        for ( TableItem item : selectedItems )
        {
            aliases.remove( item.getText() );
            lowerCasedAliases.remove( Strings.toLowerCase( item.getText() ) );
        }
    }


    /**
     * Adds a new alias
     */
    private void addANewAlias()
    {
        TableItem item = new TableItem( aliasesTable, SWT.NONE );
        item.setText( "" ); //$NON-NLS-1$
        openTableEditor( item );
    }


    /**
     * Opens the {@link TableEditor} on the given {@link TableItem}.
     *
     * @param item
     *      the {@link TableItem}
     */
    private void openTableEditor( TableItem item )
    {
        // Clean up any previous editor control
        Control oldEditor = tableEditor.getEditor();
        if ( oldEditor != null )
            oldEditor.dispose();

        if ( item == null )
            return;

        // The control that will be the editor must be a child of the Table
        Text newEditor = new Text( aliasesTable, SWT.NONE );
        newEditor.setText( item.getText() );
        newEditor.addModifyListener( new ModifyListener()
        {
            public void modifyText( ModifyEvent e )
            {
                saveTableEditorText();
            }
        } );
        newEditor.addKeyListener( new KeyAdapter()
        {
            public void keyPressed( KeyEvent e )
            {
                if ( ( e.keyCode == Action.findKeyCode( "RETURN" ) ) || ( e.keyCode == SWT.KEYPAD_CR ) ) //$NON-NLS-1$
                {
                    closeTableEditor();
                }
            }
        } );
        newEditor.selectAll();
        newEditor.setFocus();
        tableEditor.setEditor( newEditor, item, 0 );
        Activator.getDefault().getWorkbench().getDisplay().addFilter( SWT.Traverse, returnKeyListener );
    }


    /**
     * Saves the {@link TableEditor} text.
     */
    private void saveTableEditorText()
    {
        Text text = ( Text ) tableEditor.getEditor();
        if ( ( text != null ) && ( !text.isDisposed() ) )
        {
            TableItem item = tableEditor.getItem();
            String oldText = item.getText();
            String newText = text.getText();
            if ( !oldText.equals( newText ) )
            {
                aliases.remove( oldText );
                lowerCasedAliases.remove( Strings.toLowerCase( oldText ) );
                if ( !newText.equals( "" ) ) //$NON-NLS-1$
                {
                    aliases.add( newText );
                    lowerCasedAliases.add( Strings.toLowerCase( newText ) );
                }
                item.setText( newText );
            }
        }
        checkAliases();
    }


    /**
     * Closes the {@link TableEditor}.
     */
    private void closeTableEditor()
    {
        Text text = ( Text ) tableEditor.getEditor();
        if ( ( text != null ) && ( !text.isDisposed() ) )
        {
            saveTableEditorText();
            text.dispose();
        }
        Activator.getDefault().getWorkbench().getDisplay().removeFilter( SWT.Traverse, returnKeyListener );
    }


    /**
     * Checks the aliases.
     */
    private void checkAliases()
    {
        errorComposite.setVisible( false );

        for ( String alias : aliases )
        {
            if ( ( isAliasAlreadyTaken( alias ) )
                && ( !initialLowerCasedAliases.contains( Strings.toLowerCase( alias ) ) ) )
            {
                errorComposite.setVisible( true );
                errorLabel.setText( getAliasAlreadyExistsErrorMessage() );
                return;
            }
            else if ( !PluginUtils.verifyName( alias ) )
            {
                errorComposite.setVisible( true );
                errorLabel.setText( NLS.bind( Messages.getString( "AbstractAliasesDialog.InvalidAlias" ), new String[] //$NON-NLS-1$
                    { alias } ) );
                return;
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    protected void configureShell( Shell newShell )
    {
        super.configureShell( newShell );
        newShell.setText( Messages.getString( "AbstractAliasesDialog.EditAlias" ) ); //$NON-NLS-1$
    }


    /**
     * Returns the aliases.
     *  
     * @return
     *      the aliases
     */
    public String[] getAliases()
    {
        return aliases.toArray( new String[0] );
    }


    /**
     * Gets the error message in the case where an identical alias already exists.
     *
     * @return the error message
     */
    protected abstract String getAliasAlreadyExistsErrorMessage();


    /**
     * Checks if the given alias is already taken.
     *
     * @return <code>true</code> if the given alias is already taken,
     *         <code>false</code> if not.
     */
    protected abstract boolean isAliasAlreadyTaken( String alias );
}
